Jump to page: 1 2
Thread overview
D does have head const (but maybe it shouldn't)
Dec 29, 2020
ag0aep6g
Dec 29, 2020
H. S. Teoh
Dec 29, 2020
ag0aep6g
Dec 29, 2020
Paul Backus
Dec 29, 2020
John Colvin
Dec 29, 2020
John Colvin
Dec 30, 2020
Max Haughton
Dec 30, 2020
John Colvin
Dec 29, 2020
tsbockman
Dec 30, 2020
Timon Gehr
Dec 31, 2020
Imperatorn
Dec 31, 2020
ag0aep6g
December 29, 2020
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
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
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
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
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
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
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
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
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
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.
« First   ‹ Prev
1 2