Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutability in D constructors

My previous question discussed making a copy constructor like so:

struct Foo {
    int i;

    this(int j) { i = j; }

    this(Foo rhs) { this = rhs; }
}

void main()
{
    auto f = Foo(5);
    auto g = new Foo(f);
}

But, if I make i immutable, the constructor fails to compile with

Error: cannot modify struct this Foo with immutable members

Why is this the case? I was under the impression that immutable members of a class or struct do not become immutable until the end of the constructor is reached.

like image 469
Matt Kline Avatar asked Dec 19 '22 14:12

Matt Kline


2 Answers

Okay. In general, I'd advise against having structs with immutable members. There are just too many places where it's useful to be able to assign to one. What you typically want to do with a struct is make it so that it can be mutable, const, or immutable as a whole. And for the most part, that just works. e.g.

struct Foo
{
    int i;

    this(int j) { i = j; }

    this(Foo rhs) { this = rhs; }
}

void main()
{
    immutable f = Foo(5);
}

compiles just fine. The one area that generally causes trouble with that is when you have to have a postblit constructor, because those don't currently work with const or immutable structs (it's something that sorely needs to be fixed, but it's still an open problem due to how the type system works - it may result in us having to add copy constructors to the language, or we may figure out how to do it, but for now, it doesn't work, and it can be annoying). But that only affects you if you need a postblit constructor, which most structs don't need (the problem will be fixed eventually though, because it really needs to be; it's just a question of how and when).

However, to answer your question more generally, let's look at a class. For instance, this code won't compile, because the constructor is not immutable, and the compiler can't convert an immutable class object to a mutable one (it can do that with structs, because it makes a copy, but with a class, it's just copying the reference, not the object, so it doesn't work):

class Foo
{
    int i;

    this(int j) { i = j; }
}

void main()
{
    auto f = new immutable Foo(5);
}

Instead of that compiling, you get this lovely error message:

q.d(10): Error: mutable method q.Foo.this is not callable using a immutable object
q.d(10): Error: no constructor for Foo

There are three ways to solve this. The first is to make the constructor immutable

class Foo
{
    int i;

    this(int j) immutable { i = j; }
}

and that works, but it makes it so that you can only construct Foos which are immutable, which usually isn't what you want (though it sometimes is). So, the second way to solve the problem would be to take the first solution a step further and overload the constructor. e.g.

class Foo
{
    int i;

    this(int j) { i = j; }

    this(int j) immutable { i = j; }
}

However, that requires code duplication, which isn't a lot here, but it could be a lot for other types. So, what is generally the best solution is to make the constructor pure.

class Foo
{
    int i;

    this(int j) pure { i = j; }
}

This works, because the compiler then knows that nothing has escaped the constructor (since pure guarantees that nothing escapes by being assigned to a global or static variable, and the constructor's parameters don't allow anything to escape either), and because it knows that no references to Foo or its members can escape the constructor, it knows that there are no other references to this Foo and that it therefore can safely convert it to mutable, const, or immutable without violating the type system. Of course, that only works if you can make the constructor pure and nothing can escape via the constructor's arguments, but that's usually the case, and when it isn't, you can always just overload the constructor on mutability, much as that's less desirable.

The same techniques can be used on structs if you really want const or immutable members, but again, I'd advise against it. It's just going to cause you more trouble than it's worth, especially when it's usually trivial to just make the whole struct const or immutable when declaring a variable.

like image 105
Jonathan M Davis Avatar answered Jan 02 '23 02:01

Jonathan M Davis


Making members of structs immutable stops assigning to the struct which in turn causes quite a few issues, you may which to rethink that.

Otherwise assign the members directly instead of relying on the assignment operator:

struct Foo {
    immutable int i;

    this(int j) { i = j; }

    this(in Foo rhs) { this.i = rhs.i; }
}
like image 32
ratchet freak Avatar answered Jan 02 '23 03:01

ratchet freak