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