Uncallable delegates

Meta jared771 at gmail.com
Sat May 16 07:47:36 UTC 2026


On Saturday, 16 May 2026 at 06:57:56 UTC, Timon Gehr wrote:
> On 5/16/26 08:31, Meta wrote:
>> On Saturday, 16 May 2026 at 06:22:18 UTC, Timon Gehr wrote:
>>> On 5/16/26 06:40, Meta wrote:
>>>> I guess the solution is that `immutable(int* delegate())` 
>>>> should become `int* delegate() immutable`.
>>>
>>> No.
>>>
>>> Here is how D catches the same problem for classes:
>>>
>>> ```d
>>> @safe
>>> class C{
>>>     int* x;
>>>     this(int* x)pure{ this.x=x; }
>>>     int* foo(){ return x; }
>>> }
>>>
>>> C foo()pure => new C(new int(2));
>>>
>>> void main(){
>>>     immutable c = foo(); // ok
>>>     c.foo(); // error
>>> }
>>> ```
>>>
>>> ```
>>> Error: mutable method `tt.C.foo` is not callable using a 
>>> `immutable` object
>>> ```
>>>
>>>
>>> However, delegates are actually not exactly > the same case 
>>> as classes.
>> 
>> Why not? All the way down to the bedrock of CS theory, these 
>> are equivalent cases.
>> ...
>
> I gave one difference in the next sentence, and your answer was 
> "sure".

Okay, then, *why* is calling immutable(int* delegate()) sound, 
but calling foo() on an immutable C is not? In both cases, you're 
calling a method that returns an immutable value but has an 
immutable context. I can't see how they're different.

> It is a well-known property of equivalence that equivalent 
> concepts have equivalent properties.
>
>>> Calling `immutable(T delegate(S))` would be sound if there 
>>> were no safe way to convert mutable references to `immutable` 
>>> references.
>> 
>> Sure, but in D, a way does exist, so it's a moot point.
>> ...
>
> Just take `shared` then.
>
>>> The existence of `pure` factory functions means that in this 
>>> case delegates do have to behave like classes. It is a global 
>>> interaction.
>> 
>> Is your example not pretty much exactly equivalent to the fix 
>> I proposed?
>
> No. On the other hand if you had said `immutable(int* 
> delegate())` should not be callable, but `immutable(int* 
> delegate()immutable)` can keep being callable, then I would not 
> have disagreed.
>
>> What are you disagreeing with me about?
>
> All the ways I am able to read the ambiguous sentence 
> `immutable(int*) delegate())` should become `int* delegate() 
> immutable`" are a wrong statement:
>
> - immutable(int* delegate()) should not decay to `int* 
> delegate()immutable` either before or after fixing the type 
> system.
>
> - `immutable(int* delegate())` should not be the same type as 
> int* delegate()immutable either before or after fixing
>
> - `immutable(int* delegate())` before the fix should not have 
> the same meaning as `int* delegate()immutable` after the fix
>
> Maybe you mean something else.

What I mean is this:
```d
struct Delegate
{
     void* funcptr;
     void* context;
}
```

Currently immutable(int* delegate()) means that d.funcptr is 
immutable, but d.context is still mutable. So my suggestion is 
that it also make d.context immutable. This would disallow the 
code with UB that you outlined, because:

struct T{
     int* delegate() dg;
     int* q;
}

..

immutable ps = foo();

ps' type is immutable(T), so ps.dg is immutable(int* delegate()), 
which with my suggested change, "decays" to immutable(int* 
delegate() immutable), and now the compiler catches this because 
a delegate with an immutable context pointer is not allowed to 
access mutable data.


More information about the dip.development mailing list