casting away const and then mutating

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 23 20:58:04 PDT 2015


On Friday, 24 July 2015 at 03:02:30 UTC, Steven Schveighoffer 
wrote:
> On 7/23/15 7:57 PM, Jonathan M Davis wrote:
>> If
>> you cast away const, it's up to you to guarantee that the data 
>> being
>> referenced is not mutated, and if it is mutated, it's 
>> undefined behavior.
>
> Still need a reference to the spec that says that. Note that 
> the spec specifically says it's undefined to cast away and 
> modify immutable, and is careful not to include const/mutable 
> in that discussion.

It's come up a number of times in discussions on logical const, 
including from Walter. For it to be otherwise would mean that 
const is not actually physical const. I'm quite certain that the 
spec is wrong in this case.

> OK, but the point is you have run an algorithm that gets a 
> *piece* of x (pretend x is not just a simple int), which you 
> know to be mutable because x is mutable. But you don't want the 
> algorithm to mutate x.
>
> Basically, if we say this is undefined behavior, then inout is 
> undefined behavior.

inout is done by the compiler. It knows that it's safe to cast 
the return type to mutable (or immutable), because it knows that 
the return value was either the argument that it passed in or 
something constructed within the function and thus safe to cast. 
The compiler knows what's going on, so it can ensure that it 
doesn't violate the type system and is well-defined.

> An example of what the compiler can "start doing more than it 
> does now" would be helpful. I can't see how it can do anything 
> based on this.

Well, it could remove dead code. For instance, if you had

const(Foo) bar(T t)
{
     const myFoo = getFoo(t);
     auto value1 = pureFunc(myFoo);
     auto value2 = pureFunc2(myFoo);
     auto value3 = pureFunc3(value1, value2);
     return myFoo;
}

All of the lines with pureFunc* could be removed outright, 
because they're all pure function calls, and they can't possibly 
have mutated myFoo. I wouldn't expect a lot of dead code like 
that, and maybe something like that would never be implemented in 
the compiler, but it could be as long as the compiler can 
actually rely on const not being mutated.

But part of the problem with "start doing more than it does now" 
is that that could easily depend on ideas that folks come up with 
later. At some point in the future, someone might figure out how 
const interacts with some other set of attributes and be able to 
optimize based on that. So, if you're casting away const and 
mutating, relying on no one coming up with new optimizations, 
then you could be in trouble later when they do. And maybe they 
won't, but we don't know.

> Specifically, I would say you can cast away const on a 
> reference that you have created within your own function on a 
> mutable piece of data (in other words, you control the mutable 
> data), then you can mutate via the cast reference. Otherwise, 
> the inout feature is invalid, and we should remove it from the 
> language, because that's EXACTLY what it does.
>
> A simple example:
>
> struct Node
> {
>    int val;
>    Node *next;
> }
> const(Node) *find(const(Node)* n, int val)
> {
>    while(n && n.val != val) n = n.next;
>    return n;
> }
> Node *find(Node *n, int val)
> {
>     const cn = n;
>     return cast(Node *)find(cn, val);
> }
>
> Note that the mutable version of find doesn't mutate the node 
> (checked by the compiler BTW), and it's signature doesn't allow 
> any const optimizations -- it gets in a mutable and returns a 
> mutable. This can be rewritten like this:
>
> inout(Node) *find(inout(Node)* n, int val)
> {
>     while(n && n.val != val) n = n.next;
>     return n;
> }
>
> But in the case of the PR in question, we can't do this, 
> because we can't inout our range and have it continue to be a 
> range. So we are mimicking the behavior of inout, and the 
> compiler should be fine with this.

It sounds like what you really want is a tail-inout range or 
somesuch, though since we can't even sort out tail-const ranges 
properly at this point, I expect that tail-inout ranges are a bit 
of a pipe dream.

In any case, regardless of whether what you're proposing defined 
behavior or not, it'll work, because there's no way that the 
compiler could do any optimizations based on const after the cast 
is done, because it's only const within the function. It's when 
you cast away const on something that you were given as const 
that you have a real problem. e.g.

const(Foo) bar(const(Foo) foo)
{
     auto f = cast(Foo)foo;
     f.mutateMe();
     return foo;
}

I agree that what you're asking for makes sense. If you pass in a 
mutable object to a function, and you know that the const object 
you get out is either the same object or a new one that is not 
immutable, and no references to that object escaped the function, 
then casting the return type to mutable should work, and I'm not 
against that being well-defined, but as I understand it, it 
technically isn't, because it involves casting away const and 
then mutating the result. And if it is well-defined, then we'd 
need clear way to describe the circumstances to separate it from 
casting away const in general (even when the data itself is 
actually mutable).

I'm am quite sure that it is undefined behavior to cast away 
const and mutate, even if the spec doesn't say that, because it's 
come up time and time again in discussions on logical const. And 
in the general case, even without immutable, if it's 
well-defined, then compiler can't assume that a const variable 
isn't going to be mutated, even when it knows that no mutable 
references could have mutated it, and it means that const really 
isn't physical const anymore, because you would be free to cast 
away const and mutate so long as the data wasn't immutable, thus 
making const pretty meaningless as far as compiler guarantees go 
(which is Walter's big beef with C++'s const). So, I don't see 
how we could allow casting away const and mutating to be 
well-defined aside from very specific cases like this one. But 
since the spec doesn't actually seem to say anything one way or 
the other (aside from with regards to immutable), I think that 
Walter is going to have weigh in. Clearly, the the best that I 
could do convince you otherwise would be to dig through all of 
the old threads on const to find quotes from Walter.

- Jonathan M Davis


More information about the Digitalmars-d mailing list