digitalmars.D - D does have head const (but maybe it shouldn't)
- ag0aep6g (23/23) Dec 29 2020 Exercise: Consider the following `main` function. Define `f` and `g` so
- Petar Kirov [ZombineDev] (6/32) Dec 29 2020 Wow. The solution surprised me, even though in retrospect it's
- H. S. Teoh (12/34) Dec 29 2020 [...]
- ag0aep6g (30/51) Dec 29 2020 How are you not just describing head const? A thing that is
- Paul Backus (21/27) Dec 29 2020 It's a bug because `const` should apply transitively to both the
- John Colvin (3/17) Dec 29 2020 It's a const variable that contains a mutable reference. It's
- John Colvin (43/64) Dec 29 2020 It's so head-const that I made the beginnings of a friendly 100%
- Max Haughton (4/9) Dec 30 2020 Isn't there already Final in experimental typecons?
- John Colvin (5/17) Dec 30 2020 It's perfectly ok to create a type that is un-reassignable like
- Petar Kirov [ZombineDev] (39/49) Dec 29 2020 Do you agree with the following statements?
- tsbockman (3/8) Dec 29 2020 I agree. I wish D supported head-const, but this is just a bug.
- Timon Gehr (67/94) Dec 30 2020 It's a bug. Here's a number of such "solutions" with links to the
- Imperatorn (2/10) Dec 31 2020 Is anyone working on those issues?
- ag0aep6g (6/17) Dec 31 2020 This is my favorite of the bunch. Ancient issue, many duplicates, has
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
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
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); } ----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 KnowledgeThe 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
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: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.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.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
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
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:It's a const variable that contains a mutable reference. It's head-const.[...][...][...][...][...]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
Dec 29 2020
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: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.On Tue, Dec 29, 2020 at 05:13:48PM +0000, Petar via Digitalmars-d wrote:It's a const variable that contains a mutable reference. It's head-const.[...][...][...][...][...]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
Dec 29 2020
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: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 so head-const that I made the beginnings of a friendly 100% safe head-const with it. Please don't use it. [...]
Dec 30 2020
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: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.On Tuesday, 29 December 2020 at 19:42:28 UTC, John Colvin wrote: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 so head-const that I made the beginnings of a friendly 100% safe head-const with it. Please don't use it. [...]
Dec 30 2020
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. TDo 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
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:I agree. I wish D supported head-const, but this is just a bug.Solution: https://run.dlang.io/is/dsaGFSI'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
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
On Wednesday, 30 December 2020 at 22:49:09 UTC, Timon Gehr wrote:On 29.12.20 17:15, ag0aep6g wrote:Is anyone working on those issues?[...]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. [...]
Dec 31 2020
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