Thread overview | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
December 29, 2020 D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
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>). |
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to ag0aep6g | 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.
|
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Petar Kirov [ZombineDev] | 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 |
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 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. |
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 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.
|
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 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.
|
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Petar Kirov [ZombineDev] | 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.
|
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 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. |
December 29, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to John Colvin | 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. |
December 30, 2020 Re: D does have head const (but maybe it shouldn't) | ||||
---|---|---|---|---|
| ||||
Posted in reply to John Colvin | 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.
|
Copyright © 1999-2021 by the D Language Foundation