www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is this a violation of const?

reply Andrey Zherikov <andrey.zherikov gmail.com> writes:
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))
```
Jul 29 2022
next sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
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, 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; 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, because it holds a mutable reference to it. So when func invokes the delegate, the delegate modifies the data thru 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 -- Error: Keyboard not attached. Press F1 to continue. -- Yoon Ha Lee, CONLANG
Jul 29 2022
next sibling parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Friday, 29 July 2022 at 22:16:26 UTC, H. S. Teoh wrote:

This totally makes sense. Thanks for explanation!
Jul 29 2022
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Jul 30 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Saturday, 30 July 2022 at 10:02:50 UTC, Timon Gehr wrote:
 
 It's a `const` hole, plain and simple.
This code, which consists of 26 lines, does not compile in DMD 2.087. I am getting this error:
 constHole.d(15): Error: mutable method `source.Updater.opCall` 
 is not callable using a `const` object
constHole.d(15): Consider adding `const` or `inout` to source.Updater.opCall constHole.d(21): Error: function `source.Updater.opCall(string s)` is not callable using argument types `(string*)` constHole.d(21): cannot pass argument `&s.s` of type `string*` to parameter `string s` SDB 79
Jul 30 2022
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 7/30/22 15:19, Salih Dincer wrote:
 On Saturday, 30 July 2022 at 10:02:50 UTC, Timon Gehr wrote:
 It's a `const` hole, plain and simple.
This code, which consists of 26 lines, does not compile in DMD 2.087.  I am getting this error:
 constHole.d(15): Error: mutable method `source.Updater.opCall` is not 
 callable using a `const` object
constHole.d(15):        Consider adding `const` or `inout` to source.Updater.opCall constHole.d(21): Error: function `source.Updater.opCall(string s)` is not callable using argument types `(string*)` constHole.d(21):        cannot pass argument `&s.s` of type `string*` to parameter `string s` SDB 79
Exactly. This is my point. This code does not compile, and neither should the original version, because it's doing basically the same thing.
Jul 30 2022
prev sibling next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Friday, 29 July 2022 at 21:56:20 UTC, Andrey Zherikov wrote:
 In the example below `func` changes its `const*` argument. Does 
 this violates D's constness?
It's smart to use `delegate`, but `immutable` doesn't necessarily mean `const`. So if we use `const char`: ```d struct S { char s; void delegate(char s) update; } void func(const S* s) { writeln(*s); s.update('D'); writeln(*s); } import std.stdio; void main() { auto s = S('C'); s.update = (_) { s.s = _; }; writeln(s); func(&s); writeln(s); } /* Prints: S('C', void delegate(char)) const(S)('C', const(void delegate(char))) const(S)('D', const(void delegate(char))) S('D', void delegate(char)) */ ``` SDB 79
Jul 29 2022
parent Salih Dincer <salihdb hotmail.com> writes:
On Friday, 29 July 2022 at 23:15:14 UTC, Salih Dincer wrote:
 It's smart to use `delegate`, but `immutable` doesn't 
 necessarily mean `const`.  So if we use `const char`:

 ```d
 struct S
 {
     char s;

     void delegate(char s) update;
 }
 ```
Pardon 😀 I forgot the assert test, also writing const in front of char... Won't compile now: ```d struct S { const char s; void delegate(const char s) update; } ``` SDB 79
Jul 29 2022
prev sibling parent reply ag0aep6g <anonymous example.com> writes:
On 29.07.22 23:56, Andrey Zherikov wrote:
 In the example below `func` changes its `const*` argument. Does this 
 violates D's constness?
Yes. Here's a modified example to show that you can also violate `immutable` this way: struct S { string s; void delegate(string s) safe update; } S* makeS() pure safe { auto s = new S("test"); s.update = (_) { s.s = _; }; return s; } void main() safe { immutable S* s = makeS(); assert(s.s == "test"); /* passes */ s.update("func"); auto ss = s.s; assert(ss == "test"); /* fails; immutable data has changed */ }
Jul 29 2022
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Saturday, 30 July 2022 at 06:04:16 UTC, ag0aep6g wrote:
 Yes. Here's a modified example to show that you can also 
 violate `immutable` this way:
It's possible to do this because it's immutable. You don't need an extra update() function anyway. ```d void main() { auto s = S("test A"); s.update = (_) { s.s = _; }; s.update("test B"); assert(s.s == "test B"); s.s = "test C"; assert(s.s == "test C"); } // No compile error... ``` SDB 79
Jul 30 2022
parent reply ag0aep6g <anonymous example.com> writes:
On 30.07.22 09:15, Salih Dincer wrote:
 It's possible to do this because it's immutable.  You don't need an 
 extra update() function anyway.
 
 ```d
 void main()
 {
      auto s = S("test A");
      s.update = (_) { s.s = _; };
 
      s.update("test B");
      assert(s.s == "test B");
 
      s.s = "test C";
      assert(s.s == "test C");
 } // No compile error...
 ```
You're not making sense. Your `s` is mutable, not immutable.
Jul 30 2022
parent Salih Dincer <salihdb hotmail.com> writes:
On Saturday, 30 July 2022 at 10:34:09 UTC, ag0aep6g wrote:
 You're not making sense. Your `s` is mutable, not immutable.
You're right! I saw the hole at the end of the tunnel late 😀 But if you compile the example below without the `new operator`, the system does not work and does not give any errors. Why? **Voldermort Type Version:** ```d auto imstr(string str) pure safe { struct IMSTR { string s; void delegate(string s) safe update; string toString() const { return s; } } auto s = new IMSTR(str); s.update = (_) { s.s = _; }; return s; } import std.stdio; void main() safe { immutable auto str = imstr("Test 123"); //str.s = "test"; str.toString.writeln; str.update("TEST A"); str.toString.writeln; str.update("TEST B"); str.toString.writeln; typeid(str).writeln; }/* Prints: Test 123 TEST A TEST B immutable(immutable(onlineapp.imstr(immutable(char)[]).IMSTR)*) */ ``` SDB 79
Jul 30 2022