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.

Reply via email to