Thread overview
Final type qualifier
Jul 19
ryuukk_
Jul 20
harakim
Jul 23
vit
July 18

Add the final type to the language. It is head-const like in Java. Also, make type qualifiers to class references refer to the object, not the reference, except final.

That is, const(final(int)[]) is const(int[]) because const goes down transitively and supersedes final. Similarly, final(const(int[])) is the same as const(int[]), but a final(const(Object)) isn’t just const(Object) as the latter is assignable.

If a class object is accessed via a const reference, the language must add both final and const to the result.

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.

July 19

'const(final(int)[])'

we are reaching new levels of obfuscation, D needs less, not more, D needs to pursue conciseness, mot Javaness

July 19

On Friday, 19 July 2024 at 08:25:18 UTC, ryuukk_ wrote:

>

'const(final(int)[])'

That type would be const(int[]).

July 20

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.

It seems to me that there are two things that const (as a parameter) means:

  1. The caller guarantees they will not change the value
  2. The callee guarantees they will not change the value

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.

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? 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.

Hopefully that was not too dumb. I have been wanting to say something about this for a while, but I'm also not an expert.

July 22

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:

  1. The caller guarantees they will not change the value
  2. 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.

July 23

On Thursday, 18 July 2024 at 10:27:29 UTC, Quirin Schroll wrote:

>

Add the final type to the language. It is head-const like in Java. Also, make type qualifiers to class references refer to the object, not the reference, except final.

That is, const(final(int)[]) is const(int[]) because const goes down transitively and supersedes final. Similarly, final(const(int[])) is the same as const(int[]), but a final(const(Object)) isn’t just const(Object) as the latter is assignable.

If a class object is accessed via a const reference, the language must add both final and const to the result.

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.

Nice idea. Having final pointer will by nice.

Some little problems:

const is type qualifier and method qualifier.
final is already a method qualifier with different meaning. Some other new keyword will be better, for example sealed or other (method qualifier will be nice too).

What does final(ClassType) do? Is it like final(T)*, final(T*) or final(final(T)*)? (special handling of reference types is very annoying in D )


void foo(final(T*) x);
void foo(const(T*) x);

void main(){
	T* ptr;
	foo(ptr);	//what is called and why?
}

Is needed to specify implicit qualifier conversions from and to:
final
final inout
final shared
final shared inout

Try expand:
https://dlang.org/spec/const3.html#combining_qualifiers
https://dlang.org/spec/const3.html#implicit_qualifier_conversions