casting away const and then mutating

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Thu Jul 23 20:02:30 PDT 2015


On 7/23/15 7:57 PM, Jonathan M Davis wrote:
> On Thursday, 23 July 2015 at 18:43:03 UTC, anonymous wrote:
>> On a GitHub pull request, Steven Schveighoffer (schveiguy), Jonathan M
>> Davis (jmdavis), and I (aG0aep6G) have been discussing if or when it's
>> ok to cast away const and then mutate the data:
>>
>> https://github.com/D-Programming-Language/phobos/pull/3501#issuecomment-124169544
>>
>>
>> I've been under the impression that it's never allowed, i.e. it's
>> always undefined behaviour. I think Jonathan is of the same opinion.
>
> It's come up time and time again with discussions for logical const.

This is not logical const. We are starting with mutable data, moving it 
through a function that we *don't* want to mutate the data, and then 
using it as mutable again in the function where you (and the compiler) 
know its mutable. But you aren't even mutating, just getting it back to 
the original constancy (though mutation should be OK, you still have a 
mutable reference).

> 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.

> Now, if you know that the data being referenced is actually mutable and
> not immutable, and you know that the compiler isn't going to make any
> assumptions based on const which are then wrong if you mutate the
> variable after casting away const, then you can get away with it.
> But
> it's still undefined behavior, and if the compiler later starts doing
> more than it does now based on the knowledge that you can't mutate via a
> const reference, then your code might stop working correctly. So, if
> you're _really_ careful, you can get away with casting away const and
> mutating a variable, but you are depending on undefined behavior.

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.

>> ----
>> int x;
>> const int *y = &x;
>> *(cast(int *)y) = 5;
>> ----
>
> Even if this were defined behavior, what would be the point? You have
> access to x. You could just mutate it directly.

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.

> I don't see how it would
> make any sense to be attempting to mutate something via a const
> reference when you have access to it via a mutable reference. It's when
> you don't have access to it via a mutable reference that it becomes an
> issue - which means that you've crossed a function boundary.

Exactly. You still have mutable access to it, and you know the const 
access is to the same object, just transformed via an algorithm.

> The only way to make casting away const and mutating defined behavior in
> general

This is NOT what is being asked. Not the general case of making it 
defined to cast away const on any item (which could turn out to be 
immutable).

I think it's pointless to argue over this. The behavior can't be defined 
any other way than what I'm asking. The question is if we want the 
*official* position to nonsensically call it undefined behavior.

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.

-Steve


More information about the Digitalmars-d mailing list