Is this a violation of const?

Timon Gehr timon.gehr at gmx.ch
Sat Jul 30 10:02:50 UTC 2022


On 7/30/22 00:16, H. S. Teoh wrote:
> On Fri, Jul 29, 2022 at 09:56:20PM +0000, Andrey Zherikov via Digitalmars-d-learn wrote:
>> In the example below `func` changes its `const*` argument. Does this
>> violates D's constness?
>>
>> ```d
>> import std;
>>
>> struct S
>> {
>>      string s;
>>
>>      void delegate(string s) update;
>> }
>>
>> void func(const S* s)
>> {
>>      writeln(*s);
>>      s.update("func");
>>      writeln(*s);
>> }
>>
>> void main()
>> {
>>      auto s = S("test");
>>      s.update = (_) { s.s = _; };
>>
>>      writeln(s);
>>      func(&s);
>>      writeln(s);
>> }
>> ```
>>
>> The output is:
>> ```
>> S("test", void delegate(string))
>> const(S)("test", void delegate(string))
>> const(S)("func", void delegate(string))
>> S("func", void delegate(string))
>> ```
> 
> At first I thought this was a bug in the const system,

It very much is. https://issues.dlang.org/show_bug.cgi?id=9149
(Note that the fix proposed in the first post is not right, it's the 
call that should be disallowed.)

> but upon closer
> inspection, this is expected behaviour. The reason is, `const`
> guarantees no changes *only on the part of the recipient* of the `const`
> reference;

The delegate _is_ the recipient of the delegate call. The code is 
calling a mutable method on a `const` receiver.

> it does not guarantee that somebody else doesn't have a
> mutable reference to the same data.  For the latter, you want immutable
> instead of const.
> 
> So in this case, func receives a const reference to S, so it cannot
> modify S. However, the delegate created by main() *can* modify the data,

This delegate is not accessible in `func`, only a `const` version.

> because it holds a mutable reference to it. So when func invokes the
> delegate, the delegate modifies the data thru its mutable reference.
> ...

`const` is supposed to be transitive, you can't have a `const` delegate 
that modifies data through 'its mutable reference'.

> Had func been declared with an immutable parameter, it would have been a
> different story, because you cannot pass a mutable argument to an
> immutable parameter, so compilation would fail. Either s was declared
> mutable and the delegate can modify it, but you wouldn't be able to pass
> it to func(), or s was declared immutable and you can pass it to func(),
> but the delegate creation would fail because it cannot modify immutable.
> 
> In a nutshell, `const` means "I cannot modify the data", whereas
> `immutable` means "nobody can modify the data". Apparently small
> difference, but actually very important.
> 
> 
> T
> 

This is a case of "I am modifying the data anyway, even though am 
`const`." Delegate contexts are not exempt from type checking. A `const` 
existential type is still `const`.

What the code is doing is basically the same as this:

```d
import std;

struct Updater{
     string *context;
     void opCall(string s){ *context=s; }
}

struct S{
     string s;
     Updater update;
}

void func(const S* s){
     writeln(*s);
     s.update("func");
     writeln(*s);
}

void main(){
     auto s = S("test");
     s.update = Updater(&s.s);

     writeln(s);
     func(&s);
     writeln(s);
}
```

It's a `const` hole, plain and simple.


More information about the Digitalmars-d-learn mailing list