From 186c49840754a8574f4fa0ba276ace7907721710 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Wed, 25 May 2016 12:07:29 +0100 Subject: [PATCH 01/19] Add std.typecons.Rebindable!struct --- std/typecons.d | 231 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 3 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 25cf9e00841..7a2b0b0507a 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2478,11 +2478,11 @@ Convenience function for creating a `Rebindable` using automatic type inference. Params: - obj = A reference to an object, interface, associative array, or an array slice - to initialize the `Rebindable` with. + obj = A reference to an object, interface, associative array, or an array slice. + s = Struct instance. Returns: - A newly constructed `Rebindable` initialized with the given reference. + A newly constructed `Rebindable` initialized with the given data. */ Rebindable!T rebindable(T)(T obj) if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) @@ -2492,6 +2492,17 @@ if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArr return ret; } +/// ditto +Rebindable!S rebindable(S)(S s) +if (is(S == struct) && !isInstanceOf!(Rebindable, S)) +{ + // workaround for rebindableS = rebindable(s) + static if (isMutable!S) + return s; + else + return Rebindable!S(s); +} + /// @system unittest { @@ -2639,6 +2650,220 @@ Rebindable!T rebindable(T)(Rebindable!T obj) assert(rebindable(pr3341_aa)[321] == 543); } +/** Models safe reassignment of otherwise constant struct instances. + * + * A struct with a field of reference type cannot be assigned to a constant + * struct of the same type. `Rebindable!(const S)` allows assignment to + * a `const S` while enforcing only constant access to its fields. + * + * `Rebindable!(immutable S)` does the same but field access may create a + * temporary copy of `S` in order to enforce _true immutability. + */ +template Rebindable(S) +if (is(S == struct)) +{ + static if (isMutable!S) + alias Rebindable = S; + else + struct Rebindable + { + private: + // mutPayload's pointers must be treated as tail const + void[S.sizeof] mutPayload; + + void emplace(ref S s) + { + import std.conv : emplace; + static if (__traits(compiles, () @safe {S tmp = s;})) + () @trusted {emplace!S(mutPayload, s);}(); + else + emplace!S(mutPayload, s); + } + + public: + this()(auto ref S s) + { + emplace(s); + } + + // immutable S cannot be passed to auto ref S above + static if (!is(S == immutable)) + this()(immutable S s) + { + emplace(s); + } + + void opAssign()(auto ref S s) + { + movePayload; + emplace(s); + } + + static if (!is(S == immutable)) + void opAssign()(immutable S s) + { + movePayload; + emplace(s); + } + + void opAssign(Rebindable other) + { + this = other.trustedPayload; + } + + // must not escape when S is immutable + private + ref trustedPayload() @trusted + { + return *cast(S*)mutPayload.ptr; + } + + static if (!is(S == immutable)) + ref S Rebindable_getRef() @property + { + // payload exposed as const ref when S is const + return trustedPayload; + } + + static if (is(S == immutable)) + S Rebindable_get() @property + { + // we return a copy for immutable S + return trustedPayload; + } + + static if (is(S == immutable)) + alias Rebindable_get this; + else + alias Rebindable_getRef this; + + private + auto movePayload() @trusted + { + import std.algorithm : move; + return cast(S)move(*cast(Unqual!S*)mutPayload.ptr); + } + + ~this() + { + // call destructor with proper constness + movePayload; + } + } +} + +/// +@safe unittest +{ + static struct S + { + int* ptr; + } + S s = S(new int); + + const cs = s; + // Can't assign s.ptr to cs.ptr + static assert(!__traits(compiles, {s = cs;})); + + Rebindable!(const S) rs = s; + assert(rs.ptr is s.ptr); + // rs.ptr is const + static assert(!__traits(compiles, {rs.ptr = null;})); + + // Can't assign s.ptr to rs.ptr + static assert(!__traits(compiles, {s = rs;})); + + const S cs2 = rs; + // Rebind rs + rs = cs2; + rs = S(); + assert(rs.ptr is null); +} + +// Test Rebindable!immutable +@safe unittest +{ + static struct S + { + int* ptr; + } + S s = S(new int); + + Rebindable!(immutable S) ri = S(new int); + assert(ri.ptr !is null); + static assert(!__traits(compiles, {ri.ptr = null;})); + + // ri is not compatible with mutable S + static assert(!__traits(compiles, {s = ri;})); + static assert(!__traits(compiles, {ri = s;})); + + auto ri2 = ri; + assert(ri2.ptr == ri.ptr); + + const S cs3 = ri; + static assert(!__traits(compiles, {ri = cs3;})); + + immutable S si = ri; + // Rebind ri + ri = si; + ri = S(); + assert(ri.ptr is null); + + // Test RB!immutable -> RB!const + Rebindable!(const S) rc = ri; + assert(rc.ptr is null); + ri = S(new int); + rc = ri; + assert(rc.ptr !is null); + + // test rebindable, opAssign + rc.destroy; + assert(rc.ptr is null); + rc = rebindable(cs3); + rc = rebindable(si); + assert(rc.ptr !is null); + + ri.destroy; + assert(ri.ptr is null); + ri = rebindable(si); + assert(ri.ptr !is null); +} + +// Test Rebindable!mutable +@safe unittest +{ + static struct S + { + int* ptr; + } + S s; + + Rebindable!S rs = s; + static assert(is(typeof(rs) == S)); + rs = rebindable(S()); +} + +// Test disabled default ctor +unittest +{ + static struct ND + { + int i; + @disable this(); + this(int i) inout {this.i = i;} + } + static assert(!__traits(compiles, Rebindable!ND())); + + Rebindable!(const ND) rb = const ND(1); + assert(rb.i == 1); + rb = immutable ND(2); + assert(rb.i == 2); + rb = rebindable(const ND(3)); + assert(rb.i == 3); + static assert(!__traits(compiles, rb.i++)); +} + + /** Similar to `Rebindable!(T)` but strips all qualifiers from the reference as opposed to just constness / immutability. Primary intended use case is with From 93cbec3e2e953dce2d1f6f0e5f708f0d4cac1143 Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Wed, 7 Feb 2018 02:23:40 +0100 Subject: [PATCH 02/19] style fixups --- std/typecons.d | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 7a2b0b0507a..55b92fbc1d0 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2712,21 +2712,20 @@ if (is(S == struct)) } // must not escape when S is immutable - private - ref trustedPayload() @trusted + private ref trustedPayload() @trusted { - return *cast(S*)mutPayload.ptr; + return *cast(S*) mutPayload.ptr; } static if (!is(S == immutable)) - ref S Rebindable_getRef() @property + private ref S Rebindable_getRef() @property { // payload exposed as const ref when S is const return trustedPayload; } static if (is(S == immutable)) - S Rebindable_get() @property + private S Rebindable_get() @property { // we return a copy for immutable S return trustedPayload; @@ -2737,11 +2736,10 @@ if (is(S == struct)) else alias Rebindable_getRef this; - private - auto movePayload() @trusted + private auto movePayload() @trusted { import std.algorithm : move; - return cast(S)move(*cast(Unqual!S*)mutPayload.ptr); + return cast(S) move(*cast(Unqual!S*) mutPayload.ptr); } ~this() From a63b6280c11fc6dfb6b833cecaacade0178469a2 Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Wed, 7 Feb 2018 12:07:13 +0100 Subject: [PATCH 03/19] Add more test to Rebindable --- std/typecons.d | 104 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/std/typecons.d b/std/typecons.d index 55b92fbc1d0..0cb82b81c30 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2842,7 +2842,7 @@ if (is(S == struct)) } // Test disabled default ctor -unittest +@safe unittest { static struct ND { @@ -2861,6 +2861,108 @@ unittest static assert(!__traits(compiles, rb.i++)); } +@safe unittest +{ + int del; + int post; + struct S + { + int* ptr; + int level; + this(this) { + post++; + level++; + } + ~this() { + del++; + } + } + + // test const + { + Rebindable!(const S) rc = S(new int); + assert(post == 1); + assert(rc.level == 1); + assert(post == 1); + assert(rc.level == 1); // no copy is created + } + assert(post == 1); + assert(del == 2); + del = 0, post = 0; + + // immutable + { + // on every field access a temporary immutable copy gets created + Rebindable!(immutable S) ri = S(new int); + assert(post == 1); + assert(ri.level == 2); + assert(post == 2); + assert(ri.level == 2); // however it's a copy of the payload's level + } + assert(post == 3); + assert(del == 4); + del = 0, post = 0; + + { + // the initial value is copied and destructed + Rebindable!(const S) rc = S(new int); + assert(post == 1); + assert(del == 1); + assert(rc.level == 1); + + // on an assignment to another value is simply post-blitted + const S cs = rc; + assert(del == 1); + assert(post == 2); + assert(rc.level == 1); + assert(cs.level == 2); + + // however on an assignment, the old payload gets destructed + rc = cs; + assert(del == 2); + assert(post == 3); + assert(cs.level == 2); + assert(rc.level == 3); + } + assert(post == 3); + assert(del == 4); + del = 0, post = 0; + + { + // the initial value is copied and destructed + Rebindable!(immutable S) ri = S(new int); + assert(post == 1); + assert(del == 1); + // creates temporary (gets immediately destroyed), actual level is still 1 + assert(ri.level == 2); + assert(del == 2); + assert(post == 2); + + // on an assignment to another value is simply post-blitted + const S cs = ri; + assert(del == 2); + assert(post == 3); + // creates temporary (gets immediately destroyed), actual level is still 1 + assert(ri.level == 2); + assert(cs.level == 2); + assert(del == 3); + assert(post == 4); + + // however, on an assignment to Rebindable the old value gets destructed + auto ri2 = ri; + static assert(is(typeof(ri2) == Rebindable!(immutable S))); + static assert(TemplateOf!(typeof(ri2)).stringof == "Rebindable(S) if (is(S == struct))"); + assert(del == 3); + assert(post == 4); + assert(ri2.level == 2); // should be 3?? + assert(del == 4); + + // similarly an assignment + //ri = ri2; // fails to compile due to overload conflicts + } + assert(post == 5); + assert(del == 7); +} /** Similar to `Rebindable!(T)` but strips all qualifiers from the reference as From e2f11d9b4d8298b6ce96cf4184712c0948dff45f Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Mon, 26 Mar 2018 13:36:43 +0200 Subject: [PATCH 04/19] rename emplace -> emplacePayload --- std/typecons.d | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 0cb82b81c30..c5f035474bb 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2671,7 +2671,7 @@ if (is(S == struct)) // mutPayload's pointers must be treated as tail const void[S.sizeof] mutPayload; - void emplace(ref S s) + void emplacePayload(ref S s) { import std.conv : emplace; static if (__traits(compiles, () @safe {S tmp = s;})) @@ -2683,27 +2683,27 @@ if (is(S == struct)) public: this()(auto ref S s) { - emplace(s); + emplacePayload(s); } // immutable S cannot be passed to auto ref S above static if (!is(S == immutable)) this()(immutable S s) { - emplace(s); + emplacePayload(s); } void opAssign()(auto ref S s) { movePayload; - emplace(s); + emplacePayload(s); } static if (!is(S == immutable)) void opAssign()(immutable S s) { movePayload; - emplace(s); + emplacePayload(s); } void opAssign(Rebindable other) From a6e5fda4e110ba0636b8c03a04adf99e2862736f Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 29 Mar 2018 12:24:17 +0100 Subject: [PATCH 05/19] Remove `private` for alias this getters Otherwise alias this would not work outside std.typecons. --- std/typecons.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index c5f035474bb..72f8d5048f0 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2718,14 +2718,14 @@ if (is(S == struct)) } static if (!is(S == immutable)) - private ref S Rebindable_getRef() @property + ref S Rebindable_getRef() @property { // payload exposed as const ref when S is const return trustedPayload; } static if (is(S == immutable)) - private S Rebindable_get() @property + S Rebindable_get() @property { // we return a copy for immutable S return trustedPayload; From a5e35449f8784214ecc1177b1c67782b203c68a8 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 29 Mar 2018 12:24:43 +0100 Subject: [PATCH 06/19] Fix & improve docs Wording was backward: `const S = S` is fine, `S = const S` disallowed. --- std/typecons.d | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 72f8d5048f0..58cf311b689 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2652,10 +2652,13 @@ Rebindable!T rebindable(T)(Rebindable!T obj) /** Models safe reassignment of otherwise constant struct instances. * - * A struct with a field of reference type cannot be assigned to a constant - * struct of the same type. `Rebindable!(const S)` allows assignment to - * a `const S` while enforcing only constant access to its fields. + * A constant struct with a field of reference type cannot be assigned to a mutable + * struct of the same type. This protects the constant reference field from being + * mutably aliased, potentially allowing mutation of `immutable` data. However, the + * assignment could be safe if all reference fields are only exposed as `const`. * + * `Rebindable!(const S)` accepts assignment from + * a `const S` while enforcing only constant access to its fields. * `Rebindable!(immutable S)` does the same but field access may create a * temporary copy of `S` in order to enforce _true immutability. */ From 0422983e94d37069b997c0987cdf3de1d84baa80 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Mon, 23 Apr 2018 16:25:57 +0100 Subject: [PATCH 07/19] Add generic maxElement algorithm example --- std/typecons.d | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/std/typecons.d b/std/typecons.d index 58cf311b689..501606b73af 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2781,6 +2781,36 @@ if (is(S == struct)) assert(rs.ptr is null); } +/// Using Rebindable in a generic algorithm: +@safe unittest +{ + import std.range.primitives : front, popFront; + + // simple version of std.algorithm.searching.maxElement + typeof(R.init.front) maxElement(R)(R r) + { + auto max = rebindable(r.front); + r.popFront; + foreach (e; r) + if (e > max) + max = e; // Rebindable allows const-correct reassignment + return max; + } + struct S + { + char[] arr; + alias arr this; // for comparison + } + // can't convert to mutable + const S cs; + static assert(!__traits(compiles, { S s = cs; })); + + alias CS = const S; + CS[] arr = [CS("harp"), CS("apple"), CS("pot")]; + CS ms = maxElement(arr); + assert(ms.arr == "pot"); +} + // Test Rebindable!immutable @safe unittest { From 1a135dc954b6e4da892647ce5f01dd9a2a05d99a Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Wed, 11 Apr 2018 13:42:54 +0100 Subject: [PATCH 08/19] Copy for Rebindable!(const S) when S has head const fields Example of previous failing code: struct S { immutable int i; } Rebindable!(const S) r = const S(1); immutable int* p = &r.i; r = S(5); assert(*p == 1); //fails --- std/typecons.d | 79 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 501606b73af..62d705354e9 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2657,10 +2657,16 @@ Rebindable!T rebindable(T)(Rebindable!T obj) * mutably aliased, potentially allowing mutation of `immutable` data. However, the * assignment could be safe if all reference fields are only exposed as `const`. * - * `Rebindable!(const S)` accepts assignment from - * a `const S` while enforcing only constant access to its fields. - * `Rebindable!(immutable S)` does the same but field access may create a - * temporary copy of `S` in order to enforce _true immutability. + * `Rebindable!(const S)` accepts (re)assignment from + * a `const S` while enforcing only `const` access to its fields. + * It implicitly converts to `ref const S` when possible. Otherwise, + * a copy is made on each implicit conversion to `const S`. Copies must be made + * e.g. when the struct contains a head-immutable data field. + * + * `Rebindable!(immutable S)` always makes a copy on implicit conversion to + * `immutable S`. This is required to preserve `immutable`'s + * guarantees. A copy is currently always made for conversion to `const S` + * (due to `alias this` limitations). */ template Rebindable(S) if (is(S == struct)) @@ -2714,32 +2720,42 @@ if (is(S == struct)) this = other.trustedPayload; } - // must not escape when S is immutable - private ref trustedPayload() @trusted + import std.traits : Fields, isMutable; + private template isConst(T) + { + static if (is(T == struct) || is(T == union)) + enum isConst = !isMutable!T || haveConstHead!T; + else + enum isConst = !isMutable!T; + } + private enum bool haveConstHead(T) = anySatisfy!(isConst, Fields!T); + private enum bool unsafeRef = is(S == immutable) || haveConstHead!(Unqual!S); + + // must not escape when unsafeRef + private ref S trustedPayload() @trusted { return *cast(S*) mutPayload.ptr; } - static if (!is(S == immutable)) + // expose payload as const ref when members have no head const data + static if (!unsafeRef) ref S Rebindable_getRef() @property { - // payload exposed as const ref when S is const return trustedPayload; } - static if (is(S == immutable)) - S Rebindable_get() @property + static if (unsafeRef) + S Rebindable_getCopy() @property { - // we return a copy for immutable S return trustedPayload; } - static if (is(S == immutable)) - alias Rebindable_get this; + static if (unsafeRef) + alias Rebindable_getCopy this; else alias Rebindable_getRef this; - private auto movePayload() @trusted + private S movePayload() @trusted { import std.algorithm : move; return cast(S) move(*cast(Unqual!S*) mutPayload.ptr); @@ -2894,6 +2910,41 @@ if (is(S == struct)) static assert(!__traits(compiles, rb.i++)); } +// Test head const fields aren't exposed as ref const S +@safe unittest +{ + struct S(T) + { + T i; + } + auto r = rebindable(const S!(immutable int)()); + // check we can't reference `i`, as it's a field of a temporary S copy + // If r.i was an lvalue, it would break immutable when r was rebound + static assert(!__traits(compiles, {const ref get(){ return r.i; }})); + + // test when Rebindable!const should make a copy + @property rcs(T)(){ return rebindable(const S!T()); } + // values + static assert(!rcs!(int).unsafeRef); + static assert(rcs!(const int).unsafeRef); + static assert(rcs!(immutable int).unsafeRef); + // arrays + static assert(!rcs!(string).unsafeRef); + static assert(rcs!(const char[]).unsafeRef); + // class refs + static assert(!rcs!(Object).unsafeRef); + static assert(rcs!(const Object).unsafeRef); + // nested structs + static assert(!rcs!(S!int).unsafeRef); + static assert(!rcs!(S!string).unsafeRef); + static assert(!rcs!(S!Object).unsafeRef); + static assert(rcs!(const S!int).unsafeRef); + static assert(rcs!(const S!string).unsafeRef); + static assert(rcs!(S!(const Object)).unsafeRef); + static assert(rcs!(const S!Object).unsafeRef); +} + +// Test copying @safe unittest { int del; From 7ae52f028fa867764138725bcb63b220daba649e Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 09:39:44 +0100 Subject: [PATCH 09/19] Replace struct Rebindable with implementation based on https://github.com/FeepingCreature/rebindable. --- std/typecons.d | 205 ++++++++++++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 80 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 62d705354e9..09dcf5cdebe 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2650,121 +2650,77 @@ Rebindable!T rebindable(T)(Rebindable!T obj) assert(rebindable(pr3341_aa)[321] == 543); } -/** Models safe reassignment of otherwise constant struct instances. - * - * A constant struct with a field of reference type cannot be assigned to a mutable - * struct of the same type. This protects the constant reference field from being - * mutably aliased, potentially allowing mutation of `immutable` data. However, the - * assignment could be safe if all reference fields are only exposed as `const`. - * - * `Rebindable!(const S)` accepts (re)assignment from - * a `const S` while enforcing only `const` access to its fields. - * It implicitly converts to `ref const S` when possible. Otherwise, - * a copy is made on each implicit conversion to `const S`. Copies must be made - * e.g. when the struct contains a head-immutable data field. +/** + * Models safe reassignment of otherwise constant types. * - * `Rebindable!(immutable S)` always makes a copy on implicit conversion to - * `immutable S`. This is required to preserve `immutable`'s - * guarantees. A copy is currently always made for conversion to `const S` - * (due to `alias this` limitations). + * `Rebindable!S` always makes a copy. */ template Rebindable(S) -if (is(S == struct)) +if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativeArray!S) { - static if (isMutable!S) - alias Rebindable = S; - else struct Rebindable { private: // mutPayload's pointers must be treated as tail const - void[S.sizeof] mutPayload; + MutableImitation!S mutPayload; - void emplacePayload(ref S s) + void emplacePayload(S s) @trusted { import std.conv : emplace; - static if (__traits(compiles, () @safe {S tmp = s;})) - () @trusted {emplace!S(mutPayload, s);}(); - else - emplace!S(mutPayload, s); - } - public: - this()(auto ref S s) - { - emplacePayload(s); + // as MutableImitation won't call the destructor, deliberately leak a copy here: + static union DontCallDestructor + { + S value; + } + DontCallDestructor copy = DontCallDestructor(s); + mutPayload = *cast(MutableImitation!S*)© } - // immutable S cannot be passed to auto ref S above - static if (!is(S == immutable)) - this()(immutable S s) + void destroyPayload() { - emplacePayload(s); + import std.typecons : No; + + // work around reinterpreting cast being impossible in CTFE + if (__ctfe) + { + return; + } + + // call possible struct destructors + .destroy!(No.initialize)(*cast(S*) &mutPayload); } - void opAssign()(auto ref S s) + public: + this(S s) { - movePayload; emplacePayload(s); } - static if (!is(S == immutable)) - void opAssign()(immutable S s) + void opAssign(S s) { - movePayload; + destroyPayload; emplacePayload(s); } void opAssign(Rebindable other) { - this = other.trustedPayload; - } - - import std.traits : Fields, isMutable; - private template isConst(T) - { - static if (is(T == struct) || is(T == union)) - enum isConst = !isMutable!T || haveConstHead!T; - else - enum isConst = !isMutable!T; - } - private enum bool haveConstHead(T) = anySatisfy!(isConst, Fields!T); - private enum bool unsafeRef = is(S == immutable) || haveConstHead!(Unqual!S); - - // must not escape when unsafeRef - private ref S trustedPayload() @trusted - { - return *cast(S*) mutPayload.ptr; + this = other.get; } - // expose payload as const ref when members have no head const data - static if (!unsafeRef) - ref S Rebindable_getRef() @property - { - return trustedPayload; - } - - static if (unsafeRef) - S Rebindable_getCopy() @property + /** + * Get the value stored in the `Rebindable` explicitly. + */ + S get() @property { - return trustedPayload; + return *cast(S*) &mutPayload; } - static if (unsafeRef) - alias Rebindable_getCopy this; - else - alias Rebindable_getRef this; - - private S movePayload() @trusted - { - import std.algorithm : move; - return cast(S) move(*cast(Unqual!S*) mutPayload.ptr); - } + alias get this; ~this() { - // call destructor with proper constness - movePayload; + destroyPayload; } } } @@ -3048,6 +3004,95 @@ if (is(S == struct)) assert(del == 7); } +/** + * A type "like" T, but mutable. + * Used internally in Rebindable!struct. + */ +private template MutableImitation(T) +{ + alias MutableImitation = MutableImitationImpl!T; + + // sanity checks + static assert(T.sizeof == MutableImitation.sizeof, "sizeof sanity check violated"); + static assert(T.alignof == MutableImitation.alignof, "alignof sanity check violated"); + static assert(hasIndirections!T == hasIndirections!MutableImitation, "indirection sanity check violated"); +} + +private template MutableImitationImpl(T) +{ + static if (is(T == struct)) + { + static if (anyPairwiseEqual!(staticMap!(offsetOf, T.tupleof))) + { + // Struct with anonymous unions detected! + // Danger, danger! + alias MutableImitationImpl = MutableImitationFallback!T; + } + else + { + align(T.alignof) + struct MutableImitationImpl + { + staticMap!(MutableImitation, typeof(T.init.tupleof)) fields; + } + } + } + else static if (is(T == union) || isAssociativeArray!T) + { + alias MutableImitationImpl = MutableImitationFallback!T; + } + else static if (is(T == function) || is(T == enum) || __traits(isArithmetic, T) + || is(T : U*, U) || is(T : K[], K) || is(T == delegate)) + { + // types where Unqual strips head constness + alias MutableImitationImpl = Unqual!T; + } + else static if (is(T == class) || is(T == interface)) + { + alias MutableImitationImpl = void*; + } + else static if (is(T == U[size], U, size_t size)) + { + alias MutableImitationImpl = MutableImitation!U[size]; + } + else + { + static assert(false, "Unsupported type " ~ T.stringof); + } +} + +private template MutableImitationFallback(T) +{ + align(T.alignof) + private struct MutableImitationFallback + { + static if (hasIndirections!T) + { + void[T.sizeof] data; + } + else + { + // union of non-pointer types? + ubyte[T.sizeof] data; + } + } +} + +private enum offsetOf(alias member) = member.offsetof; + +// True if any adjacent members of T are equal. +private template anyPairwiseEqual(T...) +{ + static if (T.length == 0 || T.length == 1) + { + enum anyPairwiseEqual = false; + } + else + { + enum anyPairwiseEqual = T[0] == T[1] || anyPairwiseEqual!(T[1 .. $]); + } +} + /** Similar to `Rebindable!(T)` but strips all qualifiers from the reference as opposed to just constness / immutability. Primary intended use case is with From 8216facda513991f6be29412eca796e60cf221b8 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 10:21:59 +0100 Subject: [PATCH 10/19] Remove documentation comment that arrays are not supported. Any type is now supported. --- std/typecons.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/typecons.d b/std/typecons.d index 09dcf5cdebe..2882f85bda3 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2332,7 +2332,7 @@ break the soundness of D's type system and does not incur any of the risks usually associated with `cast`. Params: - T = An object, interface, array slice type, or associative array type. + T = Any type. */ template Rebindable(T) if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) From e2516f0983e1186c6d0dce82dc55a98ae8a749fe Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 10:22:02 +0100 Subject: [PATCH 11/19] Fix order: put rebindable after Rebindable!struct. --- std/typecons.d | 350 ++++++++++++++++++++++++------------------------- 1 file changed, 173 insertions(+), 177 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 2882f85bda3..bd02f3c02f4 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2473,183 +2473,6 @@ if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArr })); } -/** -Convenience function for creating a `Rebindable` using automatic type -inference. - -Params: - obj = A reference to an object, interface, associative array, or an array slice. - s = Struct instance. - -Returns: - A newly constructed `Rebindable` initialized with the given data. -*/ -Rebindable!T rebindable(T)(T obj) -if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) -{ - typeof(return) ret; - ret = obj; - return ret; -} - -/// ditto -Rebindable!S rebindable(S)(S s) -if (is(S == struct) && !isInstanceOf!(Rebindable, S)) -{ - // workaround for rebindableS = rebindable(s) - static if (isMutable!S) - return s; - else - return Rebindable!S(s); -} - -/// -@system unittest -{ - class C - { - int payload; - this(int p) { payload = p; } - } - const c = new C(1); - - auto c2 = c.rebindable; - assert(c2.payload == 1); - // passing Rebindable to rebindable - c2 = c2.rebindable; - - c2 = new C(2); - assert(c2.payload == 2); - - const c3 = c2.get; - assert(c3.payload == 2); -} - -/** -This function simply returns the `Rebindable` object passed in. It's useful -in generic programming cases when a given object may be either a regular -`class` or a `Rebindable`. - -Params: - obj = An instance of Rebindable!T. - -Returns: - `obj` without any modification. -*/ -Rebindable!T rebindable(T)(Rebindable!T obj) -{ - return obj; -} - -// TODO: remove me once the rebindable overloads have been joined -/// -@system unittest -{ - class C - { - int payload; - this(int p) { payload = p; } - } - const c = new C(1); - - auto c2 = c.rebindable; - assert(c2.payload == 1); - // passing Rebindable to rebindable - c2 = c2.rebindable; - assert(c2.payload == 1); -} - -@system unittest -{ - interface CI { int foo() const; } - class C : CI { - int foo() const { return 42; } - @property int bar() const { return 23; } - } - Rebindable!(C) obj0; - static assert(is(typeof(obj0) == C)); - - Rebindable!(const(C)) obj1; - static assert(is(typeof(obj1.get) == const(C)), typeof(obj1.get).stringof); - static assert(is(typeof(obj1.stripped) == C)); - obj1 = new C; - assert(obj1.get !is null); - obj1 = new const(C); - assert(obj1.get !is null); - - Rebindable!(immutable(C)) obj2; - static assert(is(typeof(obj2.get) == immutable(C))); - static assert(is(typeof(obj2.stripped) == C)); - obj2 = new immutable(C); - assert(obj1.get !is null); - - // test opDot - assert(obj2.foo() == 42); - assert(obj2.bar == 23); - - interface I { final int foo() const { return 42; } } - Rebindable!(I) obj3; - static assert(is(typeof(obj3) == I)); - - Rebindable!(const I) obj4; - static assert(is(typeof(obj4.get) == const I)); - static assert(is(typeof(obj4.stripped) == I)); - static assert(is(typeof(obj4.foo()) == int)); - obj4 = new class I {}; - - Rebindable!(immutable C) obj5i; - Rebindable!(const C) obj5c; - obj5c = obj5c; - obj5c = obj5i; - obj5i = obj5i; - static assert(!__traits(compiles, obj5i = obj5c)); - - // Test the convenience functions. - auto obj5convenience = rebindable(obj5i); - assert(obj5convenience is obj5i); - - auto obj6 = rebindable(new immutable(C)); - static assert(is(typeof(obj6) == Rebindable!(immutable C))); - assert(obj6.foo() == 42); - - auto obj7 = rebindable(new C); - CI interface1 = obj7; - auto interfaceRebind1 = rebindable(interface1); - assert(interfaceRebind1.foo() == 42); - - const interface2 = interface1; - auto interfaceRebind2 = rebindable(interface2); - assert(interfaceRebind2.foo() == 42); - - auto arr = [1,2,3,4,5]; - const arrConst = arr; - assert(rebindable(arr) == arr); - assert(rebindable(arrConst) == arr); - - // https://issues.dlang.org/show_bug.cgi?id=7654 - immutable(char[]) s7654; - Rebindable!(typeof(s7654)) r7654 = s7654; - - static foreach (T; AliasSeq!(char, wchar, char, int)) - { - static assert(is(Rebindable!(immutable(T[])) == immutable(T)[])); - static assert(is(Rebindable!(const(T[])) == const(T)[])); - static assert(is(Rebindable!(T[]) == T[])); - } - - // https://issues.dlang.org/show_bug.cgi?id=12046 - static assert(!__traits(compiles, Rebindable!(int[1]))); - static assert(!__traits(compiles, Rebindable!(const int[1]))); - - // Pull request 3341 - Rebindable!(immutable int[int]) pr3341 = [123:345]; - assert(pr3341[123] == 345); - immutable int[int] pr3341_aa = [321:543]; - pr3341 = pr3341_aa; - assert(pr3341[321] == 543); - assert(rebindable(pr3341_aa)[321] == 543); -} - /** * Models safe reassignment of otherwise constant types. * @@ -3004,6 +2827,179 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ assert(del == 7); } +/** +Convenience function for creating a `Rebindable` using automatic type +inference. + +Params: + obj = A reference to an object, interface, associative array, or an array slice. + s = Struct instance. + +Returns: + A newly constructed `Rebindable` initialized with the given data. +*/ +Rebindable!T rebindable(T)(T obj) +if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) +{ + typeof(return) ret; + ret = obj; + return ret; +} + +/// ditto +Rebindable!S rebindable(S)(S s) +if (is(S == struct) && !isInstanceOf!(Rebindable, S)) +{ + // workaround for rebindableS = rebindable(s) + static if (isMutable!S) + return s; + else + return Rebindable!S(s); +} + +/// +@system unittest +{ + class C + { + int payload; + this(int p) { payload = p; } + } + const c = new C(1); + + auto c2 = c.rebindable; + assert(c2.payload == 1); + // passing Rebindable to rebindable + c2 = c2.rebindable; + + c2 = new C(2); + assert(c2.payload == 2); + + const c3 = c2.get; + assert(c3.payload == 2); +} + +/** +This function simply returns the `Rebindable` object passed in. It's useful +in generic programming cases when a given object may be either a regular +`class` or a `Rebindable`. + +Params: + obj = An instance of Rebindable!T. + +Returns: + `obj` without any modification. +*/ +Rebindable!T rebindable(T)(Rebindable!T obj) +{ + return obj; +} + +// TODO: remove me once the rebindable overloads have been joined +/// +@system unittest +{ + class C + { + int payload; + this(int p) { payload = p; } + } + const c = new C(1); + + auto c2 = c.rebindable; + assert(c2.payload == 1); + // passing Rebindable to rebindable + c2 = c2.rebindable; + assert(c2.payload == 1); +} + +@system unittest +{ + interface CI { int foo() const; } + class C : CI { + int foo() const { return 42; } + @property int bar() const { return 23; } + } + Rebindable!(C) obj0; + static assert(is(typeof(obj0) == C)); + + Rebindable!(const(C)) obj1; + static assert(is(typeof(obj1.get) == const(C)), typeof(obj1.get).stringof); + static assert(is(typeof(obj1.stripped) == C)); + obj1 = new C; + assert(obj1.get !is null); + obj1 = new const(C); + assert(obj1.get !is null); + + Rebindable!(immutable(C)) obj2; + static assert(is(typeof(obj2.get) == immutable(C))); + static assert(is(typeof(obj2.stripped) == C)); + obj2 = new immutable(C); + assert(obj1.get !is null); + + // test opDot + assert(obj2.foo() == 42); + assert(obj2.bar == 23); + + interface I { final int foo() const { return 42; } } + Rebindable!(I) obj3; + static assert(is(typeof(obj3) == I)); + + Rebindable!(const I) obj4; + static assert(is(typeof(obj4.get) == const I)); + static assert(is(typeof(obj4.stripped) == I)); + static assert(is(typeof(obj4.foo()) == int)); + obj4 = new class I {}; + + Rebindable!(immutable C) obj5i; + Rebindable!(const C) obj5c; + obj5c = obj5c; + obj5c = obj5i; + obj5i = obj5i; + static assert(!__traits(compiles, obj5i = obj5c)); + + // Test the convenience functions. + auto obj5convenience = rebindable(obj5i); + assert(obj5convenience is obj5i); + + auto obj6 = rebindable(new immutable(C)); + static assert(is(typeof(obj6) == Rebindable!(immutable C))); + assert(obj6.foo() == 42); + + auto obj7 = rebindable(new C); + CI interface1 = obj7; + auto interfaceRebind1 = rebindable(interface1); + assert(interfaceRebind1.foo() == 42); + + const interface2 = interface1; + auto interfaceRebind2 = rebindable(interface2); + assert(interfaceRebind2.foo() == 42); + + auto arr = [1,2,3,4,5]; + const arrConst = arr; + assert(rebindable(arr) == arr); + assert(rebindable(arrConst) == arr); + + // https://issues.dlang.org/show_bug.cgi?id=7654 + immutable(char[]) s7654; + Rebindable!(typeof(s7654)) r7654 = s7654; + + static foreach (T; AliasSeq!(char, wchar, char, int)) + { + static assert(is(Rebindable!(immutable(T[])) == immutable(T)[])); + static assert(is(Rebindable!(const(T[])) == const(T)[])); + static assert(is(Rebindable!(T[]) == T[])); + } + + // Pull request 3341 + Rebindable!(immutable int[int]) pr3341 = [123:345]; + assert(pr3341[123] == 345); + immutable int[int] pr3341_aa = [321:543]; + pr3341 = pr3341_aa; + assert(pr3341[321] == 543); + assert(rebindable(pr3341_aa)[321] == 543); +} + /** * A type "like" T, but mutable. * Used internally in Rebindable!struct. From ace3f64d45317bfbacb27f28d6ca5380da6e5802 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 10:57:01 +0100 Subject: [PATCH 12/19] Remove some tests that I don't understand. --- std/typecons.d | 125 +------------------------------------------------ 1 file changed, 2 insertions(+), 123 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index bd02f3c02f4..c2232cbf343 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2655,20 +2655,6 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ assert(ri.ptr !is null); } -// Test Rebindable!mutable -@safe unittest -{ - static struct S - { - int* ptr; - } - S s; - - Rebindable!S rs = s; - static assert(is(typeof(rs) == S)); - rs = rebindable(S()); -} - // Test disabled default ctor @safe unittest { @@ -2689,40 +2675,6 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ static assert(!__traits(compiles, rb.i++)); } -// Test head const fields aren't exposed as ref const S -@safe unittest -{ - struct S(T) - { - T i; - } - auto r = rebindable(const S!(immutable int)()); - // check we can't reference `i`, as it's a field of a temporary S copy - // If r.i was an lvalue, it would break immutable when r was rebound - static assert(!__traits(compiles, {const ref get(){ return r.i; }})); - - // test when Rebindable!const should make a copy - @property rcs(T)(){ return rebindable(const S!T()); } - // values - static assert(!rcs!(int).unsafeRef); - static assert(rcs!(const int).unsafeRef); - static assert(rcs!(immutable int).unsafeRef); - // arrays - static assert(!rcs!(string).unsafeRef); - static assert(rcs!(const char[]).unsafeRef); - // class refs - static assert(!rcs!(Object).unsafeRef); - static assert(rcs!(const Object).unsafeRef); - // nested structs - static assert(!rcs!(S!int).unsafeRef); - static assert(!rcs!(S!string).unsafeRef); - static assert(!rcs!(S!Object).unsafeRef); - static assert(rcs!(const S!int).unsafeRef); - static assert(rcs!(const S!string).unsafeRef); - static assert(rcs!(S!(const Object)).unsafeRef); - static assert(rcs!(const S!Object).unsafeRef); -} - // Test copying @safe unittest { @@ -2741,9 +2693,9 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ } } - // test const + // test ref count { - Rebindable!(const S) rc = S(new int); + Rebindable!S rc = S(new int); assert(post == 1); assert(rc.level == 1); assert(post == 1); @@ -2752,79 +2704,6 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ assert(post == 1); assert(del == 2); del = 0, post = 0; - - // immutable - { - // on every field access a temporary immutable copy gets created - Rebindable!(immutable S) ri = S(new int); - assert(post == 1); - assert(ri.level == 2); - assert(post == 2); - assert(ri.level == 2); // however it's a copy of the payload's level - } - assert(post == 3); - assert(del == 4); - del = 0, post = 0; - - { - // the initial value is copied and destructed - Rebindable!(const S) rc = S(new int); - assert(post == 1); - assert(del == 1); - assert(rc.level == 1); - - // on an assignment to another value is simply post-blitted - const S cs = rc; - assert(del == 1); - assert(post == 2); - assert(rc.level == 1); - assert(cs.level == 2); - - // however on an assignment, the old payload gets destructed - rc = cs; - assert(del == 2); - assert(post == 3); - assert(cs.level == 2); - assert(rc.level == 3); - } - assert(post == 3); - assert(del == 4); - del = 0, post = 0; - - { - // the initial value is copied and destructed - Rebindable!(immutable S) ri = S(new int); - assert(post == 1); - assert(del == 1); - // creates temporary (gets immediately destroyed), actual level is still 1 - assert(ri.level == 2); - assert(del == 2); - assert(post == 2); - - // on an assignment to another value is simply post-blitted - const S cs = ri; - assert(del == 2); - assert(post == 3); - // creates temporary (gets immediately destroyed), actual level is still 1 - assert(ri.level == 2); - assert(cs.level == 2); - assert(del == 3); - assert(post == 4); - - // however, on an assignment to Rebindable the old value gets destructed - auto ri2 = ri; - static assert(is(typeof(ri2) == Rebindable!(immutable S))); - static assert(TemplateOf!(typeof(ri2)).stringof == "Rebindable(S) if (is(S == struct))"); - assert(del == 3); - assert(post == 4); - assert(ri2.level == 2); // should be 3?? - assert(del == 4); - - // similarly an assignment - //ri = ri2; // fails to compile due to overload conflicts - } - assert(post == 5); - assert(del == 7); } /** From 531ed07630037185793e477cf78820159e738053 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 11:08:09 +0100 Subject: [PATCH 13/19] Fix test failures. --- std/typecons.d | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index c2232cbf343..885a063583c 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2487,7 +2487,7 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ // mutPayload's pointers must be treated as tail const MutableImitation!S mutPayload; - void emplacePayload(S s) @trusted + void emplacePayload(this This)(S s) @trusted { import std.conv : emplace; @@ -2500,7 +2500,7 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ mutPayload = *cast(MutableImitation!S*)© } - void destroyPayload() + void destroyPayload(this This)() @trusted { import std.typecons : No; @@ -2515,18 +2515,24 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ } public: + + static if (!__traits(compiles, { S s; })) + { + @disable this(); + } + this(S s) { emplacePayload(s); } - void opAssign(S s) + void opAssign(this This)(S s) { destroyPayload; emplacePayload(s); } - void opAssign(Rebindable other) + void opAssign(this This)(Rebindable other) { this = other.get; } @@ -2534,7 +2540,7 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ /** * Get the value stored in the `Rebindable` explicitly. */ - S get() @property + S get(this This)() @property @trusted { return *cast(S*) &mutPayload; } @@ -2696,14 +2702,8 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ // test ref count { Rebindable!S rc = S(new int); - assert(post == 1); - assert(rc.level == 1); - assert(post == 1); - assert(rc.level == 1); // no copy is created - } - assert(post == 1); - assert(del == 2); - del = 0, post = 0; + } + assert(post == del - 1); } /** From dd9f383af55b76364560523e600f04c95c20281a Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 11:14:47 +0100 Subject: [PATCH 14/19] Combine `rebindable()` calls. Not 100% sure what's going on with the different ways of calling them. --- std/typecons.d | 44 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 885a063583c..1a8413e9ebb 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2711,29 +2711,23 @@ Convenience function for creating a `Rebindable` using automatic type inference. Params: - obj = A reference to an object, interface, associative array, or an array slice. - s = Struct instance. + value = Value to be stored in the `Rebindable`. Returns: A newly constructed `Rebindable` initialized with the given data. */ -Rebindable!T rebindable(T)(T obj) -if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) -{ - typeof(return) ret; - ret = obj; - return ret; -} - -/// ditto -Rebindable!S rebindable(S)(S s) -if (is(S == struct) && !isInstanceOf!(Rebindable, S)) +Rebindable!T rebindable(T)(T value) { - // workaround for rebindableS = rebindable(s) - static if (isMutable!S) - return s; + static if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) + { + Rebindable!T ret; + ret = value; + return ret; + } else - return Rebindable!S(s); + { + return Rebindable!T(value); + } } /// @@ -2758,22 +2752,6 @@ if (is(S == struct) && !isInstanceOf!(Rebindable, S)) assert(c3.payload == 2); } -/** -This function simply returns the `Rebindable` object passed in. It's useful -in generic programming cases when a given object may be either a regular -`class` or a `Rebindable`. - -Params: - obj = An instance of Rebindable!T. - -Returns: - `obj` without any modification. -*/ -Rebindable!T rebindable(T)(Rebindable!T obj) -{ - return obj; -} - // TODO: remove me once the rebindable overloads have been joined /// @system unittest From 30c3e265ea83b56384476bbfb8a1b8407589d00e Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 13:09:17 +0100 Subject: [PATCH 15/19] Add `std.typecons ManagedLifetime(T)`. `ManagedLifetime` allows manual, explicit control over when the copy constructor and destructor of a contained type are run, regardless of whether the passed type is mutable or immutable. --- std/typecons.d | 200 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 64 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 1a8413e9ebb..59dc50c6f5f 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2478,79 +2478,45 @@ if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArr * * `Rebindable!S` always makes a copy. */ -template Rebindable(S) +struct Rebindable(S) if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativeArray!S) { - struct Rebindable - { - private: - // mutPayload's pointers must be treated as tail const - MutableImitation!S mutPayload; - - void emplacePayload(this This)(S s) @trusted - { - import std.conv : emplace; - - // as MutableImitation won't call the destructor, deliberately leak a copy here: - static union DontCallDestructor - { - S value; - } - DontCallDestructor copy = DontCallDestructor(s); - mutPayload = *cast(MutableImitation!S*)© - } - - void destroyPayload(this This)() @trusted - { - import std.typecons : No; - - // work around reinterpreting cast being impossible in CTFE - if (__ctfe) - { - return; - } - - // call possible struct destructors - .destroy!(No.initialize)(*cast(S*) &mutPayload); - } - - public: +private: + ManagedLifetime!S payload; - static if (!__traits(compiles, { S s; })) - { - @disable this(); - } +public: + static if (!__traits(compiles, { S s; })) + { + @disable this(); + } - this(S s) - { - emplacePayload(s); - } + this(S s) + { + payload.set(s); + } - void opAssign(this This)(S s) - { - destroyPayload; - emplacePayload(s); - } + void opAssign(this This)(S s) + { + payload.clear; + payload.set(s); + } - void opAssign(this This)(Rebindable other) - { - this = other.get; - } + void opAssign(this This)(Rebindable other) + { + payload.clear; + payload.set(other.payload.get); + } - /** - * Get the value stored in the `Rebindable` explicitly. - */ - S get(this This)() @property @trusted - { - return *cast(S*) &mutPayload; - } + S get(this This)() @property + { + return payload.get; + } - alias get this; + alias get this; - ~this() - { - destroyPayload; - } + ~this() + { + payload.clear; } } @@ -2706,6 +2672,112 @@ if (!is(S == class) && !is(S == interface) && !isDynamicArray!S && !isAssociativ assert(post == del - 1); } +/** + * Permits explicit control of the lifetime of a contained `S`. + * + * This works regardless of the constness of `S`. + * + * The container starts out in an empty state. It may only be assigned to when empty, + * and only once; it must be cleared before the next assignment. + * If the container contains a value when its lifetime expires, the destructor of the + * contained value will not be run. + * + * It is up to you to manage the lifetime of the contained value explicitly! + */ +struct ManagedLifetime(T) +{ +private: + MutableImitation!T mutPayload; + +public: + /** + * Initializes the container with a value. + */ + this(T value) + { + set(value); + } + + /** + * Set the container to a value. + * The container must be empty. + */ + void set(this This)(T value) @trusted + { + // as `mutPayload` won't call the destructor, we deliberately leak a copy here: + static union DontCallDestructor + { + T value; + } + DontCallDestructor copy = DontCallDestructor(value); + mutPayload = *cast(MutableImitation!T*)© + } + + /** + * Destroy the value saved in the container. + * The container must be set to a value. + */ + void clear(this This)() @trusted + { + import std.typecons : No; + + // work around reinterpreting cast being impossible in CTFE + if (__ctfe) + { + return; + } + + static if (is(T == class) || is(T == interface)) + { + mutPayload = null; + } + else + { + // call possible struct destructors + .destroy!(No.initialize)(*cast(T*) &mutPayload); + } + } + + /** + * Return a copy of the stored value. + * The container must be set to a value. + */ + T get(this This)() @property @trusted + { + return *cast(T*) &mutPayload; + } +} + +/// +@safe unittest +{ + int del; + int post; + struct S + { + int* ptr; + this(this) { + post++; + } + ~this() { + del++; + } + } + + { + ManagedLifetime!S container; + + assert(post == 0 && del == 0); + + container.set(S(new int)); + assert(post == 1 && del == 1); + + container.clear; + assert(post == 1 && del == 2); + } + assert(post == 1 && del == 2); +} + /** Convenience function for creating a `Rebindable` using automatic type inference. From 3b032b5c494aa52539580413bfb7cd2ac63d4e31 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 13:18:02 +0100 Subject: [PATCH 16/19] Add convenience helper `ManagedLifetime.replace`. --- std/typecons.d | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/std/typecons.d b/std/typecons.d index 59dc50c6f5f..39cefb06792 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2738,6 +2738,16 @@ public: } } + /** + * Set the container to a value if there is already a value in it. + * Convenience shortcut for `clear; set(value);` + */ + void replace(this This)(T value) + { + clear; + set(value); + } + /** * Return a copy of the stored value. * The container must be set to a value. From 0bf8da6ec67771cf4401d097ee9212dc08f31d96 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 13:19:51 +0100 Subject: [PATCH 17/19] Fix comment: T, not S. --- std/typecons.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 39cefb06792..9eb09f11b6a 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2673,9 +2673,9 @@ public: } /** - * Permits explicit control of the lifetime of a contained `S`. + * Permits explicit control of the lifetime of a contained `T`. * - * This works regardless of the constness of `S`. + * This works regardless of the constness of `T`. * * The container starts out in an empty state. It may only be assigned to when empty, * and only once; it must be cleared before the next assignment. From 0f0e305f9c389c2e4e5dc17c665f55d867ca1831 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Wed, 1 Feb 2023 13:27:03 +0100 Subject: [PATCH 18/19] Re-add accidentally-removed `rebindable(Rebindable!T)` function. --- std/typecons.d | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/std/typecons.d b/std/typecons.d index 9eb09f11b6a..975cd8a8734 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2799,6 +2799,7 @@ Returns: A newly constructed `Rebindable` initialized with the given data. */ Rebindable!T rebindable(T)(T value) +if (!is(T == Rebindable!U, U)) { static if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) { @@ -2834,6 +2835,20 @@ Rebindable!T rebindable(T)(T value) assert(c3.payload == 2); } +/** +This function simply returns the `Rebindable` object passed in. It's useful +in generic programming cases when a given object may be either a regular +`class` or a `Rebindable`. +Params: + obj = An instance of Rebindable!T. +Returns: + `obj` without any modification. +*/ +Rebindable!T rebindable(T)(Rebindable!T obj) +{ + return obj; +} + // TODO: remove me once the rebindable overloads have been joined /// @system unittest From a4e8586453917fd6476d440f15941b803c2e4c6f Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Thu, 2 Feb 2023 11:48:06 +0100 Subject: [PATCH 19/19] Make ManagedLifetime not @trusted. --- std/typecons.d | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/std/typecons.d b/std/typecons.d index 975cd8a8734..ab80698589c 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2490,38 +2490,38 @@ public: @disable this(); } - this(S s) + this(S s) @trusted { payload.set(s); } - void opAssign(this This)(S s) + void opAssign(this This)(S s) @trusted { payload.clear; payload.set(s); } - void opAssign(this This)(Rebindable other) + void opAssign(this This)(Rebindable other) @trusted { payload.clear; payload.set(other.payload.get); } - S get(this This)() @property + S get(this This)() @property @trusted { return payload.get; } alias get this; - ~this() + ~this() @trusted { payload.clear; } } /// -@safe unittest +@system unittest { static struct S { @@ -2702,7 +2702,7 @@ public: * Set the container to a value. * The container must be empty. */ - void set(this This)(T value) @trusted + void set(this This)(T value) { // as `mutPayload` won't call the destructor, we deliberately leak a copy here: static union DontCallDestructor @@ -2717,7 +2717,7 @@ public: * Destroy the value saved in the container. * The container must be set to a value. */ - void clear(this This)() @trusted + void clear(this This)() { import std.typecons : No; @@ -2752,14 +2752,14 @@ public: * Return a copy of the stored value. * The container must be set to a value. */ - T get(this This)() @property @trusted + T get(this This)() @property { return *cast(T*) &mutPayload; } } /// -@safe unittest +@system unittest { int del; int post;