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