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
|