digitalmars.dip.ideas - Delegates and qualifier transitivity
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
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








Dukc <ajieskola gmail.com>