From a05eee1ca6b4b64b268ec1bcea2d2822001bbf38 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Mon, 21 May 2018 15:52:02 +0300 Subject: [PATCH 01/10] Copy Constructor --- DIPs/DIP1xxx-rn.md | 645 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 645 insertions(+) create mode 100644 DIPs/DIP1xxx-rn.md diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md new file mode 100644 index 000000000..48bfde450 --- /dev/null +++ b/DIPs/DIP1xxx-rn.md @@ -0,0 +1,645 @@ +# The Copy Constructor + +| Field | Value | +|-----------------|---------------------------------------------------------------------------------| +| DIP: | (number/id -- assigned by DIP Manager) | +| Review Count: | 0 (edited by DIP Manager) | +| Authors: | Razvan Nitu - razvan.nitu1305@gmail.com, Andrei Alexandrescu - andrei@erdani.com| +| Implementation: | (links to implementation PR if any) | +| Status: | Will be set by the DIP manager (e.g. "Approved" or "Rejected") | + +## Abstract + +This document proposes the copy constructor semantics as an alternative +to the design flaws and inherent limitations of the postblit. + +### Reference + +* [1] https://github.com/dlang/dmd/pull/8032 + +* [2] https://forum.dlang.org/post/p9p64v$ue6$1@digitalmars.com + +* [3] http://en.cppreference.com/w/cpp/language/copy_constructor + +* [4] https://dlang.org/spec/struct.html#struct-postblit + +* [5] https://news.ycombinator.com/item?id=15008636 + +* [6] https://dlang.org/spec/struct.html#struct-constructor + +* [7] https://dlang.org/spec/struct.html#field-init + +## Contents +* [Rationale and Motivation](#rationale-and-motivation) +* [Description](#description) +* [Breaking Changes and Deprecations](#breaking-changes-and-deprecations) +* [Future Directions](#future-directions) +* [Acknowledgements](#acknowledgements) +* [Reviews](#reviews) + +## Rationale and Motivation + +This section highlights the existing problems with the postblit and motivates +why the implementation of a copy constructor is more desirable than an attempt +to fix all the postblit issues. + +### Overview of this(this) + +The postblit function is a non-overloadable, non-qualifiable +function. However, the compiler does not reject the +following code: + +```D +struct A { this(this) const {} } +struct B { this(this) immutable {} } +struct C { this(this) shared {} } +``` + +Since the semantics of the postblit in the presence of qualifiers was +not defined and most likely not intended, this led to a series of problems: + +* `const` postblits are not able to modify any fields in the destination +* `immutable` postblits never get called (resulting in compilation errors) +* `shared` postblits cannot guarantee atomicity while blitting the fields + +#### `const`/`immutable` postblits + +The solution for `const` and `immutable` postblits is to type check them as normal +constructors where the first assignment of a member is treated as an initialization +and subsequent assignments are treated as modifications. This is problematic because +after the blitting phase, the destination object is no longer in its initial state +and subsequent assignments to its fields will be regarded as modifications making +it impossible to construct `immutable`/`const` objects in the postblit. In addition, +it is possible for multiple postblits to modify the same field. Consider: + +```D +struct A +{ + immutable int a; + this(this) + { + this.a += 2; // modifying immutable or ok? + } +} + +struct B +{ + A a; + this(this) + { + this.a.a += 2; // modifying immutable error or ok? + } +} + +void main() +{ + B b = B(A(7)); + B c = b; +} +``` + +When `B c = b;` is encountered, the following actions are taken: + +1. `b`'s fields are blitted to `c` +2. A's postblit is called +3. B's postblit is called + +After `step 1`, the object `c` has the exact contents as `b` but it is not +initialized (the postblits still need to fix it) nor uninitialized (the field +`B.a` does not have its initial value). From a type checking perspective +this is a problem because the assignment inside A's postblit is breaking immutability. +This makes it impossible to postblit objects that have `immutable`/`const` fields. +To alleviate this problem we can consider that after the blitting phase the object is +in a raw state, therefore uninitialized; this way, the first assignment of `B.a.a` +is considered an initialization. However, after this step, the field +`B.a.a` is considered initialized, therefore how is the assignment inside B's postblit +supposed to be type checked ? Is it breaking immutability or should +it be legal? Indeed it is breaking immutability because it is changing an immutable +value, however being part of initialization (remember that `c` is initialized only +after all the postblits are ran) it should be legal, thus weakening the immutability +concept and creating a different strategy from the one that normal constructors +implement. + +#### Shared postblits + +Shared postblits cannot guarantee atomicity while blitting the fields because +that part is done automatically and it does not involve any +synchronization techniques. The following example demonstrates the problem: + +```D +shared struct A +{ + long a, b, c; + this(this) { ... } +} + +A a = A(1, 2, 3); + +void fun() +{ + A b = a; + /* do other work */ +} +``` + +Let's consider the above code is ran in a multithreaded environment +When `A b = a;` is encountered the following actions are taken: + +* `a`'s fields are copied to `b` +* the user code defined in this(this) is called + +In the blitting phase no synchronization mechanism is employed, which means +that while the copying is done, another thread may modify `a`'s data resulting +in the corruption of `b`. In order to fix this issue there are 4 possibilities: + +1. Make shared objects larger than 2 words uncopyable. This solution cannot be +taken into account because it imposes a major arbitrary limitation: almost all +structs will become uncopyable. + +2. Allow incorrect copying and expect that the user will +do the necessary synchronization. Example: + +```D +shared struct A +{ + Mutex m; + long a, b, c; + this(this) { ... } +} + +A a = A(1, 2, 3); + +void fun() +{ + A b; + a.m.acquire(); + b = a; + a.m.release(); + /* do other work */ +} +``` + +Although this solution solves our synchronization problem it does it in a manner +that requires unencapsulated attention at each copy site. Another problem is +represented by the fact that the mutex release is done after the postblit was +ran which imposes some overhead. The release may be done right after the blitting +phase (first line of the postblit) because the copy is thread-local, but then +we end up with non-scoped locking: the mutex is released in a different scope +than the scope in which it was acquired. Also, the mutex is automatically +(wrongfully) copied. + +3. Introduce a preblit function that will be called before blitting the fields. +The purpose of the preblit is to offer the possibility of preparing the data +before the blitting phase; acquiring the mutex on the `struct` that will be +copied is one of the operations that the preblit will be responsible for. Later +on, the mutex will be released in the postblit. This approach has the benefit of +minimizing the mutex protected area in a manner that offers encapsulation, but +suffers the disadvantage of adding even more complexity on top of existing one by +introducing a new concept which requires typecheking of disparate sections of +code (need to typecheck across preblit, mempcy and postblit). + +4. Use an implicit global mutex to synchronize the blitting of fields. This approach +has the advantage that the compiler will do all the work and synchronize all blitting +phases (even if the threads don't actually touch each other's data) at the cost +of performance. Python implements a global interpreter lock and it was proven +to cause unscalable high contention; there are ongoing discussions of removing it +from the Python implementation [5]. + +### Introducing the copy constructor + +As stated above, the fundamental problem with the postblit is the automatic blitting +of fields which makes it impossible to type check and cannot be synchronized without +additional costs. + +As an alternative, this DIP proposes the implementation of a copy constructor. The benefits +of this approach are the following: + +* it is a known concept. C++ has it [3]; +* it can be typechecked as a normal constructor (since no blitting is done, the data is +initialized as if it were in a normal constructor); this means that `const`/`immutable`/`shared` +copy constructors will be type checked exactly as their analogous constructors +* offers encapsulation + +The downside of this solution is that the user must do all the field copying by hand +and every time a field is added to a struct, the copy constructor must be modified. +However, this can be easily bypassed by D's introspection mechanisms. For example, +this simple code may be used as a language idiom or library function: + +```D +foreach (i, ref field; src.tupleof) + this.tupleof[i] = field; +``` + +As shown above, the single benefit of the postblit can be easily substituted with a +few lines of code. On the other hand, to fix the limitations of the postblit it is +required that more complexity is added for little to no benefit. In these circumstances, +for the sake of uniformity and consistency, replacing the postblit constructor with a +copy constructor is the reasonable thing to do. + +## Description + +This section discusses all the technical aspects regarding the semantics +of the copy constructor. + +### Syntax + +The following link exhibits the proposed syntax and the necessary grammar changes in order to support +the copy construct: + +https://github.com/dlang/dlang.org/pull/2414 + +A declaration is a copy constructor declaration if it is a constructor declaration annotated with +the `@implicit` attribute and takes only one parameter by reference that is of the same type as +`typeof(this)`. `@implicit` is a compiler recognised attribute like `@safe` or `nogc` and it can be +syntactally used the same as the others. `@implicit` can be legally used solely to mark the declaration +of a copy constructor; all other uses of this attribute will result in a compiler error. The proposed syntax +benefits from the advantage of declaring the copy constructor in an expressive manner without adding +additional complexity to the existing syntax. + +The copy constructor needs to be annotated with `@implicit` in order to distinguish a copy +constructor from a normal constructor and avoid silent modification of code behavior. Consider +this example: + +```d +import std.stdio; +struct A { + this(ref immutable A obj) { writeln("x"); } +} +void main() +{ + immutable A a1; + A a2 = A(a1); + A a3 = a1; +} +``` + +With the current state of the language, `x` is printed once. with the addition of the copy constructor without +`@implicit`, `x` would be printed twice, thus modifying the semantics of existing code. + +The argument to the copy constructor is passed by reference in order to avoid infinite recursion (passing by +value would require a copy of the `struct` which would be made by calling the copy constructor, thus leading +to an infinite chain of calls). + +The type qualifiers may be applied to the parameter of the copy constructor, but also to the function itself +in order to facilitate the ability to describe copies between objects of different mutability levels. The type +qualifiers are optional. + +### Semantics + +This sections discusses all the aspects regarding the semantic analysis and interaction with other +components of the copy constructor + +#### Requirements + +1. The type of the parameter to the copy constructor needs to be identical to `typeof(this)`; an error is issued +otherwise: + +```d +struct A +{ + int a; + string b; +} + +struct C +{ + A a; + string b; + @implicit this(ref C another) // ok, typeof(this) == C + { + this.a = another.a; + this.b = another.b; + } + + @implicit this(ref A another) // error typeof(this) != C + { + this.a = another; + this.b = "hello"; + } +} + +void main() +{ + C a, b; + A aa; + + a = b; // ok + a = aa; // error +} +``` +2. It is illegal to declare a copy constructor for a struct that has a postblit defined and vice versa: + +```d +struct A +{ + this(this) {} + @implicit this(ref A another) {} // error, struct A defines a postblit +} +``` + +Note that structs that do not define a postblit explicitly but contain fields that +define one have a generated postblit. From the copy constructor perspective it makes +no difference whether the postblit is user defined or generated: + +```d +struct A +{ + this(this) {} +} + +struct B +{ + A a; // A has a postblit -> __fieldPostblit is generated for B + @implicit this(ref B another) {} // error, struct B defines a postblit +} +``` + +#### Semantics + +The copy constructor typecheck is identical to the constructor one [[6](https://dlang.org/spec/struct.html#struct-constructor)] +[[7](https://dlang.org/spec/struct.html#field-init)]. + +The copy constructor overloads can be explicitly disabled: + +```d +struct A +{ + @disable @implicit this(ref A another) + @implicit this(ref immutable A another) +} + +void main() +{ + A a, b; + a = b; // error: disabled copy construction + + immutable A c; + A d = c; // ok + +} +``` + +In order to disable copy construction, all copy constructor overloads need to be disabled. +In the above example, only copies from mutable to mutable are disabled; the overload for +immutable to mutable copies is still callable. + +#### Overloading + +The copy constructor can be overloaded with different qualifiers applied to the parameter +(copying from qualified source) or to the copy constructor itself (copying to qualified +destination): + +```d +struct A +{ + @implicit this(ref A another) {} // 1 - mutable source, mutable destination + @implicit this(ref immutable A another) {} // 2 - immutable source, mutable destination + @implicit this(ref A another) immutable {} // 3 - mutable source, immutable destination + @implicit this(ref immutable A another) immutable {} // 4 - immutable source, immutable destination +} + +void main() +{ + A a, b, a1; + immutable A ia, ib, ia1; + + a = b; // calls 1 + a1 = ia; // calls 2 + ia = a; // calls 3 + ia1 = ib; // calls 4 +} +``` +The proposed model enables the user to define the copying from an object of any qualified type +to an object of any qualified type: any combination of 2 between mutable, `const`, `immutable`, `shared`, +`const shared`. + +The `inout` qualifier may be used for the copy constructor parameter in order to specify that mutable, `const` or `immutable` types are +treated the same: + +```d +struct A +{ + @implicit this(ref inout A another) immutable + { + } +} + +void main() +{ + A a; + const A b; + immutable A c, r1, r2, r3; + + // All call the same copy construcor because `inout` acts like a wildcard + a = r1; + b = r2; + c = r3; +} +``` + +In case of partial matching, the existing overloading and implicit conversions +apply to the argument. + +#### Interaction with `alias this` + +There are situations in which a struct defines both an `alias this` and a copy constructor, and +for which assignments to variables of the struct type may lead to ambiguities: + +```d +struct A +{ + int a; + immutable(A) fun() + { + return immutable A(7); + } + + alias fun this; + + @implicit this(ref A another) immutable {} +} + +struct B +{ + int a; + A fun() + { + return A(7); + } + + alias fun this; + + @implicit this(ref B another) immutable {} + +} + +void main() +{ + A a; + immutable A ia; + a = ia; // 1 - calls copy constructor + + B b, bc; + b = bc; // 2 - b is evaluated to B.fun +} +``` + +In situations where both the copy constructor and `alias this` are suitable +to solve the assignment (1), the copy constructor takes precedence over `alias this` +because it is considered more specialized (the sole purpose of the copy constructor is to +create copies). However, if no copy constructor in the overload set matches the exact +qualified types of the source and the destination, the `alias this` is preferred (2). + +#### Interaction with `opAssign` + +The copy constructor is used to initialize an object from another object, whereas `opAssign` is +used to copy an object to another object that has already been initialized: + +```d +struct A +{ + int a; + immutable int id; + this(int a, int b) + { + this.a = a; + this.b = b; + } + @implicit this(ref A rhs) + { + this.a = rhs.a; + this.b = rhs.b; + } + void opAssign(S rhs) + { + this.a = rhs.a; + } +} + +void main() +{ + A a = A(2); + A b = a; // calls copy constructor; + a.a = 5; + b = a; // calls opAssign; +} +``` + +The reason why both the copy constructor and the `opAssign` method are needed is because +the two are type checked differently: `opAssign` is type checked as a normal function whereas +the copy constructor is type checked as a constructor (where the first assignment of non-mutable +fields is allowed). However, in the majority of cases, the copy constructor body is identical +to the `opAssign` one: + +```d +struct A +{ + int a; + this(int a) + { + this.a = a; + } + @implicit this(ref A rhs) + { + this.a = rhs.a; + } + void opAssign(S rhs) + { + this.a = rhs.a; + } +} + +void main() +{ + A a = A(2); + A b = a; // calls copy constructor; + a.a = 5; + b = a; // calls opAssign; +} +``` + +In order to avoid the code duplication resulting from such situations, ideally, the user could +define a single method that deals with both copy construction and normal copying. This DIP proposes +the following resolution: if the copy constructor can be succesfully type checked as a normal +function (where the initialization of non-mutable fields is forbidden), then it can be used for +both initialization and assignment. If the mentioned condition does not hold, the user needs to +define an `opAssign` to handle the cases that the copy constructor cannot. + +#### Generated Copy Constructor + +A copy constructor is generated for a `struct S` if the following conditions are met: + +1. No copy constructor is defined for `S`. +2. At least one field of `S` defines a copy constructor + +The body of the generated copy constructor does memberwise initialization: + +```d +@implicit this(ref S s) +{ + this.field1 = s.field1; + this.field2 = s.field2; + ...; +} +``` + +For the fields that define a copy constructor, the assignment will be rewritten to a call +to it; for those that do not, trivial copying is employed. + +## Breaking Changes and Deprecations + +1. The parameter of the copy constructor is passed by a mutable reference to the +source object. This means that a call to the copy constructor may legally modify +the source object: + +```d +struct A +{ + int[] a; + @implicit this(ref A another) + { + another.a[2] = 3; + } +} + +void main() +{ + A a, b; + a = b; // b.a[2] is modified +} +``` + +A solution to this might be to make the reference `const`, but that would make code like +`this.a = another.a` inside the copy constructor illegal. Of course, this can be solved by means +of casting : `this.a = cast(int[])another.a`. + +2. If `@implicit` is used in existing code for a constructor, the constructor will be silently changed +to a copy constructor: + +```d +enum implicit = 0; +struct C +{ + @implicit this(ref C another) {} // normal constructor before DIP, copy constructor after +} +``` + +3. With this DIP `@implicit` becomes a compiler recognised attribute that can be used solely to +distinguish copy constructors from normal constructors. This will break code that used `@implicits` +as a used defined attribute: + +```d +enum implicit = 0; +@implicit void foo() {} // error: `@implicit is used solely to mark the definition of a copy constructor` +``` + +## Copyright & License + +Copyright (c) 2018 by the D Language Foundation + +Licensed under [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt) + +## Review + +The DIP Manager will supplement this section with a summary of each review stage +of the DIP process beyond the Draft Review. From 6904f9b0603cbfa8d92ada28114fece90afb1022 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 26 Jul 2018 18:04:10 +0100 Subject: [PATCH 02/10] Tweak examples, remove missing link I've renamed some variables to make the examples easier to read. --- DIPs/DIP1xxx-rn.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index 48bfde450..f3f8519bc 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -33,7 +33,6 @@ to the design flaws and inherent limitations of the postblit. * [Rationale and Motivation](#rationale-and-motivation) * [Description](#description) * [Breaking Changes and Deprecations](#breaking-changes-and-deprecations) -* [Future Directions](#future-directions) * [Acknowledgements](#acknowledgements) * [Reviews](#reviews) @@ -267,9 +266,9 @@ struct A { } void main() { - immutable A a1; - A a2 = A(a1); - A a3 = a1; + immutable A ia; + A a = A(ia); + A b = ia; } ``` @@ -320,11 +319,11 @@ struct C void main() { - C a, b; - A aa; + C c, d; + A a; - a = b; // ok - a = aa; // error + c = d; // ok + c = a; // error } ``` 2. It is illegal to declare a copy constructor for a struct that has a postblit defined and vice versa: @@ -373,8 +372,8 @@ void main() A a, b; a = b; // error: disabled copy construction - immutable A c; - A d = c; // ok + immutable A ia; + A c = ia; // ok } ``` From b797b946b31eacc0bdd936f292d1d2858cd1b6c3 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Thu, 16 Aug 2018 17:21:13 +0300 Subject: [PATCH 03/10] Add sections about overloading --- DIPs/DIP1xxx-rn.md | 223 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 202 insertions(+), 21 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index f3f8519bc..d5b6a1566 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -288,6 +288,65 @@ qualifiers are optional. This sections discusses all the aspects regarding the semantic analysis and interaction with other components of the copy constructor +#### Copy constructor usage + +The copy constructor is implicitly used by the compiler whenever a struct variable is initialized +as a copy of another variable of the same unqualified type: + +1. When a variable is explicitly initialized: + +```d +struct A +{ + @implicit this(ref A another) {} +} + +void main() +{ + A a; + A b = a; // copy constructor gets called + b = a; // assignment, not initialization +} +``` + +2. When a parameter is passed by value to a function: + +``` +struct A +{ + @implicit this(ref A another) {} +} + +void fun(A a) {} + +void main() +{ + A a; + fun(a); // copy constructor gets called +} +``` + +3. When a parameter is returned by value from a function: + +``` +struct A +{ + @implicit this(ref A another) {} +} + +A fun() +{ + A a; + return a; +} + +void main() +{ + A a; + a = fun(); // copy constructor gets called +} +``` + #### Requirements 1. The type of the parameter to the copy constructor needs to be identical to `typeof(this)`; an error is issued @@ -319,11 +378,10 @@ struct C void main() { - C c, d; + C c; + C d = c; // ok A a; - - c = d; // ok - c = a; // error + C e = a; // error } ``` 2. It is illegal to declare a copy constructor for a struct that has a postblit defined and vice versa: @@ -353,7 +411,7 @@ struct B } ``` -#### Semantics +#### Type checking The copy constructor typecheck is identical to the constructor one [[6](https://dlang.org/spec/struct.html#struct-constructor)] [[7](https://dlang.org/spec/struct.html#field-init)]. @@ -418,27 +476,153 @@ treated the same: ```d struct A { - @implicit this(ref inout A another) immutable - { - } + @implicit this(ref inout A another) immutable {} } void main() { - A a; - const A b; immutable A c, r1, r2, r3; - // All call the same copy construcor because `inout` acts like a wildcard - a = r1; - b = r2; - c = r3; + // All call the same copy constructor because `inout` acts like a wildcard + A a = r1; + const A b = r2; + immutable A c = r3; } ``` In case of partial matching, the existing overloading and implicit conversions apply to the argument. +#### Copy constructor call vs. Standard copying (memcpy) + +When a copy constructor is not defined for a struct, initializations are treated +by copying the contents from the memory location of the right-hand side expression +to the memory location of the left-hand side one (called "standard copying"): + +```d +struct A +{ + int[] a; +} + +void main() +{ + A a = A([7]); + A b = a; // mempcy(&b, &a) + immutable A c = A([12]); + immutable A d = c; // memcpy(&d, &c) +} +``` + +In C++, when a copy constructor is defined, initializations which fall under the stated categories +are lowered to a copy constructor call. If this design would be implemented, situations like these +would occur: + +```d +struct A +{ + int[] a; + @implicit this(ref immutable A) {} +} + +void fun(A a) +{} + +void main() +{ + A a; + A b = a; // error: cannot call `@implicit this(immutable(A))` with type `A` + fun(a) // ditto +} +``` + +Because all expressions that result in initialization by copying are lowered to a copy +constructor call, by defining a specific copy constructor the struct loses the posibility +of any standard copying. This is not acceptable, because we want the user to define only +the situations that are of interest and have the compiler generate the code for the default +cases. + +In order to solve this problem, there are 2 solutions: + +1. When at least one copy constructor is defined by a user, generate all copy constructors +with the signature `@implicit this(ref $q S) $q`. This solution has the advantage that +all initializations by copying can be directly lowered to a copy constructor call without +any other checks, but has the disadvantage that for any struct that defines one copy +constructor, another 8 have to be generated (mutable, `const`, `const inout`, `inout`, `immutable`, +`const inout shared`, `inout shared`, `const shared`, `shared`), regardless of the fact that +none might be used. This can be optimized by generating a templated copy constructor which will +be instantiated depending on the use case, but this still is not optimal in terms of compilation +time. + +2. Whenever an initialization by copying is encountered, a call to a copy constructor is tried +and if a matching copy constructor is found it is used, otherwise if no copy constructor is +suitable, standard copying is employed (and if that also fails, an error is issued). +This solution has the advantage that no copy constructor is generated `apriori` and all +undefined copies are treated by the standard copying (if possible). This solution is optimal +in terms of compilation time, since no code is generated at all, therefore it is the one +chosen in the design of the copy constructor. + +#### Overload resolution in the presence of parameters with copy constructors + +For a function in an overload set each argument is matched against the parameter the following way: + +1. Check if the argument type is a direct match to the parameter type. +2. If not, check if the argument is implicitly convertible to the parameter type. +3. If not, check if the argument has a copy constructor that converts to the parameter type. + +In the first 2 situations, if the check is true for a specific overload and that overload +is selected to be called, if any of the parameters have a matching copy constructor, it will get called, +otherwise, standard copying is employed. In the third situation, the overload matches based on +conversion resulted from the copy constructor: + +```d +struct A +{ + int[] a; + @implicit this(ref shared A) immutable {} +} + +void fun(const A a) {} // 1 +void fun(immutable A a) {} // 2 + +struct B +{ + int a[]; + @implicit this(ref A) shared {} // ctor1 + @implicit this(ref A) const {} // ctor2 + @implicit this(ref shared A) shared {} // ctor3 + @implicit this(ref immutable A) const {} // ctor4 +} + +void gun(shared A) {} // 3 +void gun(const A) {} // 4 + +void main() +{ + A a; + fun(a); // calls 1 - conversion to const match; standard copying + + const A a1; + fun(a1); // calls 1 - direct match; standard copying + + shared A a2; + fun(a2); // calls 2 - conversion with copy ctor match; call copy ctor on parameter + + B b; + fun(b); // error: both gun functions match, because copy + // ctor matches both ctor1 and ctor2 + + const B b1; + fun(b1) // calls 4 - direct match; standard copying + + immutable B b2; + fun(b2); // calls 4 - conversion to const match; call ctor4 + + shared B b3; + fun(b3); // calls 4 - direct match; call ctor3 +} +``` + #### Interaction with `alias this` There are situations in which a struct defines both an `alias this` and a copy constructor, and @@ -474,12 +658,11 @@ struct B void main() { - A a; immutable A ia; - a = ia; // 1 - calls copy constructor + A a = ia; // 1 - calls copy constructor - B b, bc; - b = bc; // 2 - b is evaluated to B.fun + B bc; + B b = bc; // 2 - b is evaluated to B.fun } ``` @@ -509,7 +692,7 @@ struct A this.a = rhs.a; this.b = rhs.b; } - void opAssign(S rhs) + void opAssign(A rhs) { this.a = rhs.a; } @@ -519,7 +702,6 @@ void main() { A a = A(2); A b = a; // calls copy constructor; - a.a = 5; b = a; // calls opAssign; } ``` @@ -552,7 +734,6 @@ void main() { A a = A(2); A b = a; // calls copy constructor; - a.a = 5; b = a; // calls opAssign; } ``` From 7815bc3fc49dd9f1a2195f871f3f6afe6c9d6c8b Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Fri, 17 Aug 2018 12:53:28 +0300 Subject: [PATCH 04/10] Add generated opAssign/generated copy constructors sections --- DIPs/DIP1xxx-rn.md | 166 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 143 insertions(+), 23 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index d5b6a1566..628a1930a 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -311,7 +311,7 @@ void main() 2. When a parameter is passed by value to a function: -``` +```d struct A { @implicit this(ref A another) {} @@ -328,7 +328,7 @@ void main() 3. When a parameter is returned by value from a function: -``` +```d struct A { @implicit this(ref A another) {} @@ -567,13 +567,13 @@ chosen in the design of the copy constructor. For a function in an overload set each argument is matched against the parameter the following way: 1. Check if the argument type is a direct match to the parameter type. -2. If not, check if the argument is implicitly convertible to the parameter type. -3. If not, check if the argument has a copy constructor that converts to the parameter type. +2. If not, check if the argument type is implicitly convertible to the parameter type. +3. If not, check if the argument has a copy constructor that converts it to the parameter type. In the first 2 situations, if the check is true for a specific overload and that overload is selected to be called, if any of the parameters have a matching copy constructor, it will get called, -otherwise, standard copying is employed. In the third situation, the overload matches based on -conversion resulted from the copy constructor: +otherwise, standard copying is employed. In the third situation, the overload is matched based on the +conversion resulted from the copy constructor, therefore it the copy constructor will surely get called: ```d struct A @@ -672,7 +672,86 @@ because it is considered more specialized (the sole purpose of the copy construc create copies). However, if no copy constructor in the overload set matches the exact qualified types of the source and the destination, the `alias this` is preferred (2). -#### Interaction with `opAssign` +#### Generating copy constructors + +A copy constructor is generated for a `struct` S if one of S's fields defines +a copy constructor that S does not define. + +A field of a `struct` may define multiple copy constructors. In this situation, +a copy constructor is generated for each overload. + +```d +struct M +{ + @implicit this(ref immutable M) {} + @implicit this(ref immutable M) shared {} +} + +struct S +{ + M m; + @implicit this(ref shared S) {} + /* The copy constructors with the following signatures are generated: + @implicit this(ref immutable S) + @implicit this(ref immutable S) shared + */ +} +``` + +The body of all the generated copy constructors does memberwise initialization for all +the fields: + +```d +{ + this.field1 = s.field1; + this.field2 = s.field2; + ...; +} +``` + +Inside the body of the generated copy constructors, for the fields that define a copy +constructor, the assignment will be rewritten to a call to it; for those that do not, +standard copying is employed, if possible. If certain fields cannot perform copies +(for example: the copy constructor is disabled), the generated copy constructor will be +annotated with `@disable`. + +If copy constructors for fields intersect, a single copy constructor is generated: + +```d +struct A +{ + int a; + @implicit this(ref A rhs) + { + this.a = rhs.a; + } +} + +struct B +{ + int[] b; + @implicit this(ref B rhs) + { + this.b = rhs.b.dup; + } +} + +struct C +{ + A a; + B b; + /* the following copy constructor is generated for C: + + @implicit this(ref C rhs) + { + this.a = rhs.a; // which, in turn, is lowered to a.__copyCtor(rhs.a); + this.b = rhs.b; // which, in turn, is lowered to b.__copyCtor(rhs.b); + } + */ +} +``` + +#### Generating `opAssign` from a copy constructor The copy constructor is used to initialize an object from another object, whereas `opAssign` is used to copy an object to another object that has already been initialized: @@ -739,32 +818,73 @@ void main() ``` In order to avoid the code duplication resulting from such situations, ideally, the user could -define a single method that deals with both copy construction and normal copying. This DIP proposes -the following resolution: if the copy constructor can be succesfully type checked as a normal -function (where the initialization of non-mutable fields is forbidden), then it can be used for -both initialization and assignment. If the mentioned condition does not hold, the user needs to -define an `opAssign` to handle the cases that the copy constructor cannot. +define a single method that deals with both copy construction and normal copying. This is possible +in certain situations which will be discussed below: -#### Generated Copy Constructor +1. If the struct contains any `const`/`immutable` fields, it is impossible to use the copy constructor +for `opAssign`, because the copy constructor might initialize them. Even if the copy constructor doesn't +modify the `const`/`immutable` fields the compiler has to analyze the function body to know that, which +is problematic in situations when the body is missing. In conclusion, `opAssign` can be generated from the +copy constructor when the `struct` contains only assignable (mutable) fields. -A copy constructor is generated for a `struct S` if the following conditions are met: +2. The copy constructor signature is : `@implicit this(ref $q1 S rhs) $q2`, where `q1` and `q2` represent the +qualifiers that can be applied to the function and the parameter (`const`, `immutable`, `shared`, etc.). The +problem that arises is: depending on the values of `$q1` and `$q2` what should the signature of `opAssign` be? -1. No copy constructor is defined for `S`. -2. At least one field of `S` defines a copy constructor +A solution might be to generate for every copy constructor its counterpart `opAssign`: `void opAssign(ref $q1 S rhs) $q2`. +However, when is a `const`/`immutable` `opAssign` needed? There might be obscure cases when that is useful, but those are +niche situations where the user must step it and clarify what the desired outcome is and define its own `opAssign`. For +the sake of simplicity, `opAssign` will be generated solely for copy constructors that have a missing `$q2`. -The body of the generated copy constructor does memberwise initialization: +3. If the struct that has a copy constructor does not define a destructor, it is easy to create the body of the +above-mentioned `opAssign`: ```d -@implicit this(ref S s) +void opAssign(ref $q1 S rhs) // version 1 { - this.field1 = s.field1; - this.field2 = s.field2; - ...; + S tmp = rhs; // copy constructor is called + memcpy(this, tmp); // blit it into this } ``` -For the fields that define a copy constructor, the assignment will be rewritten to a call -to it; for those that do not, trivial copying is employed. +If the `struct` does define a destructor, it has to get called on the destination: + +```d + void opAssign(ref $q1 S rhs) // version 2 +{ + this.__dtor; // ensure the dtor is called + memcpy(this, S.init) // bring the object in the initial state + this.copyCtor(rhs); // call constructor on object in .init state +} +``` + +The problem with the above solution is that it does not take into account the fact +that the copyCtor may throw and if it does, then the object will be in a partially +initialized state. In order to overcome this, two temporaries are used: + +```d +void opAssign(ref $q1 S rhs) // version 3 +{ + S tmp1 = rhs; // call copy constructor + S tmp2; + + // swapbits(tmp1, this); + memcpy(tmp2, this); + memcpy(this, tmp1); + memcpy(tmp1, tmp2); + + tmp1.__dtor(); +} +``` + +In this version, if the copy constructor throws the object will still be in a valid state. + +If the copy constructor is marked `nothrow` and the struct defines a destructor, then +`version 2` is used, otherwise `version 3`. If the `struct` does not define a destructor, +`version 1` is used. + +The generated `opAssign` functions inffer their attributes based on the copy constructor +and destructor attributes. ## Breaking Changes and Deprecations From 0bf2319019cd30e0ab90930d0291ebb286195b64 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Mon, 20 Aug 2018 15:33:20 +0300 Subject: [PATCH 05/10] Fix examples --- DIPs/DIP1xxx-rn.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index 628a1930a..bf069a667 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -427,8 +427,8 @@ struct A void main() { - A a, b; - a = b; // error: disabled copy construction + A b; + A a = b; // error: disabled copy construction immutable A ia; A c = ia; // ok @@ -457,13 +457,13 @@ struct A void main() { - A a, b, a1; - immutable A ia, ib, ia1; + A a; + immutable A ia; - a = b; // calls 1 - a1 = ia; // calls 2 - ia = a; // calls 3 - ia1 = ib; // calls 4 + A a2 = a; // calls 1 + A a3 = ia; // calls 2 + immutable A a4 = a; // calls 3 + immutable A a5 = ia; // calls 4 } ``` The proposed model enables the user to define the copying from an object of any qualified type From 43e5a4210b1b8acbb2ee46570941021acdddae1a Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Tue, 11 Sep 2018 13:39:42 +0300 Subject: [PATCH 06/10] Add rvalue information --- DIPs/DIP1xxx-rn.md | 84 ++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index bf069a667..2d97bb78a 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -343,7 +343,34 @@ A fun() void main() { A a; - a = fun(); // copy constructor gets called + a = fun(); // copy constructor gets called +} +``` + +The parameter of the copy constructor is passed by reference, so initializations are going +to be lowered to copy constructor calls only if the source is an lvalue. Although this can be +worked around by declaring temporary lvalues which can be forwarded to the copy constructor, +binding rvalues to lvalues is beyond the scope of this DIP. + +Note that when a function returns a struct that defines a copy constructor, the copy constructor +is called on the callee site, before actually returning: + +```d +struct A +{ + @implicit this(ref A rhs) {} +} + +A fun() +{ + A a; + return a; // lowered to return tmp.copyCtor(a) + // return A(); // rvalue, no copyCtor called +} + +void main() +{ + A b = fun(); // the return value of fun() is moved to the location of b } ``` @@ -830,61 +857,16 @@ copy constructor when the `struct` contains only assignable (mutable) fields. 2. The copy constructor signature is : `@implicit this(ref $q1 S rhs) $q2`, where `q1` and `q2` represent the qualifiers that can be applied to the function and the parameter (`const`, `immutable`, `shared`, etc.). The problem that arises is: depending on the values of `$q1` and `$q2` what should the signature of `opAssign` be? - A solution might be to generate for every copy constructor its counterpart `opAssign`: `void opAssign(ref $q1 S rhs) $q2`. However, when is a `const`/`immutable` `opAssign` needed? There might be obscure cases when that is useful, but those are niche situations where the user must step it and clarify what the desired outcome is and define its own `opAssign`. For the sake of simplicity, `opAssign` will be generated solely for copy constructors that have a missing `$q2`. -3. If the struct that has a copy constructor does not define a destructor, it is easy to create the body of the -above-mentioned `opAssign`: - -```d -void opAssign(ref $q1 S rhs) // version 1 -{ - S tmp = rhs; // copy constructor is called - memcpy(this, tmp); // blit it into this -} -``` - -If the `struct` does define a destructor, it has to get called on the destination: - -```d - void opAssign(ref $q1 S rhs) // version 2 -{ - this.__dtor; // ensure the dtor is called - memcpy(this, S.init) // bring the object in the initial state - this.copyCtor(rhs); // call constructor on object in .init state -} -``` - -The problem with the above solution is that it does not take into account the fact -that the copyCtor may throw and if it does, then the object will be in a partially -initialized state. In order to overcome this, two temporaries are used: - -```d -void opAssign(ref $q1 S rhs) // version 3 -{ - S tmp1 = rhs; // call copy constructor - S tmp2; - - // swapbits(tmp1, this); - memcpy(tmp2, this); - memcpy(this, tmp1); - memcpy(tmp1, tmp2); - - tmp1.__dtor(); -} -``` - -In this version, if the copy constructor throws the object will still be in a valid state. - -If the copy constructor is marked `nothrow` and the struct defines a destructor, then -`version 2` is used, otherwise `version 3`. If the `struct` does not define a destructor, -`version 1` is used. - -The generated `opAssign` functions inffer their attributes based on the copy constructor -and destructor attributes. +Taking into account the above mentioned restrictions we can generate a single `opAssign` function +and leverage the existing `opAssign` generation logic for the [[postblit](https://github.com/dlang/dmd/pull/8505)]. +Because `opAssing` is generated only if there are copy constructors that create a mutable object, the copy +constructor is going to be called (if possible) when the source is passed as a parameter to the `opAssign` +function. ## Breaking Changes and Deprecations From da504989bdd78513f4b3e747a3a679e4509d8930 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Tue, 2 Oct 2018 12:22:26 +0300 Subject: [PATCH 07/10] Remove @implicit + remove standard copying in the presence of copy constructors --- DIPs/DIP1xxx-rn.md | 346 ++++++++++++++------------------------------- 1 file changed, 110 insertions(+), 236 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index 2d97bb78a..a6991cbe0 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -242,38 +242,28 @@ of the copy constructor. ### Syntax -The following link exhibits the proposed syntax and the necessary grammar changes in order to support -the copy construct: - -https://github.com/dlang/dlang.org/pull/2414 - -A declaration is a copy constructor declaration if it is a constructor declaration annotated with -the `@implicit` attribute and takes only one parameter by reference that is of the same type as -`typeof(this)`. `@implicit` is a compiler recognised attribute like `@safe` or `nogc` and it can be -syntactally used the same as the others. `@implicit` can be legally used solely to mark the declaration -of a copy constructor; all other uses of this attribute will result in a compiler error. The proposed syntax -benefits from the advantage of declaring the copy constructor in an expressive manner without adding -additional complexity to the existing syntax. - -The copy constructor needs to be annotated with `@implicit` in order to distinguish a copy -constructor from a normal constructor and avoid silent modification of code behavior. Consider -this example: +A declaration is a copy constructor declaration if it is a constructor declaration that takes only one +parameter by reference that is of the same type as `typeof(this)`. Declaring a copy constructor as +mentioned has the advantage that the parser does not get modified at all, thus leaving the language grammar +unchanged. ```d import std.stdio; -struct A { - this(ref immutable A obj) { writeln("x"); } + +struct A +{ + this(ref A rhs) { writeln("x"); } // copy constructor } + void main() { - immutable A ia; - A a = A(ia); - A b = ia; + A a; + A b = a; // calls copy constructor implicitly + A c = A(b); // calls constructor explicitly } ``` -With the current state of the language, `x` is printed once. with the addition of the copy constructor without -`@implicit`, `x` would be printed twice, thus modifying the semantics of existing code. +The copy constructor may also be called explicitly since it is also a constructor. The argument to the copy constructor is passed by reference in order to avoid infinite recursion (passing by value would require a copy of the `struct` which would be made by calling the copy constructor, thus leading @@ -285,8 +275,22 @@ qualifiers are optional. ### Semantics -This sections discusses all the aspects regarding the semantic analysis and interaction with other -components of the copy constructor +This section discusses all the aspects regarding the semantic analysis and interaction with other +components of the copy constructor. + +#### Copy constructor and postblit cohabitation + +In order to assure a smooth transition from postblit to copy constructor, the following +strategy is employed: if a `struct` defines a postblit (user-defined or generated) all +copy constructor definitions will be ignored for that particular `struct` and the postblit +is going to be used. Existing codebases that didn't use the postblit may start using the +copy constructor without any problems, while codebases that did rely on the postblit may +start writing new code using the copy constructor while removing the deprecated postblit +from their code. + +Transitioning from using a postblit to using a copy constructor may simply be done by +replacing the postblit declaration `this(this)` with `this(ref inout(typeof(this)) rhs) +inout`. #### Copy constructor usage @@ -298,7 +302,7 @@ as a copy of another variable of the same unqualified type: ```d struct A { - @implicit this(ref A another) {} + this(ref A rhs) {} } void main() @@ -314,7 +318,7 @@ void main() ```d struct A { - @implicit this(ref A another) {} + this(ref A another) {} } void fun(A a) {} @@ -326,12 +330,15 @@ void main() } ``` -3. When a parameter is returned by value from a function: +3. When a parameter is returned by value from a function and NRVO cannot be performed: ```d struct A { - @implicit this(ref A another) {} + this(ref A another) + { + writeln("cpCtor"); + } } A fun() @@ -340,10 +347,18 @@ A fun() return a; } +A a; +A gun() +{ + return a; +} + void main() { A a; - a = fun(); // copy constructor gets called + a = fun(); // NRVO - no copy constructor call + A b; + b = gun(); // NRVO cannot be performed, copy constructor is called } ``` @@ -352,18 +367,19 @@ to be lowered to copy constructor calls only if the source is an lvalue. Althoug worked around by declaring temporary lvalues which can be forwarded to the copy constructor, binding rvalues to lvalues is beyond the scope of this DIP. -Note that when a function returns a struct that defines a copy constructor, the copy constructor -is called on the callee site, before actually returning: +Note that when a function returns a struct that defines a copy constructor and NRVO cannot be +performed, the copy constructor is called on the callee site, before actually returning. If NRVO +may be performed, then the copy is elided: ```d struct A { - @implicit this(ref A rhs) {} + this(ref A rhs) {} } +A a; A fun() { - A a; return a; // lowered to return tmp.copyCtor(a) // return A(); // rvalue, no copyCtor called } @@ -374,70 +390,6 @@ void main() } ``` -#### Requirements - -1. The type of the parameter to the copy constructor needs to be identical to `typeof(this)`; an error is issued -otherwise: - -```d -struct A -{ - int a; - string b; -} - -struct C -{ - A a; - string b; - @implicit this(ref C another) // ok, typeof(this) == C - { - this.a = another.a; - this.b = another.b; - } - - @implicit this(ref A another) // error typeof(this) != C - { - this.a = another; - this.b = "hello"; - } -} - -void main() -{ - C c; - C d = c; // ok - A a; - C e = a; // error -} -``` -2. It is illegal to declare a copy constructor for a struct that has a postblit defined and vice versa: - -```d -struct A -{ - this(this) {} - @implicit this(ref A another) {} // error, struct A defines a postblit -} -``` - -Note that structs that do not define a postblit explicitly but contain fields that -define one have a generated postblit. From the copy constructor perspective it makes -no difference whether the postblit is user defined or generated: - -```d -struct A -{ - this(this) {} -} - -struct B -{ - A a; // A has a postblit -> __fieldPostblit is generated for B - @implicit this(ref B another) {} // error, struct B defines a postblit -} -``` - #### Type checking The copy constructor typecheck is identical to the constructor one [[6](https://dlang.org/spec/struct.html#struct-constructor)] @@ -448,8 +400,8 @@ The copy constructor overloads can be explicitly disabled: ```d struct A { - @disable @implicit this(ref A another) - @implicit this(ref immutable A another) + @disable this(ref A rhs); + this(ref immutable A rhs) {} } void main() @@ -476,10 +428,10 @@ destination): ```d struct A { - @implicit this(ref A another) {} // 1 - mutable source, mutable destination - @implicit this(ref immutable A another) {} // 2 - immutable source, mutable destination - @implicit this(ref A another) immutable {} // 3 - mutable source, immutable destination - @implicit this(ref immutable A another) immutable {} // 4 - immutable source, immutable destination + this(ref A another) {} // 1 - mutable source, mutable destination + this(ref immutable A another) {} // 2 - immutable source, mutable destination + this(ref A another) immutable {} // 3 - mutable source, immutable destination + this(ref immutable A another) immutable {} // 4 - immutable source, immutable destination } void main() @@ -503,28 +455,30 @@ treated the same: ```d struct A { - @implicit this(ref inout A another) immutable {} + this(ref inout A rhs) immutable {} } void main() { - immutable A c, r1, r2, r3; + A r1; + const(A) r2; + immutable(A) r3; // All call the same copy constructor because `inout` acts like a wildcard - A a = r1; - const A b = r2; - immutable A c = r3; + immutable(A) a = r1; + immutable(A) b = r2; + immutable(A) c = r3; } ``` In case of partial matching, the existing overloading and implicit conversions apply to the argument. -#### Copy constructor call vs. Standard copying (memcpy) +#### Copy constructor call vs. Blitting When a copy constructor is not defined for a struct, initializations are treated by copying the contents from the memory location of the right-hand side expression -to the memory location of the left-hand side one (called "standard copying"): +to the memory location of the left-hand side one (called "blitting"): ```d struct A @@ -541,112 +495,22 @@ void main() } ``` -In C++, when a copy constructor is defined, initializations which fall under the stated categories -are lowered to a copy constructor call. If this design would be implemented, situations like these -would occur: - -```d -struct A -{ - int[] a; - @implicit this(ref immutable A) {} -} - -void fun(A a) -{} - -void main() -{ - A a; - A b = a; // error: cannot call `@implicit this(immutable(A))` with type `A` - fun(a) // ditto -} -``` - -Because all expressions that result in initialization by copying are lowered to a copy -constructor call, by defining a specific copy constructor the struct loses the posibility -of any standard copying. This is not acceptable, because we want the user to define only -the situations that are of interest and have the compiler generate the code for the default -cases. - -In order to solve this problem, there are 2 solutions: - -1. When at least one copy constructor is defined by a user, generate all copy constructors -with the signature `@implicit this(ref $q S) $q`. This solution has the advantage that -all initializations by copying can be directly lowered to a copy constructor call without -any other checks, but has the disadvantage that for any struct that defines one copy -constructor, another 8 have to be generated (mutable, `const`, `const inout`, `inout`, `immutable`, -`const inout shared`, `inout shared`, `const shared`, `shared`), regardless of the fact that -none might be used. This can be optimized by generating a templated copy constructor which will -be instantiated depending on the use case, but this still is not optimal in terms of compilation -time. - -2. Whenever an initialization by copying is encountered, a call to a copy constructor is tried -and if a matching copy constructor is found it is used, otherwise if no copy constructor is -suitable, standard copying is employed (and if that also fails, an error is issued). -This solution has the advantage that no copy constructor is generated `apriori` and all -undefined copies are treated by the standard copying (if possible). This solution is optimal -in terms of compilation time, since no code is generated at all, therefore it is the one -chosen in the design of the copy constructor. - -#### Overload resolution in the presence of parameters with copy constructors - -For a function in an overload set each argument is matched against the parameter the following way: - -1. Check if the argument type is a direct match to the parameter type. -2. If not, check if the argument type is implicitly convertible to the parameter type. -3. If not, check if the argument has a copy constructor that converts it to the parameter type. - -In the first 2 situations, if the check is true for a specific overload and that overload -is selected to be called, if any of the parameters have a matching copy constructor, it will get called, -otherwise, standard copying is employed. In the third situation, the overload is matched based on the -conversion resulted from the copy constructor, therefore it the copy constructor will surely get called: +Whenever a copy constructor is defined for a struct all implicit blitting is disabled for +that struct: ```d struct A { int[] a; - @implicit this(ref shared A) immutable {} + this(ref A rhs) {} } -void fun(const A a) {} // 1 -void fun(immutable A a) {} // 2 - -struct B -{ - int a[]; - @implicit this(ref A) shared {} // ctor1 - @implicit this(ref A) const {} // ctor2 - @implicit this(ref shared A) shared {} // ctor3 - @implicit this(ref immutable A) const {} // ctor4 -} - -void gun(shared A) {} // 3 -void gun(const A) {} // 4 +void fun(immutable A) {} void main() { - A a; - fun(a); // calls 1 - conversion to const match; standard copying - - const A a1; - fun(a1); // calls 1 - direct match; standard copying - - shared A a2; - fun(a2); // calls 2 - conversion with copy ctor match; call copy ctor on parameter - - B b; - fun(b); // error: both gun functions match, because copy - // ctor matches both ctor1 and ctor2 - - const B b1; - fun(b1) // calls 4 - direct match; standard copying - - immutable B b2; - fun(b2); // calls 4 - conversion to const match; call ctor4 - - shared B b3; - fun(b3); // calls 4 - direct match; call ctor3 + immutable A a; + fun(a); // error: copy constructor cannot be called with types (immutable) immutable } ``` @@ -666,7 +530,7 @@ struct A alias fun this; - @implicit this(ref A another) immutable {} + this(ref A another) immutable {} } struct B @@ -679,8 +543,7 @@ struct B alias fun this; - @implicit this(ref B another) immutable {} - + this(ref B another) immutable {} } void main() @@ -688,8 +551,8 @@ void main() immutable A ia; A a = ia; // 1 - calls copy constructor - B bc; - B b = bc; // 2 - b is evaluated to B.fun + A bc; + A b = bc; // 2 - calls B.fun } ``` @@ -710,17 +573,17 @@ a copy constructor is generated for each overload. ```d struct M { - @implicit this(ref immutable M) {} - @implicit this(ref immutable M) shared {} + this(ref immutable M) {} + this(ref immutable M) shared {} } struct S { M m; - @implicit this(ref shared S) {} + this(ref shared S) {} /* The copy constructors with the following signatures are generated: - @implicit this(ref immutable S) - @implicit this(ref immutable S) shared + this(ref immutable S) + this(ref immutable S) shared */ } ``` @@ -738,7 +601,7 @@ the fields: Inside the body of the generated copy constructors, for the fields that define a copy constructor, the assignment will be rewritten to a call to it; for those that do not, -standard copying is employed, if possible. If certain fields cannot perform copies +blitting is employed, if possible. If certain fields cannot perform copies (for example: the copy constructor is disabled), the generated copy constructor will be annotated with `@disable`. @@ -748,7 +611,7 @@ If copy constructors for fields intersect, a single copy constructor is generate struct A { int a; - @implicit this(ref A rhs) + this(ref A rhs) { this.a = rhs.a; } @@ -757,7 +620,7 @@ struct A struct B { int[] b; - @implicit this(ref B rhs) + this(ref B rhs) { this.b = rhs.b.dup; } @@ -769,7 +632,7 @@ struct C B b; /* the following copy constructor is generated for C: - @implicit this(ref C rhs) + this(ref C rhs) { this.a = rhs.a; // which, in turn, is lowered to a.__copyCtor(rhs.a); this.b = rhs.b; // which, in turn, is lowered to b.__copyCtor(rhs.b); @@ -793,7 +656,7 @@ struct A this.a = a; this.b = b; } - @implicit this(ref A rhs) + this(ref A rhs) { this.a = rhs.a; this.b = rhs.b; @@ -826,7 +689,7 @@ struct A { this.a = a; } - @implicit this(ref A rhs) + this(ref A rhs) { this.a = rhs.a; } @@ -854,7 +717,7 @@ modify the `const`/`immutable` fields the compiler has to analyze the function b is problematic in situations when the body is missing. In conclusion, `opAssign` can be generated from the copy constructor when the `struct` contains only assignable (mutable) fields. -2. The copy constructor signature is : `@implicit this(ref $q1 S rhs) $q2`, where `q1` and `q2` represent the +2. The copy constructor signature is: `this(ref $q1 S rhs) $q2`, where `q1` and `q2` represent the qualifiers that can be applied to the function and the parameter (`const`, `immutable`, `shared`, etc.). The problem that arises is: depending on the values of `$q1` and `$q2` what should the signature of `opAssign` be? A solution might be to generate for every copy constructor its counterpart `opAssign`: `void opAssign(ref $q1 S rhs) $q2`. @@ -868,6 +731,10 @@ Because `opAssing` is generated only if there are copy constructors that create constructor is going to be called (if possible) when the source is passed as a parameter to the `opAssign` function. +### POD (Plain Old Data) + +A struct that defines a copy constructor is not POD. + ## Breaking Changes and Deprecations 1. The parameter of the copy constructor is passed by a mutable reference to the @@ -878,7 +745,7 @@ the source object: struct A { int[] a; - @implicit this(ref A another) + this(ref A another) { another.a[2] = 3; } @@ -895,26 +762,33 @@ A solution to this might be to make the reference `const`, but that would make c `this.a = another.a` inside the copy constructor illegal. Of course, this can be solved by means of casting : `this.a = cast(int[])another.a`. -2. If `@implicit` is used in existing code for a constructor, the constructor will be silently changed -to a copy constructor: +2. This DIP changes the semantic of constructors which receive a parameter by reference of type +`typeof(this)`. The consequence is that the constructor might get called implicitly in some +situations: ```d -enum implicit = 0; struct C { - @implicit this(ref C another) {} // normal constructor before DIP, copy constructor after + this(ref C another) // normal constructor before DIP, copy constructor after + { + import std.stdio : writeln; + writeln("Yo"); + } } -``` -3. With this DIP `@implicit` becomes a compiler recognised attribute that can be used solely to -distinguish copy constructors from normal constructors. This will break code that used `@implicits` -as a used defined attribute: +void fun(C c) {} -```d -enum implicit = 0; -@implicit void foo() {} // error: `@implicit is used solely to mark the definition of a copy constructor` +void main() +{ + C c; + fun(c); +} ``` +Before this DIP this code would not print anything, while after it will print "Yo". However, a case can be +made that a constructor with the above definition could not be correctly used as anything else than a copy +constructor, in which case the current DIP, although surprising, actually fixes the code. + ## Copyright & License Copyright (c) 2018 by the D Language Foundation From 796930bc8186956b92e0b110e9cf9565d12081f3 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Thu, 15 Nov 2018 16:08:37 +0200 Subject: [PATCH 08/10] Apply review changes --- DIPs/DIP1xxx-rn.md | 236 ++++++++++++++++++++++----------------------- 1 file changed, 115 insertions(+), 121 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index a6991cbe0..7dbfca520 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -10,7 +10,7 @@ ## Abstract -This document proposes the copy constructor semantics as an alternative +This document proposes copy constructor semantics as an alternative to the design flaws and inherent limitations of the postblit. ### Reference @@ -38,9 +38,9 @@ to the design flaws and inherent limitations of the postblit. ## Rationale and Motivation -This section highlights the existing problems with the postblit and motivates +This section highlights the existing problems with the postblit and demonstrates why the implementation of a copy constructor is more desirable than an attempt -to fix all the postblit issues. +to resolve all the postblit issues. ### Overview of this(this) @@ -54,8 +54,8 @@ struct B { this(this) immutable {} } struct C { this(this) shared {} } ``` -Since the semantics of the postblit in the presence of qualifiers was -not defined and most likely not intended, this led to a series of problems: +Since the semantics of the postblit in the presence of qualifiers is +not defined and most likely not intended, a series of problems has arisen: * `const` postblits are not able to modify any fields in the destination * `immutable` postblits never get called (resulting in compilation errors) @@ -64,10 +64,10 @@ not defined and most likely not intended, this led to a series of problems: #### `const`/`immutable` postblits The solution for `const` and `immutable` postblits is to type check them as normal -constructors where the first assignment of a member is treated as an initialization +constructors, where the first assignment of a member is treated as an initialization and subsequent assignments are treated as modifications. This is problematic because after the blitting phase, the destination object is no longer in its initial state -and subsequent assignments to its fields will be regarded as modifications making +and subsequent assignments to its fields will be regarded as modifications, making it impossible to construct `immutable`/`const` objects in the postblit. In addition, it is possible for multiple postblits to modify the same field. Consider: @@ -103,21 +103,20 @@ When `B c = b;` is encountered, the following actions are taken: 2. A's postblit is called 3. B's postblit is called -After `step 1`, the object `c` has the exact contents as `b` but it is not +After `step 1`, the object `c` has the exact contents as `b`, but it is not initialized (the postblits still need to fix it) nor uninitialized (the field `B.a` does not have its initial value). From a type checking perspective -this is a problem because the assignment inside A's postblit is breaking immutability. +this is a problem, because the assignment inside A's postblit is breaking immutability. This makes it impossible to postblit objects that have `immutable`/`const` fields. To alleviate this problem we can consider that after the blitting phase the object is in a raw state, therefore uninitialized; this way, the first assignment of `B.a.a` -is considered an initialization. However, after this step, the field +is considered an initialization. However, after this step the field `B.a.a` is considered initialized, therefore how is the assignment inside B's postblit supposed to be type checked ? Is it breaking immutability or should -it be legal? Indeed it is breaking immutability because it is changing an immutable -value, however being part of initialization (remember that `c` is initialized only +it be legal? Indeed, it is breaking immutability because it is changing an immutable +value. However as this is part of initialization (remember that `c` is initialized only after all the postblits are ran) it should be legal, thus weakening the immutability -concept and creating a different strategy from the one that normal constructors -implement. +concept and creating a different strategy from that implemented by normal constructors. #### Shared postblits @@ -141,19 +140,19 @@ void fun() } ``` -Let's consider the above code is ran in a multithreaded environment -When `A b = a;` is encountered the following actions are taken: +Let's consider the above code is run in a multithreaded environment +When `A b = a;` is encountered, the following actions are taken: * `a`'s fields are copied to `b` -* the user code defined in this(this) is called +* the user code defined in `this(this)` is called -In the blitting phase no synchronization mechanism is employed, which means -that while the copying is done, another thread may modify `a`'s data resulting -in the corruption of `b`. In order to fix this issue there are 4 possibilities: +In the blitting phase, no synchronization mechanism is employed, which means +that while the copying is in progress, another thread may modify `a`'s data, resulting +in the corruption of `b`. There four possible approaches to fixing this issue: -1. Make shared objects larger than 2 words uncopyable. This solution cannot be +1. Make shared objects larger than two words uncopyable. This solution cannot be taken into account because it imposes a major arbitrary limitation: almost all -structs will become uncopyable. +`struct`s will become uncopyable. 2. Allow incorrect copying and expect that the user will do the necessary synchronization. Example: @@ -178,51 +177,51 @@ void fun() } ``` -Although this solution solves our synchronization problem it does it in a manner +Although this solution solves our synchronization problem, it does so in a manner that requires unencapsulated attention at each copy site. Another problem is -represented by the fact that the mutex release is done after the postblit was -ran which imposes some overhead. The release may be done right after the blitting -phase (first line of the postblit) because the copy is thread-local, but then -we end up with non-scoped locking: the mutex is released in a different scope -than the scope in which it was acquired. Also, the mutex is automatically -(wrongfully) copied. +represented by the fact that the mutex release occurs after the postblit is +run, which imposes some overhead. The release may occur immediately following +the blitting phase (the first line of the postblit) because the copy is +thread-local, but this results in non-scoped locking: the mutex is released +in a different scope than that in which it was acquired. Also, the mutex is +automatically (wrongfully) copied. 3. Introduce a preblit function that will be called before blitting the fields. -The purpose of the preblit is to offer the possibility of preparing the data +The purpose of the preblit is to offer the possibility of preparing data before the blitting phase; acquiring the mutex on the `struct` that will be copied is one of the operations that the preblit will be responsible for. Later on, the mutex will be released in the postblit. This approach has the benefit of -minimizing the mutex protected area in a manner that offers encapsulation, but -suffers the disadvantage of adding even more complexity on top of existing one by -introducing a new concept which requires typecheking of disparate sections of -code (need to typecheck across preblit, mempcy and postblit). +minimizing the mutex-protected area in a manner that offers encapsulation, but +suffers the disadvantage of adding even more complexity by introducing a new +concept which requires type checking disparate sections of code (need to type check +across preblit, `mempcy` and postblit). 4. Use an implicit global mutex to synchronize the blitting of fields. This approach has the advantage that the compiler will do all the work and synchronize all blitting -phases (even if the threads don't actually touch each other's data) at the cost -of performance. Python implements a global interpreter lock and it was proven -to cause unscalable high contention; there are ongoing discussions of removing it -from the Python implementation [5]. +phases (even if the threads don't actually touch each other's data) at a cost to +performance. Python implements a global interpreter lock which was proven to cause +unscalable high contention; there are ongoing discussions of removing it from the Python +implementation [5]. ### Introducing the copy constructor As stated above, the fundamental problem with the postblit is the automatic blitting -of fields which makes it impossible to type check and cannot be synchronized without +of fields, which makes type checking impossible and cannot be synchronized without additional costs. As an alternative, this DIP proposes the implementation of a copy constructor. The benefits of this approach are the following: * it is a known concept. C++ has it [3]; -* it can be typechecked as a normal constructor (since no blitting is done, the data is -initialized as if it were in a normal constructor); this means that `const`/`immutable`/`shared` +* it can be type checked as a normal constructor (since no blitting is done, the data are +initialized as if they were in a normal constructor); this means that `const`/`immutable`/`shared` copy constructors will be type checked exactly as their analogous constructors * offers encapsulation -The downside of this solution is that the user must do all the field copying by hand -and every time a field is added to a struct, the copy constructor must be modified. -However, this can be easily bypassed by D's introspection mechanisms. For example, -this simple code may be used as a language idiom or library function: +The downside of this solution is that the user must copy all fields by hand, and every +time a field is added to a `struct`, the copy constructor must be modified. However, this +can be easily bypassed by D's introspection mechanisms. For example, this simple code +may be used as a language idiom or library function: ```D foreach (i, ref field; src.tupleof) @@ -231,9 +230,9 @@ foreach (i, ref field; src.tupleof) As shown above, the single benefit of the postblit can be easily substituted with a few lines of code. On the other hand, to fix the limitations of the postblit it is -required that more complexity is added for little to no benefit. In these circumstances, +required that more complexity be added for little to no benefit. In these circumstances, for the sake of uniformity and consistency, replacing the postblit constructor with a -copy constructor is the reasonable thing to do. +copy constructor is a reasonable approach. ## Description @@ -243,8 +242,8 @@ of the copy constructor. ### Syntax A declaration is a copy constructor declaration if it is a constructor declaration that takes only one -parameter by reference that is of the same type as `typeof(this)`. Declaring a copy constructor as -mentioned has the advantage that the parser does not get modified at all, thus leaving the language grammar +parameter by reference that is of the same type as `typeof(this)`. Declaring a copy constructor in this +manner has the advantage that no parser modifications are required, thus leaving the language grammar unchanged. ```d @@ -266,35 +265,34 @@ void main() The copy constructor may also be called explicitly since it is also a constructor. The argument to the copy constructor is passed by reference in order to avoid infinite recursion (passing by -value would require a copy of the `struct` which would be made by calling the copy constructor, thus leading +value would require a copy of the `struct`, which would be made by calling the copy constructor, thus leading to an infinite chain of calls). -The type qualifiers may be applied to the parameter of the copy constructor, but also to the function itself +Type qualifiers may be applied to the parameter of the copy constructor, but also to the function itself, in order to facilitate the ability to describe copies between objects of different mutability levels. The type qualifiers are optional. ### Semantics -This section discusses all the aspects regarding the semantic analysis and interaction with other -components of the copy constructor. +This section discusses all the aspects of the semantic analysis and interaction between the copy +constructor and other components. #### Copy constructor and postblit cohabitation In order to assure a smooth transition from postblit to copy constructor, the following strategy is employed: if a `struct` defines a postblit (user-defined or generated) all copy constructor definitions will be ignored for that particular `struct` and the postblit -is going to be used. Existing codebases that didn't use the postblit may start using the -copy constructor without any problems, while codebases that did rely on the postblit may -start writing new code using the copy constructor while removing the deprecated postblit +will be preferred. Existing code bases that do not use the postblit may start using the +copy constructor without any problems, while codebases that rely on the postblit may +start writing new code using the copy constructor and remove the deprecated postblit from their code. -Transitioning from using a postblit to using a copy constructor may simply be done by -replacing the postblit declaration `this(this)` with `this(ref inout(typeof(this)) rhs) -inout`. +The transition from postblit to copy constructor may be simply achieved by replacing the postblit +declaration `this(this)` with `this(ref inout(typeof(this)) rhs) inout`. #### Copy constructor usage -The copy constructor is implicitly used by the compiler whenever a struct variable is initialized +The copy constructor is implicitly used by the compiler whenever a `struct` variable is initialized as a copy of another variable of the same unqualified type: 1. When a variable is explicitly initialized: @@ -362,13 +360,13 @@ void main() } ``` -The parameter of the copy constructor is passed by reference, so initializations are going +The parameter of the copy constructor is passed by reference, so initializations will be to be lowered to copy constructor calls only if the source is an lvalue. Although this can be worked around by declaring temporary lvalues which can be forwarded to the copy constructor, binding rvalues to lvalues is beyond the scope of this DIP. -Note that when a function returns a struct that defines a copy constructor and NRVO cannot be -performed, the copy constructor is called on the callee site, before actually returning. If NRVO +Note that when a function returns a `struct` instace that defines a copy constructor and NRVO cannot be +performed, the copy constructor is called at the callee site before returning. If NRVO may be performed, then the copy is elided: ```d @@ -392,10 +390,10 @@ void main() #### Type checking -The copy constructor typecheck is identical to the constructor one [[6](https://dlang.org/spec/struct.html#struct-constructor)] +The copy constructor type-check is identical to that of the constructor [[6](https://dlang.org/spec/struct.html#struct-constructor)] [[7](https://dlang.org/spec/struct.html#field-init)]. -The copy constructor overloads can be explicitly disabled: +Copy constructor overloads can be explicitly disabled: ```d struct A @@ -415,14 +413,14 @@ void main() } ``` -In order to disable copy construction, all copy constructor overloads need to be disabled. +In order to disable copy construction, all copy constructor overloads must be disabled. In the above example, only copies from mutable to mutable are disabled; the overload for immutable to mutable copies is still callable. #### Overloading The copy constructor can be overloaded with different qualifiers applied to the parameter -(copying from qualified source) or to the copy constructor itself (copying to qualified +(copying from a qualified source) or to the copy constructor itself (copying to a qualified destination): ```d @@ -445,11 +443,11 @@ void main() immutable A a5 = ia; // calls 4 } ``` -The proposed model enables the user to define the copying from an object of any qualified type -to an object of any qualified type: any combination of 2 between mutable, `const`, `immutable`, `shared`, +The proposed model enables the user to define the copy from an object of any qualified type +to an object of any qualified type: any combination of two among mutable, `const`, `immutable`, `shared`, `const shared`. -The `inout` qualifier may be used for the copy constructor parameter in order to specify that mutable, `const` or `immutable` types are +The `inout` qualifier may be applied to the copy constructor parameter in order to specify that mutable, `const`, or `immutable` types are treated the same: ```d @@ -471,14 +469,14 @@ void main() } ``` -In case of partial matching, the existing overloading and implicit conversions +In the case of partial matching, existing overloading and implicit conversions apply to the argument. -#### Copy constructor call vs. Blitting +#### Copy constructor call vs. blitting -When a copy constructor is not defined for a struct, initializations are treated +When a copy constructor is not defined for a `struct`, initializations are handled by copying the contents from the memory location of the right-hand side expression -to the memory location of the left-hand side one (called "blitting"): +to the memory location of the left-hand side expression (i.e. "blitting"): ```d struct A @@ -495,8 +493,8 @@ void main() } ``` -Whenever a copy constructor is defined for a struct all implicit blitting is disabled for -that struct: +When a copy constructor is defined for a `struct`, all implicit blitting is disabled for +that `struct`: ```d struct A @@ -516,8 +514,8 @@ void main() #### Interaction with `alias this` -There are situations in which a struct defines both an `alias this` and a copy constructor, and -for which assignments to variables of the struct type may lead to ambiguities: +A `struct` may define both an `alias this` and a copy constructor for which assignments to variables +of the `struct` type may lead to ambiguities: ```d struct A @@ -536,9 +534,9 @@ struct A struct B { int a; - A fun() + B fun() { - return A(7); + return B(7); } alias fun this; @@ -551,20 +549,20 @@ void main() immutable A ia; A a = ia; // 1 - calls copy constructor - A bc; - A b = bc; // 2 - calls B.fun + B bc; + B b = bc; // 2 - calls B.fun } ``` -In situations where both the copy constructor and `alias this` are suitable -to solve the assignment (1), the copy constructor takes precedence over `alias this` -because it is considered more specialized (the sole purpose of the copy constructor is to -create copies). However, if no copy constructor in the overload set matches the exact -qualified types of the source and the destination, the `alias this` is preferred (2). +When both the copy constructor and `alias this` are suitable to resolve the assignment (1), +the copy constructor takes precedence over `alias this` as it is considered more +specialized (the sole purpose of the copy constructor is to create copies). If no +copy constructor in the overload set matches the exact qualified types of the source and the +destination, the `alias this` is preferred (2). #### Generating copy constructors -A copy constructor is generated for a `struct` S if one of S's fields defines +A copy constructor is generated for a `struct S` if any member of S defines a copy constructor that S does not define. A field of a `struct` may define multiple copy constructors. In this situation, @@ -588,8 +586,7 @@ struct S } ``` -The body of all the generated copy constructors does memberwise initialization for all -the fields: +The body of all the generated copy constructors performs memberwise initialization for each field: ```d { @@ -599,13 +596,12 @@ the fields: } ``` -Inside the body of the generated copy constructors, for the fields that define a copy -constructor, the assignment will be rewritten to a call to it; for those that do not, -blitting is employed, if possible. If certain fields cannot perform copies -(for example: the copy constructor is disabled), the generated copy constructor will be -annotated with `@disable`. +Inside the body of the generated copy constructors, the assignment will be rewritten as a call +to the copy constructor of each field that defines one; blitting is employed where possible for +each field which does not define a copy constructor. If any field is uncopyable (e.g. the +copy constructor is disabled), the generated copy constructor will be annotated with `@disable`. -If copy constructors for fields intersect, a single copy constructor is generated: +If the copy constructors of any fields, a single copy constructor is generated: ```d struct A @@ -675,11 +671,10 @@ void main() } ``` -The reason why both the copy constructor and the `opAssign` method are needed is because -the two are type checked differently: `opAssign` is type checked as a normal function whereas -the copy constructor is type checked as a constructor (where the first assignment of non-mutable -fields is allowed). However, in the majority of cases, the copy constructor body is identical -to the `opAssign` one: +Both the copy constructor and the `opAssign` method are needed because they are type checked +differently: `opAssign` is type checked as a normal function, whereas the copy constructor is type +checked as a constructor (where the initial assignment of non-mutable fields is allowed). In many +cases, the body of the copy constructor will be identical to that of `opAssign`: ```d struct A @@ -707,33 +702,32 @@ void main() } ``` -In order to avoid the code duplication resulting from such situations, ideally, the user could -define a single method that deals with both copy construction and normal copying. This is possible -in certain situations which will be discussed below: +In order to avoid such code duplication, the user could ideally define a single method that deals with +both copy construction and normal copying. This is possible if the following conditions are met: -1. If the struct contains any `const`/`immutable` fields, it is impossible to use the copy constructor -for `opAssign`, because the copy constructor might initialize them. Even if the copy constructor doesn't -modify the `const`/`immutable` fields the compiler has to analyze the function body to know that, which +1. If the `struct` contains any `const`/`immutable` fields, it is impossible to use the copy constructor +for `opAssign` as the copy constructor might initialize the fields. The compiler must analyze the function +body to very that the copy constructor does not modify `const`/`immutable` fields, which is problematic in situations when the body is missing. In conclusion, `opAssign` can be generated from the copy constructor when the `struct` contains only assignable (mutable) fields. 2. The copy constructor signature is: `this(ref $q1 S rhs) $q2`, where `q1` and `q2` represent the -qualifiers that can be applied to the function and the parameter (`const`, `immutable`, `shared`, etc.). The -problem that arises is: depending on the values of `$q1` and `$q2` what should the signature of `opAssign` be? -A solution might be to generate for every copy constructor its counterpart `opAssign`: `void opAssign(ref $q1 S rhs) $q2`. -However, when is a `const`/`immutable` `opAssign` needed? There might be obscure cases when that is useful, but those are -niche situations where the user must step it and clarify what the desired outcome is and define its own `opAssign`. For +qualifiers that can be applied to the function and its parameter (`const`, `immutable`, `shared`, etc.). +Depending on the values of `$q1` and `$q2`, what should the signature of `opAssign` be? +A solution might be to generate the counterpart `opAssign` for each copy constructor, e.g. `void opAssign(ref $q1 S rhs) $q2`. +However, when is a `const`/`immutable` `opAssign` needed? There might be obscure cases when it is useful, but those are +niche situations where the user must step in to clarify the desired outcome, and define its own `opAssign`. For the sake of simplicity, `opAssign` will be generated solely for copy constructors that have a missing `$q2`. -Taking into account the above mentioned restrictions we can generate a single `opAssign` function +Taking into account the above restrictions, we can generate a single `opAssign` function and leverage the existing `opAssign` generation logic for the [[postblit](https://github.com/dlang/dmd/pull/8505)]. -Because `opAssing` is generated only if there are copy constructors that create a mutable object, the copy -constructor is going to be called (if possible) when the source is passed as a parameter to the `opAssign` +Because `opAssign` is generated only if there are copy constructors that create a mutable object, the copy +constructor will be called (if possible) when the source is passed as a parameter to the `opAssign` function. ### POD (Plain Old Data) -A struct that defines a copy constructor is not POD. +A `struct` that defines a copy constructor is not POD. ## Breaking Changes and Deprecations @@ -759,11 +753,11 @@ void main() ``` A solution to this might be to make the reference `const`, but that would make code like -`this.a = another.a` inside the copy constructor illegal. Of course, this can be solved by means -of casting : `this.a = cast(int[])another.a`. +`this.a = another.a` inside the copy constructor illegal. This can be solved with a cast, e.g. +`this.a = cast(int[])another.a`. 2. This DIP changes the semantic of constructors which receive a parameter by reference of type -`typeof(this)`. The consequence is that the constructor might get called implicitly in some +`typeof(this)`. The consequence is that existing constructors might be called implicitly in some situations: ```d @@ -785,9 +779,9 @@ void main() } ``` -Before this DIP this code would not print anything, while after it will print "Yo". However, a case can be +This will print "Yo" subsequent to the implementation of this DIP, where before it printed nothing. A case can be made that a constructor with the above definition could not be correctly used as anything else than a copy -constructor, in which case the current DIP, although surprising, actually fixes the code. +constructor, in which case this DIP actually fixes the code. ## Copyright & License From ea4ac871a5b5651aef0648e45989a212079b1899 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Tue, 11 Dec 2018 14:46:28 +0200 Subject: [PATCH 09/10] Remove opAssign generation, tweak copy constructor generation + minor additions --- DIPs/DIP1xxx-rn.md | 213 ++++++++------------------------------------- 1 file changed, 36 insertions(+), 177 deletions(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index 7dbfca520..489132d69 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -242,23 +242,25 @@ of the copy constructor. ### Syntax A declaration is a copy constructor declaration if it is a constructor declaration that takes only one -parameter by reference that is of the same type as `typeof(this)`. Declaring a copy constructor in this -manner has the advantage that no parser modifications are required, thus leaving the language grammar -unchanged. +non-default parameter by reference that is of the same type as `typeof(this)`, followed by any number +of default parameters. Declaring a copy constructor in this manner has the advantage that no parser +modifications are required, thus leaving the language grammar unchanged. ```d import std.stdio; struct A { - this(ref A rhs) { writeln("x"); } // copy constructor + this(ref A rhs) { writeln("x"); } // copy constructor + this(ref A rhs, int b = 7) immutable { writeln(b)} // copy constructor with default parameter } void main() { A a; - A b = a; // calls copy constructor implicitly - A c = A(b); // calls constructor explicitly + A b = a; // calls copy constructor implicitly - prints "x" + A c = A(b); // calls constructor explicitly + immutable A d = a; // calls copy constructor implicittly - prints 7 } ``` @@ -361,7 +363,7 @@ void main() ``` The parameter of the copy constructor is passed by reference, so initializations will be -to be lowered to copy constructor calls only if the source is an lvalue. Although this can be +lowered to copy constructor calls only if the source is an lvalue. Although this can be worked around by declaring temporary lvalues which can be forwarded to the copy constructor, binding rvalues to lvalues is beyond the scope of this DIP. @@ -520,10 +522,10 @@ of the `struct` type may lead to ambiguities: ```d struct A { - int a; + int *a; immutable(A) fun() { - return immutable A(7); + return immutable A(); } alias fun this; @@ -533,201 +535,58 @@ struct A struct B { - int a; - B fun() + int *a; + immutable(B) fun() { - return B(7); + return immutable B(); } alias fun this; - this(ref B another) immutable {} + this(ref A another) {} } void main() -{ - immutable A ia; - A a = ia; // 1 - calls copy constructor - - B bc; - B b = bc; // 2 - calls B.fun -} -``` - -When both the copy constructor and `alias this` are suitable to resolve the assignment (1), -the copy constructor takes precedence over `alias this` as it is considered more -specialized (the sole purpose of the copy constructor is to create copies). If no -copy constructor in the overload set matches the exact qualified types of the source and the -destination, the `alias this` is preferred (2). - -#### Generating copy constructors - -A copy constructor is generated for a `struct S` if any member of S defines -a copy constructor that S does not define. - -A field of a `struct` may define multiple copy constructors. In this situation, -a copy constructor is generated for each overload. - -```d -struct M -{ - this(ref immutable M) {} - this(ref immutable M) shared {} -} - -struct S -{ - M m; - this(ref shared S) {} - /* The copy constructors with the following signatures are generated: - this(ref immutable S) - this(ref immutable S) shared - */ -} -``` - -The body of all the generated copy constructors performs memberwise initialization for each field: - -```d -{ - this.field1 = s.field1; - this.field2 = s.field2; - ...; -} -``` - -Inside the body of the generated copy constructors, the assignment will be rewritten as a call -to the copy constructor of each field that defines one; blitting is employed where possible for -each field which does not define a copy constructor. If any field is uncopyable (e.g. the -copy constructor is disabled), the generated copy constructor will be annotated with `@disable`. - -If the copy constructors of any fields, a single copy constructor is generated: - -```d -struct A -{ - int a; - this(ref A rhs) - { - this.a = rhs.a; - } -} - -struct B -{ - int[] b; - this(ref B rhs) - { - this.b = rhs.b.dup; - } -} - -struct C { A a; - B b; - /* the following copy constructor is generated for C: + immutable A ia = a; // copy constructor - this(ref C rhs) - { - this.a = rhs.a; // which, in turn, is lowered to a.__copyCtor(rhs.a); - this.b = rhs.b; // which, in turn, is lowered to b.__copyCtor(rhs.b); - } - */ + B b; + immutable B ib = b; // error: copy constructor may not be called for argument types `() immutable` } ``` -#### Generating `opAssign` from a copy constructor +When both the copy constructor and `alias this` are suitable to resolve the +assignment, the copy constructor takes precedence over `alias this` as it is +considered more specialized (the sole purpose of the copy constructor is to +create copies). If no copy constructor in the overload set is matched, an error +is issued, even though an `alias this` might be used to resolve the assignemnt. -The copy constructor is used to initialize an object from another object, whereas `opAssign` is -used to copy an object to another object that has already been initialized: +#### Generating copy constructors -```d -struct A -{ - int a; - immutable int id; - this(int a, int b) - { - this.a = a; - this.b = b; - } - this(ref A rhs) - { - this.a = rhs.a; - this.b = rhs.b; - } - void opAssign(A rhs) - { - this.a = rhs.a; - } -} +A copy constructor is generated implicitly by the compiler for a `struct S` if: -void main() -{ - A a = A(2); - A b = a; // calls copy constructor; - b = a; // calls opAssign; -} -``` +1. `S` does not define any copy constructors; +2. `S` does not have an overlapping field that defines a copy constructor; +3. `S` defines at least one member that has a copy constructor. -Both the copy constructor and the `opAssign` method are needed because they are type checked -differently: `opAssign` is type checked as a normal function, whereas the copy constructor is type -checked as a constructor (where the initial assignment of non-mutable fields is allowed). In many -cases, the body of the copy constructor will be identical to that of `opAssign`: +If all of the restrictions above are met, the following copy constructor is generated: ```d -struct A +this(ref inout(S) src) inout { - int a; - this(int a) - { - this.a = a; - } - this(ref A rhs) - { - this.a = rhs.a; - } - void opAssign(S rhs) - { - this.a = rhs.a; - } -} - -void main() -{ - A a = A(2); - A b = a; // calls copy constructor; - b = a; // calls opAssign; + foreach (i, ref inout field; src.tupleof) + this.tupleof[i] = field; } ``` -In order to avoid such code duplication, the user could ideally define a single method that deals with -both copy construction and normal copying. This is possible if the following conditions are met: +### Plain Old Data (POD) -1. If the `struct` contains any `const`/`immutable` fields, it is impossible to use the copy constructor -for `opAssign` as the copy constructor might initialize the fields. The compiler must analyze the function -body to very that the copy constructor does not modify `const`/`immutable` fields, which -is problematic in situations when the body is missing. In conclusion, `opAssign` can be generated from the -copy constructor when the `struct` contains only assignable (mutable) fields. - -2. The copy constructor signature is: `this(ref $q1 S rhs) $q2`, where `q1` and `q2` represent the -qualifiers that can be applied to the function and its parameter (`const`, `immutable`, `shared`, etc.). -Depending on the values of `$q1` and `$q2`, what should the signature of `opAssign` be? -A solution might be to generate the counterpart `opAssign` for each copy constructor, e.g. `void opAssign(ref $q1 S rhs) $q2`. -However, when is a `const`/`immutable` `opAssign` needed? There might be obscure cases when it is useful, but those are -niche situations where the user must step in to clarify the desired outcome, and define its own `opAssign`. For -the sake of simplicity, `opAssign` will be generated solely for copy constructors that have a missing `$q2`. - -Taking into account the above restrictions, we can generate a single `opAssign` function -and leverage the existing `opAssign` generation logic for the [[postblit](https://github.com/dlang/dmd/pull/8505)]. -Because `opAssign` is generated only if there are copy constructors that create a mutable object, the copy -constructor will be called (if possible) when the source is passed as a parameter to the `opAssign` -function. +A `struct` that defines a copy constructor is not POD. -### POD (Plain Old Data) +### Interaction with unions -A `struct` that defines a copy constructor is not POD. +Unions may not have fields that define a copy constructor. ## Breaking Changes and Deprecations From efacbf8efde8027291113633984b0e7a039e8f30 Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Tue, 18 Dec 2018 11:12:04 +0200 Subject: [PATCH 10/10] Add an explanatory note to the interation with unions sections --- DIPs/DIP1xxx-rn.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DIPs/DIP1xxx-rn.md b/DIPs/DIP1xxx-rn.md index 489132d69..b6c82f1f7 100644 --- a/DIPs/DIP1xxx-rn.md +++ b/DIPs/DIP1xxx-rn.md @@ -586,7 +586,9 @@ A `struct` that defines a copy constructor is not POD. ### Interaction with unions -Unions may not have fields that define a copy constructor. +If an `union S` has fields that define a copy constructor, whevener an object of type +`S` is initialized by copy, an error will be issued. The same rule applies to overlapped +fields (anonymous unions). ## Breaking Changes and Deprecations