February 16, 2014
On Fri, 14 Feb 2014 23:03:50 -0500, Adam D. Ruppe <destructionator@gmail.com> wrote:

> D doesn't have logical const, but sometimes it is useful, especially with lazy initialization of a field, and we can kinda fake it with casts or with global variables. Modifying an immutable object is undefined behavior, so how can we avoid that, and if not, try to minimize the problems in practice?
>
> Using global variables to store local state is kinda silly, it seems to me that doing a AA lookup kinda defeats the point of caching in the first place, so I want to focus on the cast method.
>
> So:
>
> * should we always wrap the write in a synchronized block to minimize the chances that we'll have thread problems with implicitly shared immutable things passed as const? What's the best way to do this btw?

As a start, I would take a look at the code that generates the mutex when you synchronize an object. It is the only case where logical const is allowed in D.

> * should objects with logical const methods deliberately not provide immutable constructors to prevent them from being immutable? Would this actually work?

This is a good idea. Can you @disable the immutable constructor?

> * Anything else that is likely to come up?

Where you must be very very cautious is immutable pure functions. The compiler may make assumptions that are not correct in terms of shortcuts.

My advice is to never change "logical const" data inside a const pure function.

Other than that, depending on the use case, use C threading rules when making changes. Basically, if you know the object will never be referenced in multiple threads (very application specific), then you are OK to just change it. Otherwise, I'd put a mutex on it, or (ironically) use the object's monitor to protect it.

-Steve
February 16, 2014
On Sat, 15 Feb 2014 21:36:43 -0500, Meta <jared771@gmail.com> wrote:

> On Sunday, 16 February 2014 at 02:34:36 UTC, Steven Schveighoffer wrote:
>> On Sat, 15 Feb 2014 15:02:44 -0500, Meta <jared771@gmail.com> wrote:
>>
>>> On Saturday, 15 February 2014 at 19:59:07 UTC, Meta wrote:
>>>> inout is *sort of* logical const, if the underlying type is immutable.
>>>
>>> I mean mutable, of course.
>>
>> No, it's not. It's not allowed to mutate with inout.
>>
>> -Steve
>
> Right, but inout can accept any of mutable, const, and mutable. The compiler will statically disallow you from mutating an inout variable, but if you know the underlying data is mutable, it's safe to cast inout away and mutate. If the data is actually mutable, then inout is effectively logical const. I guess the same is true of regular const.

inout does not know when the incoming data is mutable.

Effectively, inout is just like const, but has implications on the return value.

-Steve
February 16, 2014
On Sunday, 16 February 2014 at 02:41:36 UTC, Steven Schveighoffer wrote:
> inout does not know when the incoming data is mutable.
>
> Effectively, inout is just like const, but has implications on the return value.
>
> -Steve

Yeah, but say you magically knew the data was mutable.

void logicalConstBump(T)(ref inout(T) t, string originalMutability)
{
    if (originalMutability == "mutable")
    {
        cast(T)t += 1;
    }
    else
    {
        assert(0);
    }
}

Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?
February 16, 2014
On Sat, 15 Feb 2014 22:04:25 -0500, Meta <jared771@gmail.com> wrote:

> Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?

No, because only one version of the function is generated. That is part of the benefit of inout over a template.

If you DO want to know whether it's mutable, use a template.

-Steve
February 16, 2014
On Sunday, 16 February 2014 at 03:16:16 UTC, Steven Schveighoffer wrote:
> On Sat, 15 Feb 2014 22:04:25 -0500, Meta <jared771@gmail.com> wrote:
>
>> Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?
>
> No, because only one version of the function is generated. That is part of the benefit of inout over a template.
>
> If you DO want to know whether it's mutable, use a template.
>
> -Steve

If you pass a mutable value to a function taking an inout parameter, what's the difference between that function and an identical function that takes a regular mutable parameter instead of an inout parameter?
February 16, 2014
On Saturday, 15 February 2014 at 04:03:51 UTC, Adam D. Ruppe wrote:

What about a library solution for something like C++-esque mutable?

struct Mutable(T)
{
    private T val;

    this(T v) { val = v; }

    @property ref T get() const { return *cast(T*)&val; }
    alias get this;
}

import std.stdio;

struct Foo
{
    private Mutable!int cache;

    void bar() const
    {
        writeln("Updating cache");
        ++cache;
    }

    @property int cached() const { return cache; }
}

void main()
{
    Foo foo;
    writeln(foo.cached);
    foo.bar();
    writeln(foo.cached);
}
February 16, 2014
On Sat, 15 Feb 2014 22:17:48 -0500, Meta <jared771@gmail.com> wrote:

> On Sunday, 16 February 2014 at 03:16:16 UTC, Steven Schveighoffer wrote:
>> On Sat, 15 Feb 2014 22:04:25 -0500, Meta <jared771@gmail.com> wrote:
>>
>>> Obviously this is extremely contrived, but inout is effectively logical const here. Is there no way to know what the "original" mutability of t is at compile-time?
>>
>> No, because only one version of the function is generated. That is part of the benefit of inout over a template.
>>
>> If you DO want to know whether it's mutable, use a template.
>>
>> -Steve
>
> If you pass a mutable value to a function taking an inout parameter, what's the difference between that function and an identical function that takes a regular mutable parameter instead of an inout parameter?

I'm not sure what you are asking, because the answer I want to give is so trivial :)

Identical functions produce identical code. Const, inout, immutable, whatever, these do not change code generation, just what is allowed to those variables.

The rational behind inout came from the strstr problem:

const char *strstr(const char *haystack, const char *needle);

1. You want the signature of strstr to guarantee it will not change it's parameters.
2. You do not want strstr to change the CALLER's permissions on the parameters, which it effectively does by applying const to the return value.

I want the result of strstr to have the same restrictions as the parameter I pass in (mutable, const or immutable). The only way to do this was with a template. But a template would generate 3 separate versions of strstr with the exact same code in it. Maybe an enterprising compiler can elide the 2 superfluous versions, but there's another problem -- the mutable version would be able to change the parameter! We gave up on item 1.

inout does this correctly, and it makes a world of difference in accessors of structs and classes.

-Steve
February 16, 2014
On Sunday, 16 February 2014 at 03:26:42 UTC, Steven Schveighoffer wrote:
> I'm not sure what you are asking, because the answer I want to give is so trivial :)

Then I don't know what we're arguing about. I'm saying that if you pass mutable data to an inout function, inout effectively acts as logical const within that function, as the compiler doesn't allow you to modify the inout function argument but it is safe to cast away inout. Do you agree or disagree with this?
February 16, 2014
On Sat, 15 Feb 2014 23:35:52 -0500, Meta <jared771@gmail.com> wrote:

> On Sunday, 16 February 2014 at 03:26:42 UTC, Steven Schveighoffer wrote:
>> I'm not sure what you are asking, because the answer I want to give is so trivial :)
>
> Then I don't know what we're arguing about. I'm saying that if you pass mutable data to an inout function, inout effectively acts as logical const within that function, as the compiler doesn't allow you to modify the inout function argument but it is safe to cast away inout. Do you agree or disagree with this?

It is safe if you can guarantee the input is ultimately mutable. But there is no way to enforce such a guarantee. It is on the caller to make sure that is true.

I don't see why inout should be identified as any different from const in this respect. You could just as easily say the same thing about const.

-Steve
February 16, 2014
On Sunday, 16 February 2014 at 04:46:34 UTC, Steven Schveighoffer wrote:
> It is safe if you can guarantee the input is ultimately mutable. But there is no way to enforce such a guarantee. It is on the caller to make sure that is true.
>
> I don't see why inout should be identified as any different from const in this respect. You could just as easily say the same thing about const.
>
> -Steve

Which is what I said in an earlier post. Wouldn't you be able to do this with a template somehow?

template LogicalConst(T)
if (isMutable!T)
{
    alias LogicalConst = inout(T);
}

void bumpWithConstArg(T)(ref LogicalConst!T t)
{
    //Compiler error if you try to modify t
    //t += 1;

    //Fine, cast it to mutable then modify
    //Nothing unsafe here because we verified t is mutable
    cast(T)t += 1;
}

void main()
{
    immutable n = 0;
    //No IFTI match because n is immutable
    bumpWithConstArg(n);

    auto m = 0;
    //Okay, m is mutable
    bumpWithConstArg(m);
}

This doesn't actually work, but this is just off the top of my head.