digitalmars.D - Immovable types
- Stanislav Blinov (95/95) Apr 18 2017 Currently, we have the ability to disable postblit and/or
- sarn (7/10) Apr 18 2017 I don't have comments about the syntax, but I did want this
- kinke (10/11) Apr 19 2017 It's not just assumed, it's a key requirement for structs in D,
- Stanislav Blinov (14/25) Apr 19 2017 That's not quite correct. Copy elision in C++ also elides copy
- Meta (5/8) Apr 19 2017 It's an interesting idea but I can't even begin to fathom how
- Stanislav Blinov (17/25) Apr 19 2017 It should not break any existing code, unless it is using the
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.
Apr 18 2017
On Wednesday, 19 April 2017 at 02:53:18 UTC, Stanislav Blinov wrote: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.I don't have comments about the syntax, but I did want this feature when writing Xanthe (https://gitlab.com/sarneaud/xanthe). In normal D you can make a struct instance effectively immovable by dynamically allocating it, but I had to allocate stuff statically or on the stack.
Apr 18 2017
On Wednesday, 19 April 2017 at 02:53:18 UTC, Stanislav Blinov wrote:But it is always assumed that a value can be moved.It's not just assumed, it's a key requirement for structs in D, as the compiler can move stuff automatically this way (making a bitcopy and then eliding the postblit ctor for the new instance and the destructor for the moved-from instance). That is quite a different concept to C++, where a (non-elided) special move ctor is required, moved-from instances need to be reset so that their (non-elided) destructor doesn't free moved-from resources etc.
Apr 19 2017
On Wednesday, 19 April 2017 at 08:52:45 UTC, kinke wrote:On Wednesday, 19 April 2017 at 02:53:18 UTC, Stanislav Blinov wrote:That's not quite correct. Copy elision in C++ also elides copy and move ctors and dtor. Move ctors aren't a requirement, they *can* be defined to override default move semantics, or deleted to disable move construction. That is concerning optimizations performed by the compiler. Library move(), both in C++ and in D, cannot elide the destructor, as the value already exists. But move() in C++ and D is indeed different. In C++ it's just a cast, and it is up to the programmer to redefine the semantics if needed, or disable it. In D, we're not allowed to do either. I'm only proposing to relax the restriction in terms of disabling move, not introduce move ctors into D.But it is always assumed that a value can be moved.It's not just assumed, it's a key requirement for structs in D, as the compiler can move stuff automatically this way (making a bitcopy and then eliding the postblit ctor for the new instance and the destructor for the moved-from instance). That is quite a different concept to C++, where a (non-elided) special move ctor is required, moved-from instances need to be reset so that their (non-elided) destructor doesn't free moved-from resources etc.
Apr 19 2017
On Wednesday, 19 April 2017 at 02:53:18 UTC, Stanislav Blinov wrote: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:It's an interesting idea but I can't even begin to fathom how much code this would break. So much D code relies on every type having a valid .init.
Apr 19 2017
On Wednesday, 19 April 2017 at 14:45:59 UTC, Meta wrote:On Wednesday, 19 April 2017 at 02:53:18 UTC, Stanislav Blinov wrote:It should not break any existing code, unless it is using the syntax disable this(typeof(this)), which, at the moment, is nonsensical, though not invalid. Nor does it make .init invalid. Non-copyable immovables simply won't be able to explicitly initialize from it (it's an rvalue). We'll still be able to e.g. compare against .init, etc: struct Immovable { int value = 42; this(int v) { value = v; } disable this(this); disable this(Immovable); } assert(Immovable.init.value == 42); Immovable i = 42; assert(i == Immovable.init);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:It's an interesting idea but I can't even begin to fathom how much code this would break. So much D code relies on every type having a valid .init.
Apr 19 2017