On Saturday, 14 November 2015 at 23:20:08 UTC, 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
Either way, we'd be throwing away the idea that const is physical
const and provides actual guarantees against mutation via const
references. We'd essentially be going the route of having C++'s
const except that it's transitive, which is a definite loss IMHO,
but at the same time, with D's const, you frequently have to give
up on using const, because physical const is so restrictive as to
be unusable in many cases.
I honestly don't know if it's better to just say that you can't
use const if you want to do something like reference counting or
using an allocator or to gut the guarantees that D's const
provides. Ideally, we'd keep the guarantees, but they often seem
to end up being completely impractical in practice. And they
don't jive at all with the recent push to support RC.
That being said, if we are going to make a change like this, I'm
not sure if we even _can_ do it. immutable throws a serious
wrench in any attempt have something like C++'s const in D.
As it stands, the only thing that really ensures that immutable
objects aren't mutated is the type system. As I understand it, if
an immutable object gets put into ROM (or memory that's being
treated as ROM), then mutating it will cause a segfault, but the
only case where that might happen right now AFAIK is if the
object was created at compile time and stored as part of the
program's data. Certainly, any immutable objects created at
runtime are only protected from mutation by the type system. So,
even if the compiler makes _zero_ assumptions based on const,
casting away const and mutating is very dangerous, because you
risk mutating an immutable object and violating all of the
guarantees associated with that. The only time that casting away
const and mutating could work would be in cases where you could
somehow guarantee that the object you're mutating is actually a
mutable variable underneath the hood. That would be possible in a
restricted setting, but in a large program or in a public API,
it's a lot less likely that you can guarantee that the object
isn't actually immutable. There would have to be some way for the
type system to guarantee that the object isn't actually immutable
- which either means making it so that the type in question can't
be immutable for some reason or having an attribute other than
const for non-physical const.
Your @mutable suggestion/proposal does step in that direction by
basically making it so that a const type which contains a
@mutable member isn't really const. It's some other attribute
that's not explicitly named (cpp_const, logical_const,
@mutable_const, or whatever we'd want to call it). And that seems
like it would work to a point. It would even allow for implicit
casts instead of explicit ones and make the whole thing far safer
in general than simply allowing arbitrary casting would. However,
@mutable still isn't an attribute on the type. It's an attribute
on a member. So, as soon as you have an opaque pointer or a base
class reference, the compiler doesn't know that the object is
actually @mutable_const. So, it can't know that it's not legit to
have the object be immutable. Now, the compiler would have to
know that when the object is created, and presumably an
@mutable_const derived class couldn't convert to an immutable
base class, so maybe this would actually work, but it seems like
we're at serious risk of a loophole if we're not really careful
here. It feels like each time work through this I flip-flop as to
whether I think that it can work or not.
And of course, even if we do make @mutable work, it would
basically mean that const doesn't necessarily guarantee much of
anything anymore other than preventing accidental mutation, since
unless the compiler can guarantee that no @mutable_const is
involved, it can't assume much of anything about const (though
what it can assume about const is already pretty limited -
particularly when pure isn't involved), and programmers can't
assume that a const object will really not be mutated by const
member functions (though in practice, it's unlikely that anyone
is going to slap @mutable on everything). But even then, we'd
still be ahead of C++'s const if we have transitivity and
disallow casting away const to mutate.
I'm torn on the whole thing. On the one hand, I think that D's
guarantees with physical const have been great. On the other
hand, too much stuff doesn't work with it. And system programming
stuff like ref-counting, mutexes, and allocators definitely don't
work with it.
- Jonathan M Davis