November 15, 2015
On Sunday, 15 November 2015 at 15:14:38 UTC, Jonathan M Davis wrote:
> mutating is undefined behavior, arguing that if it weren't, it would be like C++'s const and that C++'s const is pretty much useless, because it doesn't provide actual guarantees.

Hm, I think C++ const requires that the object isn't modified, but you can cast away const in order to call functions that are erroneously missing a const specifier.

C++'s solution is to have a "mutable" specifier for fields like that can be mutated without affecting the type's state externally (mutexes etc).

From the C++ faq:

«NOTE: there is an extremely unlikely error that can occur with const_cast. It only happens when three very rare things are combined at the same time: a data member that ought to be mutable (such as is discussed above), a compiler that doesn’t support the mutable keyword and/or a programmer who doesn’t use it, and an object that was originally defined to be const (as opposed to a normal, non-const object that is pointed to by a pointer-to-const). Although this combination is so rare that it may never happen to you, if it ever did happen, the code may not work (the Standard says the behavior is undefined).»

So const_casting away const followed by mutation is undefined behaviour.

November 15, 2015
On Sunday, 15 November 2015 at 18:26:56 UTC, Ola Fosheim Grøstad wrote:
> On Sunday, 15 November 2015 at 15:14:38 UTC, Jonathan M Davis wrote:
>> mutating is undefined behavior, arguing that if it weren't, it would be like C++'s const and that C++'s const is pretty much useless, because it doesn't provide actual guarantees.
>
> Hm, I think C++ const requires that the object isn't modified, but you can cast away const in order to call functions that are erroneously missing a const specifier.
>
> C++'s solution is to have a "mutable" specifier for fields like that can be mutated without affecting the type's state externally (mutexes etc).
>
> From the C++ faq:
>
> «NOTE: there is an extremely unlikely error that can occur with const_cast. It only happens when three very rare things are combined at the same time: a data member that ought to be mutable (such as is discussed above), a compiler that doesn’t support the mutable keyword and/or a programmer who doesn’t use it, and an object that was originally defined to be const (as opposed to a normal, non-const object that is pointed to by a pointer-to-const). Although this combination is so rare that it may never happen to you, if it ever did happen, the code may not work (the Standard says the behavior is undefined).»
>
> So const_casting away const followed by mutation is undefined behaviour.

As I understand it, the only time that casting away const and mutating is undefined behavior in C++ is when the object was specifically constructed as const such that no mutable reference to it even can exist without a cast - which would be the bit from that quote where it talks about "an object that was originally defined to be const". And in general, _very_ few objects are likely to be constructed as const. So, in general, in C++, you can cast away const and mutate as much as you'd like. It's just that that's generally considered bad practice, and folks usually behave themselves and try to use const as a "logical" const such that no member variables which would normally be considered part of the state of the object have a different value after a const function call than what they had before the function call. So, for the most part, const works by convention.

- Jonathan M Davis
November 15, 2015
On Sunday, 15 November 2015 at 17:42:24 UTC, Jonathan M Davis wrote:
> On Sunday, 15 November 2015 at 15:20:24 UTC, Dicebot wrote:
>> On Sunday, 15 November 2015 at 15:09:25 UTC, Jonathan M Davis wrote:
>>> After the call to bar, the compiler can guarantee that the value of foo has not changed, because it's not possible for bar to access foo except via a const reference, and it can clearly see that, because it can see that it's not possible for bar to access foo except via the reference that's passed to it.
>>
>> Note that it can only do so if escape analysis is able to prove no other reference escapes to the same data. Otherwise it can be mutated even during that function call in other thread.
>
> Except that shared isn't involved here. The objects are thread-local, so other threads don't matter. The combination of TLS and pure does make it so that the compiler could theoretically do optimizations based on const. immutable is not actually required, though it obviously provides far better guarantees.

Ok, agreed about threading part (though does currently spec require all non-TLS data to be qualified as shared?). But same applies to fiber context switches too (within same thread). Basically anything that can cause extra code to be executed between target function start and finish requires escape analysis to justify such optimization.

> But my point was that there _are_ cases when the compiler can do optimizations based on const without immutable being involved, even if they're rare.

If they are that rare, probably they are not worth the trouble :) Or should be mentioned in spec explicitly (i.e. pure case)

> The primary benefit of making it undefined behavior to cast away const and mutate is that then you actually have the guarantee that an object will not be mutated via a const reference. You get actual, physical const. As long as there's a backdoor out of const, const provides no real guarantees.

No, not at all. It vast majority of cases (and pretty much all library code) you must assume that const can be immutable and thus is must be treated as physical const (because immutability is physical). In rare cases where it is possible to prove/ensure mutability of the data it makes no difference because you can have the backdoor via cast anyway, it will only affect how reliable result will be across different compilers.

> Now, given how incredibly limiting physical const is, maybe we should be more lax with D's const, but if we are, we lose out on the compiler guarantees that we currently get and basically end up with a transitive version of C++'s const (except that we have the added risk of mutating an immutable object if we're not careful).

No, I think concept of physical const/immutable is fine as it is. Limiting but valuable for multi-threading, requiring to build whole design of your application around it. It just happens that language sometimes forces handling const even in cases you don't need to care about immutability (earlier invariant example) which makes casting it away (at least temporarily) only practical option - and it would be very unpleasant to have that UB.
November 15, 2015
On Sunday, 15 November 2015 at 18:09:15 UTC, Andrei Alexandrescu wrote:
> On 11/15/2015 01:00 PM, Jonathan M Davis wrote:
>> Basically, we have to decide between having physical const with the
>> guarantees that it provides
>
> We have that - it's immutable. -- Andrei

Yes and no. As it stands, I can know that

const foo = getFoo();
foo.bar();

won't mutate foo unless I have another, mutable reference to foo somewhere that bar somehow accessed. And if bar is pure, and I didn't pass another reference to foo in via one of the function arguments, then I know that foo won't have been mutated by bar. The same goes if I foo was created inside the current function and had no chance to be assigned somewhere where bar could get at even if it weren't pure. It's only when an object gets passed around a bit that there's really even the possibility that it will be mutated by a function that treats it as const, and the combination of TLS and pure reduces that risk considerably. So, in general, you really can rely on a const object not being mutated when you call a const member function or even pass it to a function which takes it as const.

However, if we make it so that casting away const and mutating is defined behavior or allow any other kind of backdoor to const, then all bets are off. Without looking at the implementation of every function that an object gets passed to, you can't know whether it's actually being mutated or not even though it's const. So, where's a definite loss there. Putting backdoors into const costs us the guarantees that we get with const now. We go from treating const as physically const to treating it as logically const (and logically const can't actually be guaranteed by the compiler).

Now, that puts us in a place that's similar to C++, and that works, but it definitely does not provide the same guarantees that we have now and is worse in the cases where backdoors aren't needed.

- Jonathan M Davis
November 15, 2015
On 11/13/2015 06:10 PM, Andrei Alexandrescu wrote:
[snip]

Thanks all for the feedback. I've uploaded an updated version to http://dpaste.dzfl.pl/52a3013efe34. It doesn't use "inout", but doesn't duplicate code either; instead, it relies on private internal functions to do the deed.

While revising feedback I realized I'd missed Steve's version that works with inout: http://dpaste.dzfl.pl/3fbc786a50c1. It works and the inout-related parts don't look difficult; the only thing is making sure the right incantations are in place, and that's the difficult part.

So one important question is what style we use for Phobos and endorse as idiomatic. My sense after working on this for a while is - forget inout. Qualifiers are rather complex, and of them inout is the most. So I'd rather marginalize it. Please chime in with any thoughts.


Andrei

November 15, 2015
On Sunday, 15 November 2015 at 18:36:20 UTC, Jonathan M Davis wrote:
> was originally defined to be const". And in general, _very_ few objects are likely to be constructed as const.

It is common in constructors.

> So, in general, in C++, you can cast away const and mutate as much as you'd like.

Well, since it is "shared const", the compiler cannot assume that another thread does not write to the object without whole program analysis. Which leaves the compiler writer with less incentive to do things differently because the object is const locally. Not sure if the FAQ reflects the standard or what current compilers do.

I would expect it to change in the future though, since whole program analysis is becoming more and more realistic. The FAQ also end with this statement:

«Please don’t write saying version X of compiler Y on machine Z lets you change a non-mutable member of a const object. I don’t care — it is illegal according to the language and your code will probably fail on a different compiler or even a different version (an upgrade) of the same compiler. Just say no. Use mutable instead. Write code that is guaranteed to work, not code that doesn’t seem to break.»


November 15, 2015
On 11/15/2015 06:38 PM, Andrei Alexandrescu wrote:
> On 11/15/2015 10:09 AM, Jonathan M Davis wrote:
>> const in D guarantees that the object will not be mutated via that
>> reference regardless of what that reference refers to.
>
> We need to change that if we want things like composable containers that
> work with const. And I think it's a good thing to want. -- Andrei

I don't think so.
November 15, 2015
On Sunday, 15 November 2015 at 19:44:27 UTC, Timon Gehr wrote:
> On 11/15/2015 06:38 PM, Andrei Alexandrescu wrote:
>> On 11/15/2015 10:09 AM, Jonathan M Davis wrote:
>>> const in D guarantees that the object will not be mutated via that
>>> reference regardless of what that reference refers to.
>>
>> We need to change that if we want things like composable containers that
>> work with const. And I think it's a good thing to want. -- Andrei
>
> I don't think so.

I support this statement.
November 15, 2015
On 11/15/2015 01:57 PM, Andrei Alexandrescu wrote:
> On 11/14/2015 06:48 PM, Timon Gehr wrote:
>> On 11/15/2015 12:20 AM, Andrei Alexandrescu wrote:
>>> On 11/14/15 5:49 PM, Timon Gehr wrote:
>>>> It's supposed to guarantee that the given reference is not used to
>>>> transitively mutate the object. The casts violate this.
>>>
>>> I think that semantics needs to change. Specifically, either we add a
>>> @mutable attribute (which means const doesn't apply to fields marked as
>>> such and immutable objects cannot be created); or we could just decree
>>> that if a const object originates in a mutable object, casts should be
>>> well-defined. -- Andrei
>>>
>>
>> There's also this closely related situation:
>> https://issues.dlang.org/show_bug.cgi?id=9149
>>
>> (I.e. delegates with mutable context pointer can be implicitly converted
>> to delegates with const context pointer, but when type checking the
>> delegate, a mutable context pointer is assumed.)
>
> That's a hole straight into the middle of things. We need to fix that.
> -- Andrei

Given your recent efforts to change the meaning of const, I think it is no longer as clear-cut as it was when I reported the bug. Those delegates can only mutate const objects that were constructed as mutable.
November 15, 2015
On 11/15/2015 02:47 PM, Dicebot wrote:
> On Sunday, 15 November 2015 at 19:44:27 UTC, Timon Gehr wrote:
>> On 11/15/2015 06:38 PM, Andrei Alexandrescu wrote:
>>> On 11/15/2015 10:09 AM, Jonathan M Davis wrote:
>>>> const in D guarantees that the object will not be mutated via that
>>>> reference regardless of what that reference refers to.
>>>
>>> We need to change that if we want things like composable containers that
>>> work with const. And I think it's a good thing to want. -- Andrei
>>
>> I don't think so.
>
> I support this statement.

Just to clarify - is that referring to the part "We need to change that if we want things like composable containers that work with const." or to the "I think it's a good thing to want" part? -- Andrei