Practical difference between a struct with const members and with mutable members?

Jonathan M Davis via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sat Apr 9 17:27:30 PDT 2016


On Saturday, April 09, 2016 16:07:36 pineapple via Digitalmars-d-learn wrote:
> I'm mainly coming from languages that haven't got structs, let
> alone the kind of differentiation D offers between
> mutable/immutable/const/etc variables, so I'm still trying to
> work out just when to use each - What's different between these
> two examples, practically speaking? When would you use one over
> the other?
>
> struct thing1{
>      const int x, y;
> }
>
> struct thing2{
>      int x, y;
> }
>
> Thanks!

You'll be much happier if you don't make the members of a struct const or
immutable. When they're const or immutable, you cannot mutate them, and you
cannot mutate the struct as a whole (though other members can still be
mutated). Not being able to mutate the const members is presumably what you
want, but not being able to mutate the struct as a whole can be very
annoying. For instance, if you have

struct S
{
    string foo;
    const int bar;
}

auto s = S("hello", 42);
s.foo = "world";
s.bar = 77; // does not compile
s = S("dlang", 99); // does not compile

Presumably, you don't want bar to be able to change (which is why you made
it const), so the first error is fine, but the second could be a big
problem. By making a portion of the struct const, you can't assign to it
anymore or do anything that would mutate it as a whole. You can just mutate
its mutable members directly. Contrast this with

struct S
{
    string foo;
    int bar;
}

auto s = S("hello", 42);
s.foo = "world";
s.bar = 77; // compiles
s = S("dlang", 99); // compiles

const t = S("str", 12);
t.foo = "ing"; // does not compile
t.bar = 7; // does not compile
t = S("duck", 2); // does not compile

This version of S can still be const if you want it to be, but it doesn't
have to be - though obviously, bar can now be mutated if the struct as a
whole is not const. The way to fix that is to provide a getter but not a
setter. eg.

struct S
{
public:
    @property string foo() const { return _foo; }
    @property void foo(string value) { _foo = value; }

    @property int bar() const { return _bar; }

private:
    string _foo;
    int _bar;
}

auto s = S("hello", 42);
s.foo = "world";
s.bar = 77; // does not compile
s = S("dlang", 99); // compiles

const t = S("str", 12);
t.foo = "ing"; // does not compile
t.bar = 7; // does not compile
t = S("duck", 2); // does not compile

Because foo provides both a getter and a setter, it can be mutated as
before, but because bar only provides a setter, it can't be mutated even
when the struct is mutable - but you can still replace its value when you
assign a value to the entire struct. And of course, if an instance of the
struct is marked as const, then you can't mutate any of it. So, this
approach gives you full flexibility.

Now, having const or immutable members for classes isn't generally a
problem, beacause they're on the heap, and you don't normally assign a new
value to a class (rather, you just assign a new value to a reference to that
object so that the reference refers to a different object, but the previous
object wasn't actually mutated). e.g.

class C
{
    this(string f, int b)
    {
        foo = f;
        bar = b;
    }

    string foo;
    const int bar;
}

auto c = new C("hello", 42);
c.foo = "world";
c.bar = 77; // does not compile
c = new C("dlang", 99); // compiles

const d = new C("str", 12);
d.foo = "ing"; // does not compile
d.bar = 7; // does not compile
d = new C("duck", 2); // does not compile

So, having const members for classes usually works well, but because structs
normally live on the stack rather than having that layer of indirection,
giving them const members does tend to get in the way.

- Jonathan M Davis



More information about the Digitalmars-d-learn mailing list