const and immutable members

Jonathan M Davis jmdavisProg at gmx.com
Sun Sep 22 20:51:27 PDT 2013


On Monday, September 23, 2013 05:18:31 Daniel Davidson wrote:
> On Sunday, 22 September 2013 at 20:17:03 UTC, Jonathan M Davis
> > which can be useful
> > sometimes, but in the case of a struct, it makes it so that the
> > whole struct
> > can't be reassigned, so it's pretty much never a good idea IMHO
> > to have a
> > struct with const or immutable members -
> 
> Why that conclusion? That is, why should reassignment of S take
> priority over stable data used by S? For example, take something
> like a BalanceSheetForecaster which takes a BalanceSheet as input
> in its ctor. Isn't it important that the numerous queries on and
> forecasts by the BalanceSheetForecaster be on *exactly* the same
> data?

If any of a struct's members are const, you reassign them in a postblit 
constructor, meaning that if they're reference types, you can't copy them.

Dynamic arrays of S become almost useless (and static arrays _definitely become 
useless), because once any value of type S is initialized it can't be 
assigned. So, if you have

S[12] foo;

or

auto bar = new S[](17);

you can't reassign any of the elements in those arrays. Best case, you can 
mutate their members that aren't const or immutable, but all of their const 
and immutable members are stuck referring to the value in S.init.

Anywhere that involves S.init stops working properly, because you can't change 
any S's that were default-initialized with S.init to something else. So, 
outside of the most basic cases where you just put an S on the stack by itself 
and don't pass it to anything or put it in an array or anything like that, you 
start running into trouble, because you can't change the value of S.

Constrast that with where none of the member variables are const or immutable 
(or at most are tail-const or tail-immutable), and you can only not assign S 
when it's a const S or immutable S. In that sort of situation, you can often 
create an S as mutable and then cast it to const or immutable and get around 
the problems with S.init. e.g.

auto temp = new S[](3);
temp[0] = baz();
temp[1] = S(12);
temp[2] = blah();
auto bar = assumeUnique(temp);

bar and all of its elmeents are now immutable, but you could actually tweak 
them initially, whereas if any of the member variables were actually fully 
const or immutable, you couldn't change any of them from their init value.

> > but having them be tail-const or
> > tail-immutable still makes it so that what they refer to gets
> > all of the
> > benefits of const or immutable without restricting the struct.
> 
> I appreciate the explanation and think I understand the benefit
> of tail-const better. You would not easily be able to store
> modifiable collections of BalanceSheetForecasters, unless you did
> it with pointers. But the focus of the discussion seems to be on
> immutable(T)[] which is my fault for choosing the first and
> easiest type of aliasing I could in my example. I think the slice
> is really a special case when it comes to aliasing. With
> immutable(T)[] you have the benefit of not having to worry about
> aliasing because of the way it is implemented - the contiguous
> nature and copy on write semantics. But this is not the general
> case is it? For example, do the same arguments hold for
> associative arrays?
> 
> struct S {
>    immutable string[string] aarr;
> }
> 
> Doesn't using immutable there present the same problem as with
> the slice? S is no longer assignable. But who would recommend not
> using immutable in this case if you want aarr to be stable. If
> you do not use immutable then who knows when your array will grow
> without your expecting it? At least with the slice your memory
> safety is in the control of your module if you make it private.
> But with associative array it would depend entirely on client
> code providing the data still having a handle on it.

Of course, it's the same for AAs. Or classes. Or pointers. Or int. Or float. 
It's the same for _any_ type. If you make any of them fully immutable, then 
you can't reassign the struct, which makes the struct unusable in a number of 
situations.

But if your concern is client code messing with your member variable, then 
don't give them access to it in the first place. Encapsulate it properly, and 
use property functions or getters and setters to access it. If all you provide 
is a getter property or getter function and no setters, then they can't assign 
to it. And if it's a reference type, all you have to do is return a const 
reference to it rather than a mutable one, and client code won't be able to 
alter it. And in most cases, using tail-const also solves the problem, because 
then the reference can be altered but not the data. AAs are one of the few 
cases where that's not true, because there really isn't a way to have a tail-
const AA (not without an AA equivalent to std.typecons.Rebindable anyway). But 
it works with pointers and arrays just fine, and it works with classes if you 
use std.typecons.Rebindable. Just return a mutable reference to const or 
immutable data, and you don't have to worry about client code mutating your 
data.

If you're making your member variables const or immutable so that client code 
can't mutate them, then you're just causing yourself problems because you 
didn't use proper encapsulation.

- Jonathan M Davis


More information about the Digitalmars-d-learn mailing list