/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Lifetime management functions, types and related exceptions. * * Copyright: Eugene Wissner 2019-2025. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/lifetime.d, * tanya/memory/lifetime.d) */ module tanya.memory.lifetime; import tanya.memory.allocator; import tanya.meta.metafunction; import tanya.meta.trait; package(tanya) void destroyAllImpl(R, E)(R p) { static if (hasElaborateDestructor!E) { foreach (ref e; p) { destroy(e); } } } /** * Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the * given arguments. * * If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference * of type $(D_PARAM T), otherwise a pointer to the constructed object is * returned. * * If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer) * should be an instance of the outer class. * * $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If * $(D_PARAM T) isn't an aggregate type and doesn't have a constructor, * $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`, * `Args[0]` should be implicitly convertible to $(D_PARAM T) then. * * Params: * T = Constructed type. * U = Type of the outer class if $(D_PARAM T) is a nested class. * Args = Types of the constructor arguments if $(D_PARAM T) has a constructor * or the type of the initial value. * outer = Outer class instance if $(D_PARAM T) is a nested class. * args = Constructor arguments if $(D_PARAM T) has a constructor or the * initial value. * * Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory). * * Precondition: `memory.length == stateSize!T`. * Postcondition: $(D_PARAM memory) and the result point to the same memory. */ T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args) if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U)) in { assert(memory.length >= stateSize!T); } out (result) { assert(memory.ptr is (() @trusted => cast(void*) result)()); } do { import tanya.memory.op : copy; copy(typeid(T).initializer, memory); auto result = (() @trusted => cast(T) memory.ptr)(); result.outer = outer; static if (is(typeof(result.__ctor(args)))) { result.__ctor(args); } return result; } /// ditto T emplace(T, Args...)(void[] memory, auto ref Args args) if (is(T == class) && !isAbstractClass!T && !isInnerClass!T) in { assert(memory.length == stateSize!T); } out (result) { assert(memory.ptr is (() @trusted => cast(void*) result)()); } do { import tanya.memory.op : copy; copy(typeid(T).initializer, memory); auto result = (() @trusted => cast(T) memory.ptr)(); static if (is(typeof(result.__ctor(args)))) { result.__ctor(args); } return result; } /// @nogc nothrow pure @safe unittest { class C { int i = 5; class Inner { int i; this(int param) pure nothrow @safe @nogc { this.i = param; } } } ubyte[stateSize!C] memory1; ubyte[stateSize!(C.Inner)] memory2; auto c = emplace!C(memory1); assert(c.i == 5); auto inner = emplace!(C.Inner)(memory2, c, 8); assert(c.i == 5); assert(inner.i == 8); assert(inner.outer is c); } /// ditto T* emplace(T, Args...)(void[] memory, auto ref Args args) if (!isAggregateType!T && (Args.length <= 1)) in { assert(memory.length >= T.sizeof); } out (result) { assert(memory.ptr is result); } do { auto result = (() @trusted => cast(T*) memory.ptr)(); static if (Args.length == 1) { *result = T(args[0]); } else { *result = T.init; } return result; } private void initializeOne(T)(ref void[] memory, ref T* result) @trusted { import tanya.memory.op : copy, fill; static if (!hasElaborateAssign!T && isAssignable!T) { *result = T.init; } else static if (__VERSION__ >= 2083 // __traits(isZeroInit) available. && __traits(isZeroInit, T)) { memory.ptr[0 .. T.sizeof].fill!0; } else { static immutable T init = T.init; copy((&init)[0 .. 1], memory); } } /// ditto T* emplace(T, Args...)(void[] memory, auto ref Args args) if (!isPolymorphicType!T && isAggregateType!T) in { assert(memory.length >= T.sizeof); } out (result) { assert(memory.ptr is result); } do { auto result = (() @trusted => cast(T*) memory.ptr)(); static if (Args.length == 0) { static assert(is(typeof({ static T t; })), "Default constructor is disabled"); initializeOne(memory, result); } else static if (is(typeof(result.__ctor(args)))) { initializeOne(memory, result); result.__ctor(args); } else static if (Args.length == 1 && is(typeof({ T t = args[0]; }))) { import tanya.memory.op : copy; ((ref arg) @trusted => copy((cast(void*) &arg)[0 .. T.sizeof], memory))(args[0]); static if (hasElaborateCopyConstructor!T) { result.__postblit(); } } else static if (is(typeof({ T t = T(args); }))) { auto init = T(args); (() @trusted => moveEmplace(init, *result))(); } else { static assert(false, "Unable to construct value with the given arguments"); } return result; } /// @nogc nothrow pure @safe unittest { ubyte[4] memory; auto i = emplace!int(memory); static assert(is(typeof(i) == int*)); assert(*i == 0); i = emplace!int(memory, 5); assert(*i == 5); static struct S { int i; @disable this(); @disable this(this); this(int i) @nogc nothrow pure @safe { this.i = i; } } auto s = emplace!S(memory, 8); static assert(is(typeof(s) == S*)); assert(s.i == 8); } private void deinitialize(bool zero, T)(ref T value) { static if (is(T == U[S], U, size_t S)) { foreach (ref e; value) { deinitialize!zero(e); } } else { import tanya.memory.op : copy, fill; static if (isNested!T) { // Don't override the context pointer. enum size_t size = T.sizeof - (void*).sizeof; } else { enum size_t size = T.sizeof; } static if (zero) { fill!0((cast(void*) &value)[0 .. size]); } else { copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]); } } } /** * Moves $(D_PARAM source) into $(D_PARAM target) assuming that * $(D_PARAM target) isn't initialized. * * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places * the $(D_PARAM source) into a valid but unspecified state, which means that * after moving $(D_PARAM source) can be destroyed or assigned a new value, but * accessing it yields an unspecified value. No postblits or destructors are * called. If the $(D_PARAM target) should be destroyed before, use * $(D_PSYMBOL move). * * $(D_PARAM source) and $(D_PARAM target) must be different objects. * * Params: * T = Object type. * source = Source object. * target = Target object. * * See_Also: $(D_PSYMBOL move), * $(D_PSYMBOL hasElaborateCopyConstructor), * $(D_PSYMBOL hasElaborateDestructor). * * Precondition: `&source !is &target`. */ void moveEmplace(T)(ref T source, ref T target) @system in { assert(&source !is &target, "Source and target must be different"); } do { static if (is(T == struct) || isStaticArray!T) { import tanya.memory.op : copy; copy((&source)[0 .. 1], (&target)[0 .. 1]); static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T) { static if (__VERSION__ >= 2083) // __traits(isZeroInit) available. { deinitialize!(__traits(isZeroInit, T))(source); } else { if (typeid(T).initializer().ptr is null) { deinitialize!true(source); } else { deinitialize!false(source); } } } } else { target = source; } } /// @nogc nothrow pure @system unittest { static struct S { int member = 5; this(this) @nogc nothrow pure @safe { assert(false); } } S source, target = void; moveEmplace(source, target); assert(target.member == 5); int x1 = 5, x2; moveEmplace(x1, x2); assert(x2 == 5); } /** * Moves $(D_PARAM source) into $(D_PARAM target) assuming that * $(D_PARAM target) isn't initialized. * * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places * the $(D_PARAM source) into a valid but unspecified state, which means that * after moving $(D_PARAM source) can be destroyed or assigned a new value, but * accessing it yields an unspecified value. $(D_PARAM target) is destroyed before * the new value is assigned. If $(D_PARAM target) isn't initialized and * therefore shouldn't be destroyed, $(D_PSYMBOL moveEmplace) can be used. * * If $(D_PARAM target) isn't specified, $(D_PSYMBOL move) returns the source * as rvalue without calling its copy constructor or destructor. * * $(D_PARAM source) and $(D_PARAM target) are the same object, * $(D_PSYMBOL move) does nothing. * * Params: * T = Object type. * source = Source object. * target = Target object. * * See_Also: $(D_PSYMBOL moveEmplace). */ void move(T)(ref T source, ref T target) { if ((() @trusted => &source is &target)()) { return; } static if (hasElaborateDestructor!T) { target.__xdtor(); } (() @trusted => moveEmplace(source, target))(); } /// ditto T move(T)(ref T source) @trusted { static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T) { T target = void; moveEmplace(source, target); return target; } else { return source; } } /// @nogc nothrow pure @safe unittest { static struct S { int member = 5; this(this) @nogc nothrow pure @safe { assert(false); } } S source, target = void; move(source, target); assert(target.member == 5); assert(move(target).member == 5); int x1 = 5, x2; move(x1, x2); assert(x2 == 5); assert(move(x2) == 5); } /** * Exchanges the values of $(D_PARAM a) and $(D_PARAM b). * * $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b) * without calling its postblits or destructors. * * Params: * a = The first object. * b = The second object. */ void swap(T)(ref T a, ref T b) @trusted { T tmp = void; moveEmplace(a, tmp); moveEmplace(b, a); moveEmplace(tmp, b); } /// @nogc nothrow pure @safe unittest { int a = 3, b = 5; swap(a, b); assert(a == 5); assert(b == 3); } /** * Forwards its argument list preserving $(D_KEYWORD ref) and $(D_KEYWORD out) * storage classes. * * $(D_PSYMBOL forward) accepts a list of variables or literals. It returns an * argument list of the same length that can be for example passed to a * function accepting the arguments of this type. * * Params: * args = Argument list. * * Returns: $(D_PARAM args) with their original storage classes. */ template forward(args...) { static if (args.length == 0) { alias forward = AliasSeq!(); } else static if (__traits(isRef, args[0]) || __traits(isOut, args[0])) { static if (args.length == 1) { alias forward = args[0]; } else { alias forward = AliasSeq!(args[0], forward!(args[1 .. $])); } } else { @property auto forwardOne() { return move(args[0]); } static if (args.length == 1) { alias forward = forwardOne; } else { alias forward = AliasSeq!(forwardOne, forward!(args[1 .. $])); } } } /// @nogc nothrow pure @safe unittest { static assert(is(typeof((int i) { int v = forward!i; }))); static assert(is(typeof((ref int i) { int v = forward!i; }))); static assert(is(typeof({ void f(int i, ref int j, out int k) { f(forward!(i, j, k)); } }))); }