www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D does have head const (but maybe it shouldn't)

reply ag0aep6g <anonymous example.com> writes:
Exercise: Consider the following `main` function. Define `f` and `g` so 
that the code is valid, compiles, and runs successfully. The asserts 
must be executed and they must pass.

----
void main() pure  safe
{
     int* x = new int;
     const y = f(x);
     *x = 1;
     g(y);
     assert(*x == 2);
}
----

Hints:
* You can't store `x` in a global because the code is `pure`.
* You can't cast `const` away in `g` because that would be invalid.
* You're supposed to find a type for which `const` means head const.

Solution: https://run.dlang.io/is/dsaGFS

The validity of the given solution might be arguable, and I'd be in 
favour of outlawing it. It's surprising that there's a type for which 
`const` means head const when it means transitive const for everything 
else. It's so surprising that even DMD trips over it 
(<https://issues.dlang.org/show_bug.cgi?id=21511>).
Dec 29 2020
next sibling parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Tuesday, 29 December 2020 at 16:15:56 UTC, ag0aep6g wrote:
 Exercise: Consider the following `main` function. Define `f` 
 and `g` so that the code is valid, compiles, and runs 
 successfully. The asserts must be executed and they must pass.

 ----
 void main() pure  safe
 {
     int* x = new int;
     const y = f(x);
     *x = 1;
     g(y);
     assert(*x == 2);
 }
 ----

 Hints:
 * You can't store `x` in a global because the code is `pure`.
 * You can't cast `const` away in `g` because that would be 
 invalid.
 * You're supposed to find a type for which `const` means head 
 const.

 Solution: https://run.dlang.io/is/dsaGFS

 The validity of the given solution might be arguable, and I'd 
 be in favour of outlawing it. It's surprising that there's a 
 type for which `const` means head const when it means 
 transitive const for everything else. It's so surprising that 
 even DMD trips over it 
 (<https://issues.dlang.org/show_bug.cgi?id=21511>).
Wow. The solution surprised me, even though in retrospect it's obvious to me why it works (bugs in this area of the type system have been know for more than a few years). I'd say head-const is a very useful tool, but it's pretty obvious to me that this shouldn't be a supported way to implement it.
Dec 29 2020
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Dec 29, 2020 at 05:13:48PM +0000, Petar via Digitalmars-d wrote:
 On Tuesday, 29 December 2020 at 16:15:56 UTC, ag0aep6g wrote:
[...]
 ----
 void main() pure  safe
 {
     int* x = new int;
     const y = f(x);
     *x = 1;
     g(y);
     assert(*x == 2);
 }
 ----
[...]
 The validity of the given solution might be arguable, and I'd be in
 favour of outlawing it. It's surprising that there's a type for
 which `const` means head const when it means transitive const for
 everything else. It's so surprising that even DMD trips over it
 (<https://issues.dlang.org/show_bug.cgi?id=21511>).
Wow. The solution surprised me, even though in retrospect it's obvious to me why it works (bugs in this area of the type system have been know for more than a few years). I'd say head-const is a very useful tool, but it's pretty obvious to me that this shouldn't be a supported way to implement it.
I don't see why this is considered head const, nor why this should be considered a bug. In the proposed solution `y` is not a value type but a delegate that wraps the reference to *x. Since x itself is non-const, this is perfectly valid, and the `const` in `const y` refers to the immutability of the delegate reference, not to the immutability of the wrapped reference. T -- GEEK = Gatherer of Extremely Enlightening Knowledge
Dec 29 2020
next sibling parent ag0aep6g <anonymous example.com> writes:
On Tuesday, 29 December 2020 at 17:56:51 UTC, H. S. Teoh wrote:
 On Tue, Dec 29, 2020 at 05:13:48PM +0000, Petar via 
 Digitalmars-d wrote:
 On Tuesday, 29 December 2020 at 16:15:56 UTC, ag0aep6g wrote:
[...]
 The validity of the given solution might be arguable, and 
 I'd be in favour of outlawing it. It's surprising that 
 there's a type for which `const` means head const when it 
 means transitive const for everything else. It's so 
 surprising that even DMD trips over it 
 (<https://issues.dlang.org/show_bug.cgi?id=21511>).
Wow. The solution surprised me, even though in retrospect it's obvious to me why it works (bugs in this area of the type system have been know for more than a few years). I'd say head-const is a very useful tool, but it's pretty obvious to me that this shouldn't be a supported way to implement it.
I don't see why this is considered head const, nor why this should be considered a bug. In the proposed solution `y` is not a value type but a delegate that wraps the reference to *x. Since x itself is non-const, this is perfectly valid, and the `const` in `const y` refers to the immutability of the delegate reference, not to the immutability of the wrapped reference.
How are you not just describing head const? A thing that is itself const and refers to something mutable => head const. You know how a dynamic array (or slice) is often described as a pointer plus a length. Explanations to newbies often contain a mockup struct like this: struct Slice(E) { E* ptr; size_t length; } Because that's what a slice is, right? And when the newbie starts to think about it that way, things often fall into place for them. Similarly, it makes sense to think of a delegate like this: struct Delegate(C, R, P ...) { C* context_ptr; R function(C* context_ptr, P params) funcptr; R opCall(P params) { return funcptr(context_ptr, params); } } (Ignoring details like how the context pointer is actually passed.) Now, when you have a `const Delegate!(int, void, /* no parameters */)`, its `context_ptr` is also const and you cannot gain mutable access to the referenced int. Because that's how `const` works in D (for the most part). But that restriction does not apply to a `const void delegate()` whose context pointer is an `int*`. I think it would save us some headaches if delegates behaved like the structs that they really are.
Dec 29 2020
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 29 December 2020 at 17:56:51 UTC, H. S. Teoh wrote:
 I don't see why this is considered head const, nor why this 
 should be considered a bug. In the proposed solution `y` is not 
 a value type but a delegate that wraps the reference to *x.  
 Since x itself is non-const, this is perfectly valid, and the 
 `const` in `const y` refers to the immutability of the delegate 
 reference, not to the immutability of the wrapped reference.
It's a bug because `const` should apply transitively to both the delegate's function pointer and its context pointer. The easiest way to see this is to replace the delegate with a hand-written closure struct: struct Closure { int* p; this(inout int* p) pure safe inout { this.p = p; } void opCall() pure safe { *p = 2; } } auto f(int* p) pure safe { return Closure(p); } void g(const Closure y) pure safe { y(); } In this version, `g` will fail to compile, because it is trying to call a mutable method using a const object.
Dec 29 2020
prev sibling next sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 29 December 2020 at 17:56:51 UTC, H. S. Teoh wrote:
 On Tue, Dec 29, 2020 at 05:13:48PM +0000, Petar via 
 Digitalmars-d wrote:
 [...]
[...]
 [...]
[...]
 [...]
I don't see why this is considered head const, nor why this should be considered a bug. In the proposed solution `y` is not a value type but a delegate that wraps the reference to *x. Since x itself is non-const, this is perfectly valid, and the `const` in `const y` refers to the immutability of the delegate reference, not to the immutability of the wrapped reference. T
It's a const variable that contains a mutable reference. It's head-const.
Dec 29 2020
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Tuesday, 29 December 2020 at 19:42:28 UTC, John Colvin wrote:
 On Tuesday, 29 December 2020 at 17:56:51 UTC, H. S. Teoh wrote:
 On Tue, Dec 29, 2020 at 05:13:48PM +0000, Petar via 
 Digitalmars-d wrote:
 [...]
[...]
 [...]
[...]
 [...]
I don't see why this is considered head const, nor why this should be considered a bug. In the proposed solution `y` is not a value type but a delegate that wraps the reference to *x. Since x itself is non-const, this is perfectly valid, and the `const` in `const y` refers to the immutability of the delegate reference, not to the immutability of the wrapped reference. T
It's a const variable that contains a mutable reference. It's head-const.
It's so head-const that I made the beginnings of a friendly 100% safe head-const with it. Please don't use it. https://run.dlang.io/is/sZLSvZ struct HeadConstable(T) { import std.traits : ReturnType; ReturnType!makeAccessor accessor; private static makeAccessor(return ref T v) { ref T impl() { return v; } return &impl; } this(return ref T v) const { accessor = makeAccessor(v); } ref T get() const pure safe { return accessor(); } void opAssign(Q)(Q rhs) const if (__traits(compiles, { get() = rhs; })) { get() = rhs; } } auto headConst(T)(ref T v) { return const HeadConstable!T(v); } void main() pure safe { int x; auto y = headConst(x); static assert(is(typeof(y) == const)); version (TestConstness) { int z; y = headConst(z); // Error: cannot modify const expression y } x = 1; y = 2; assert(x == 2); } also, you can replace const with immutable or shared and have endless fun breaking language guarantees and library expectations.
Dec 29 2020
parent reply Max Haughton <maxhaton gmail.com> writes:
On Tuesday, 29 December 2020 at 20:47:10 UTC, John Colvin wrote:
 On Tuesday, 29 December 2020 at 19:42:28 UTC, John Colvin wrote:
 [...]
It's so head-const that I made the beginnings of a friendly 100% safe head-const with it. Please don't use it. [...]
Isn't there already Final in experimental typecons? I think with any method like this you are slightly at the mercy of the ABI for structs but I haven't been bitten yet.
Dec 30 2020
parent John Colvin <john.loughran.colvin gmail.com> writes:
On Wednesday, 30 December 2020 at 20:33:34 UTC, Max Haughton 
wrote:
 On Tuesday, 29 December 2020 at 20:47:10 UTC, John Colvin wrote:
 On Tuesday, 29 December 2020 at 19:42:28 UTC, John Colvin 
 wrote:
 [...]
It's so head-const that I made the beginnings of a friendly 100% safe head-const with it. Please don't use it. [...]
Isn't there already Final in experimental typecons? I think with any method like this you are slightly at the mercy of the ABI for structs but I haven't been bitten yet.
It's perfectly ok to create a type that is un-reassignable like std.experimental.typecons.Final. What makes my code evil is it actually carrying the type-qualifier.
Dec 30 2020
prev sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Tuesday, 29 December 2020 at 17:56:51 UTC, H. S. Teoh wrote:
 [..]
 I don't see why this is considered head const, nor why this 
 should be considered a bug. In the proposed solution `y` is not 
 a value type but a delegate that wraps the reference to *x.  
 Since x itself is non-const, this is perfectly valid, and the 
 `const` in `const y` refers to the immutability of the delegate 
 reference, not to the immutability of the wrapped reference.


 T
Do you agree with the following statements? --- The const / immutable type qualifiers being transitive implies that it's not possible to mutate any data reachable through an object of type qualified with const / immutable. Transitive immutable implies that anything reachable through an immutable object won't change after being constructed. In contrast, const only prevents mutation through const objects - the same data aliased otherwise could be mutated. (This applies for any kind of object, no matter whether if one would classify it as of a value or reference type.) --- The fact that the proposed solution compiles is a contradiction of the const transitivity rule that I stated above. Before checking the proposed solution, did you consider the example solvable?
 I don't see why this is considered head const, nor why this 
 should be considered a bug.
My definition of a head const variable is a variable which it can't be re-assigned after construction, but one that allows mutation of data reachable through it, in contrast transitive const qualified variables. The behavior of `y` in the proposed solution matches my definition of head const. The root of the issue is that dmd still treats the delegate context pointer opaquely as void*. Instead, the full type returned by the function `f` should be: struct Delegate { alias F = void function(Context* ptr) pure safe; Context* ptr; F funcptr; static struct Context { int** p; // D delegates capture variables by reference } } If that was the case, I think it's easily apparent why `const` is not working as intended, but as "head const" instead in this specific case.
Dec 29 2020
prev sibling parent tsbockman <thomas.bockman gmail.com> writes:
On Tuesday, 29 December 2020 at 17:13:48 UTC, Petar Kirov 
[ZombineDev] wrote:
 On Tuesday, 29 December 2020 at 16:15:56 UTC, ag0aep6g wrote:
 Solution: https://run.dlang.io/is/dsaGFS
I'd say head-const is a very useful tool, but it's pretty obvious to me that this shouldn't be a supported way to implement it.
I agree. I wish D supported head-const, but this is just a bug.
Dec 29 2020
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 29.12.20 17:15, ag0aep6g wrote:
 Exercise: Consider the following `main` function. Define `f` and `g` so 
 that the code is valid, compiles, and runs successfully. The asserts 
 must be executed and they must pass.
 
 ----
 void main() pure  safe
 {
      int* x = new int;
      const y = f(x);
      *x = 1;
      g(y);
      assert(*x == 2);
 }
 ----
 
 Hints:
 * You can't store `x` in a global because the code is `pure`.
 * You can't cast `const` away in `g` because that would be invalid.
 * You're supposed to find a type for which `const` means head const.
 
 Solution: https://run.dlang.io/is/dsaGFS
 
 The validity of the given solution might be arguable, and I'd be in 
 favour of outlawing it. It's surprising that there's a type for which 
 `const` means head const when it means transitive const for everything 
 else. It's so surprising that even DMD trips over it 
 (<https://issues.dlang.org/show_bug.cgi?id=21511>).
It's a bug. Here's a number of such "solutions" with links to the corresponding bug reports, the last one is the same as your suggested solution. They are all type system holes on the same level: they break aliasing invariants that the type system is supposed to preserve in `pure safe` code. --- // https://issues.dlang.org/show_bug.cgi?id=17744 int* f(int* x)pure safe{ return x; } void g(const(int)* y)pure safe{ inout(int)* delegate(inout(int)*)pure safe delegate()pure safe foo(inout(int)* y)pure safe{ inout(int)* bar(inout(int)* p)pure safe{ return y; } return ()=>&bar; } int* x; *foo(y)()(x)=2; } --- --- // https://issues.dlang.org/show_bug.cgi?id=18566 auto f(int* x)pure safe{ struct S{ void foo()const pure{ *x=2; } } return S(); } void g(S)(S s){ s.foo(); } --- --- // https://issues.dlang.org/show_bug.cgi?id=21517 // (this one is new, I found this while solving the challenge) auto f(int* x)pure safe{ return x; } void g(const(int)* y)pure safe{ int* foo(inout(int)* x)pure safe{ int* bar(inout(int)* delegate(inout int)pure safe z){ return z(2); } return bar((inout t)=>x); } *foo(y)=2; } --- --- // https://issues.dlang.org/show_bug.cgi?id=2947 class C{ int*[] a=[null]; } auto f(int* x)pure safe{ (new C).a[0]=x; return x; } void g(const(int)* y)pure safe{ *(new C).a[0]=2; } --- --- // https://issues.dlang.org/show_bug.cgi?id=9149#c11 auto f(int* x)pure safe{ return (){ *x=2; }; } auto g(void delegate()pure safe dg)pure safe{ dg(); } ---
Dec 30 2020
next sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Wednesday, 30 December 2020 at 22:49:09 UTC, Timon Gehr wrote:
 On 29.12.20 17:15, ag0aep6g wrote:
 [...]
It's a bug. Here's a number of such "solutions" with links to the corresponding bug reports, the last one is the same as your suggested solution. They are all type system holes on the same level: they break aliasing invariants that the type system is supposed to preserve in `pure safe` code. [...]
Is anyone working on those issues?
Dec 31 2020
prev sibling parent ag0aep6g <anonymous example.com> writes:
On 30.12.20 23:49, Timon Gehr wrote:
 ---
 // https://issues.dlang.org/show_bug.cgi?id=2947
 class C{ int*[] a=[null]; }
 auto f(int* x)pure  safe{
      (new C).a[0]=x;
      return x;
 }
 void g(const(int)* y)pure  safe{
      *(new C).a[0]=2;
 }
 ---
This is my favorite of the bunch. Ancient issue, many duplicates, has been argued to be working as intended, no fix on the horizon. It's lovely. D does not have head const ... except when it does. In D, `pure` functions cannot access global mutable state ... except when they can.
Dec 31 2020