On Saturday, 20 July 2024 at 06:55:27 UTC, harakim wrote:
> On Thursday, 18 July 2024 at 10:27:29 UTC, Quirin Schroll wrote:
> This solves two unrelated issues:
-
const class objects can’t be referred to by assignable handles. It requires some trickery which probably breaks the type system. Generally, if a function returns a non-mutable class object, it’s not justified why the caller must be unable to re-assign the variable the result is put in.
-
const delegates are broken as they don’t respect const guarantees. As of today, const delegates are factually final delegates: They can’t be reassigned, but their context may change through the context pointer that’s part of the delegate. If we had final in a new edition, old-edition const delegates could become final delegates in the new edition and the new edition could fix const delegates.
Note: In your whole post, it seems you conflated const with immutable.
> It seems to me that there are two things that const (as a parameter) means:
- The caller guarantees they will not change the value
- The callee guarantees they will not change the value
Point 1 is already wrong. const means the callee won’t change values through accessing this value. The callee may change the very same value through another, mutable reference.
Point 2 only applies to immutable, which also includes point 1.
> This gets really interesting with immutable, but even with strings, having to cast char[] to const(char)[]. If the callee is okay getting a char[] that is not const, const should fulfil; the same need, but since const means two things you have to cast. It's not that big of a deal, but with immutable it can be.
Anything can be converted to const implicitly (except shared, which does not interact with const). That is for the simple reason that const simply forgets and only has outward guarantees.
> import std.stdio;
public void main()
{
immutable(char[]) world = "world";
bongo(world);
}
void bongo(char[] x)
{
writeln("Bongo " ~ x);
}
bongo could guarantee that it does not mutate the data, but it doesn't want to require the caller to pass in immutable data because it doesn't care if the data is mutated by the caller. That's the caller's issue. So what should it put for the modifier?
const; also inout would work:
void bongo(const(char)[] x);
// or
void bongo(inout(char)[] x);
> I feel like final is one of these cases where it's half of a guarantee. You could put final on bongo and it could be smart enough to see that it can pass immutable data or not.
This is one area that really tripped me up when I was trying to write multi-threaded code. I had a bunch of data that was initialized and then was effectively immutable from then on. However, it was very unintuitive that I had to copy to a new memory location.
In D, mutable implicitly converts to immutable if the value is unique. If you write a function that returns a LinkedList!T (random non-trivial example type, one with indirections) and that function is pure and has no parameters through which parts of the returned list could also be exposed (the easiest way to guarantee this is making parameters const or immutable), the returned list is unique. The caller may assign the returned list to an immutable-type variable.
Nothing of this is related to final. The final qualifier would be const, but only applies to the first layer of indirection. At ~10 years ago, I realized that a template Final for Final!T can’t really work with struct types. If a T member function is not qualified, it can mutate the first layer of indirection and thus is out for Final!T, but requiring a member function to be const to be called for a Final!T object is also wrong as it asks too much.
For a final(int[]), you could re-assign the elements, but not append to it.