www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Delegates and qualifier transitivity

reply Dukc <ajieskola gmail.com> writes:
D type qualifiers - `const`, `immutable`, `shared` and `inout` - 
are supposed to be transitive.

Delegates are supposed to work like structs that contain a 
function pointer and an untyped context pointer. In my opinion at 
least, this means the context pointer should have the same 
qualifiers as the delegate itself. In other words, 
`immutable(void delegate())` should be the same as 
`immutable(void delegate() immutable)` (it's still fine if the 
return type is mutable.).

Currently the compiler seems so buggy in this regard that I'm not 
sure what it tries to do. While testing, I even managed to get a 
declaration like `immutable del = &immutableStruct.immutableFun;` 
to have the compiler say "Error: cannot implicitly convert 
expression `&immutableStruct.immutableFun` of type `int 
delegate() immutable  safe` to `immutable(int delegate()  safe)`"

Which leads to implicit conversions between delegates being wrong:

```D
struct S
{   int field;

      safe mutableFun(){}
      safe constFun() const{}
      safe immutableFun() immutable{}
}

 safe void main()
{   void delegate()  safe mut;
     void delegate()  safe const con;
     void delegate()  safe immutable imm;

     S s1;
     immutable S s2;

     mut = &s1.mutableFun; //Compiles, as it should
     mut = &s1.constFun; //Compiles as it should
     mut = &s2.immutableFun; //Doesn't compile but should

     con = &s1.mutableFun; //Doesn't compile and neither should
     con = &s1.constFun; //Compiles as it should
     con = &s2.immutableFun; //Doesn't compile but should

     imm = &s1.mutableFun; //Doesn't compile and neither should
     imm = &s1.constFun; //Compiles but shouldn't
     imm = &s2.immutableFun; //Compiles, as it should
}
```

You might be wondering why `mut = &s2.immutableFun;` should be 
allowed in safe code - a context pointer to mutable even when the 
struct is immutable. Well, the pointer is typed as `void*`. You 
can't mutate through that in ` safe` code, and calling the 
delegate will not do so either since the referred function 
actually takes the context pointer as immutable. The compiler 
guarantees it does, as it rejects delegates like `&s2.mutableFun`.

On the other hand, `imm = &s1.constFun;` is dangerous. While 
`constFun` itself wont mutate the struct, immutability assumes 
no-one will mutate the data. The compiler is free to assume the 
context of the delegate will remain untouched by other things 
done in the calling function, yet clearly the assumption could be 
easily broken by mutating `s1`, being a mutable struct. Also, an 
immutable delegate could be stored to thread-shared memory, and 
the results of calling it would depend on a thread-local context 
- the very issue `immutable` and `shared` are supposed to prevent.

Changing the behaviour will in all likelihood lead to fairly 
large breakage, which means it must be done over an edition 
switch. But first, before I go on to write an actual DIP (and 
maybe the proposed compiler changes also), I'd like to ask you to 
check my assessment. Is there a reason other than just plain 
misdesign why it works how it works right now? If not, the fix it 
pictured above seems clear to me, but is it really? Is there 
another way to solve this that should be considered?
Dec 05
parent Dukc <ajieskola gmail.com> writes:
On Friday, 5 December 2025 at 16:37:35 UTC, Dukc wrote:
 If not, the fix it pictured above seems clear to me, but is it 
 really?
Duh, there is a hole in my proposal as [Timon noted in a DMD issue suggesting part of what I did](https://github.com/dlang/dmd/issues/19131#issuecomment-2541990651). Yet the way how it works now is also still clearly wrong. I have to rethink this. I suspect the solution will have to be quite hairy to be sound. Ideas, anyone?
Dec 08