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.