Immovable types

Stanislav Blinov via Digitalmars-d digitalmars-d at puremagic.com
Tue Apr 18 19:53:18 PDT 2017


Currently, we have the ability to disable postblit and/or 
assignments, thus create non-copyable types.
But it is always assumed that a value can be moved. Normally, 
this is great, as we don't have to deal with additional 
constructors explicitly. There are, however, occasions when move 
is undesirable (e.g. std.typecons.Scoped - class instance on the 
stack). What if a concept of immovable types was introduced? I.e. 
structs you can initialize, possibly copy, but never move. Having 
such types would e.g. disallow returning instances from 
functions, or make things like std.typecons.Scoped safe without 
relying on documented contract.
This would tie in with DIP1000, which seems not to propose using 
"scope" qualifier for type declarations.
Syntactically, this could be expressed by @disabling the rvalue 
ctor (e.g. @disable this(typeof(this))), similar to this() - a 
constructor which cannot be defined but can be @disable'd.

Consider:

// Code samples assume std.algorithm.move is additionally 
constrained
// w.r.t. disabled move construction

struct Scope(T)
{
     T value;
     this(T v) { value = v; }

     @disable this(Scope);
}

auto takesScope(Scope!int i) {}

auto usage()
{
     Scope!int i = 42;
     auto copyOfI = i;          // Ok, Scope is copyable
     takesScope(i);             // Ok, Scope is copyable
     takesScope(move(i));       // ERROR: Scope cannot be moved
     takesScope(Scope!int(10)); // Ok, constructed in-place
     return i;                  // ERROR: Scope cannot be moved
}

Non-copyable and immovable types will have to be explicitly 
initialized, as if they had @disable this(), as they can't even 
be initialized with .init:

struct ScopeUnique(T)
{
     T value;
     this(T v) { value = v; }

     @disable this(ScopeUnique);
     @disable this(this);
}

auto takesScopeUnique(ScopeUnique!int i) {}

auto usage()
{
     ScopeUnique!int i;                        // ERROR: i must be 
explicitly initialized
     ScopeUnique!int j = ScopeUnique!int.init; // ERROR: 
ScopeUnique is non-copyable
     ScopeUnique!int k = 42;                   // Ok
     k = ScopeUnique!int(30);                  // ERROR: 
ScopeUnique is non-copyable

     takesScopeUnique(k);                   // ERROR: ScopeUnique 
is non-copyable
     takesScopeUnique(move(k));             // ERROR: ScopeUnique 
cannot be moved
     takesScopeUnique(ScopeUnique!int(10)); // Ok, constructed 
in-place
     takesScopeUnique(ScopeUnique!int(ScopeUnique!int(10))); // 
ERROR: ScopeUnique cannot be moved
     return k;                              // ERROR: ScopeUnique 
cannot be moved.
}

This way, a type gains additional control over how it's instances 
can be passed around. At compile-time, it would help protect 
against escaping. At run-time, it opens a door for certain 
idioms, mainly more clearly expressing (transfer of) ownership.

It also brings certain symmetry: we already can differentiate 
between rvalue (copy) and lvalue assignments:

struct T
{
     this(int) {}
     void opAssign(T) {}
     void opAssign(ref T) {}
}

T t1, t2;
t1 = T(10);    // opAssign(T)
t2 = t1;       // opAssign(ref T)
t1 = move(t2); // opAssign(T)

but we cannot similarly differentiate the construction (move is 
always assumed to work):

T t;
T x = T(0);                    // this(int)
T y = t;                       // this(this)
T w = move(t);                 // ??? no constructor call at all

With the proposed capability, we would be able to impose or infer 
additional restrictions at compile time as to how an instance can 
be (is being) constructed.

I'd very much like to hear your thoughts on this, good/bad, if it 
already was proposed, anything. If it's found feasible, I could 
start a DIP. Destroy, please.


More information about the Digitalmars-d mailing list