digitalmars.D - Temporaries / struct RAII patterns
Not sure how to name the problem, but here it is. I want to create a struct instance on a stack using a helper function, manipulate it Fluent-like style, then destructor be invoked, that will do something with its state. I do not want to use `Unique`, because that will allocate actual data on heap. And unique will have pointer to it. This is because one can do release and move on Unique. I do not need (or want) release or move. I want something that can be done on a stack, but can be manipulated and passed to functions, and destructor called once I either go out of scope, or it is not used at all, and expression / statement ends. ```d struct X { string message; string file; int line; string[string] kvs; ref X F(string fmt, Args...)(const Args args) { return this; ... } ref X KV(string k, string v) { kvs[k] = v; return this; } ~this() { DoSomething(&this, message, kvs); } } X Foo(const string message, string file = __FILE__, int line = __LINE__) { return X(message, file, line) } X Foo(string fmt, Args...)(lazy Args args, string file = __FILE__, int line = __LINE__) { return X(message, file, line).F!(fmt, Args)(args); } void main() { Foo("a"); Foo("b").kv("k1", "v1").kv("k2", "v2"); Foo!"c %d %s"(1, "ble").kv("k3", "v3"); } ``` I know I can do this: ```d void main() { { scope x = Foo("a"); } { scope x = Foo("b"); x.KV("k1", "v1"); x.KV("k2", "v2"); } } ``` But this is not going to scale (I will have many of these statements in each file and function), and now requires me to remember to put `scope` in front of each variable (`scope` constraint keyword on struct type itself is deprecated), which is error prone. How do I ensure that `X` is not copied (i.e. on return from `f`), or when doing `kvs` (and similar functions), and that `X` is allocated on the stack, and is destroyed properly. I also do want to be able to d the second form (with explicit scope and calls to `kv`, i.e. when doing `kv` calls in a loop from some other array or associative array). I cannot use `ref` return on the `Foo`, because it the thing I am returning either is not an lvalue, or any lvalue it could reference would be on stack of `Foo`, so it will be returning invalid reference after `Foo` returns. Cheers.
Oct 15 2023
Also I added ```d // Disables default construction disable this(); // Disable copy constructor disable this(ref X); ``` to `struct X`, and that triggers an error, because compiler is trying to call a copy constructor when doing return from `Foo`.
Oct 15 2023
So it looks like to ensure RVO (return value optimization), I need to write it like this: ```d X Foo(string fmt, Args...)(lazy Args args, string file = __FILE__, int line = __LINE__) { auto r = X(message, file, line); r.F!(fmt, Args)(args); return r; } ``` Instead of: ```d X Foo(string fmt, Args...)(lazy Args args, string file = __FILE__, int line = __LINE__) { return X(message, file, line).F!(fmt, Args)(args); } ``` Otherwise copy constructor is called. In this case I can emulate "move" semantic using copy constructor: ```d struct X { disable this(); this(ref X other) { message = other.message; file = other.file; line = other.line; kvs = other.kvs; // mark other moved out other.message = null; other.file = null; other.line = -1; other.kvs = null; } // ... ~this() { // Only do action if it was not moved out by "copy" constructor if (message !is null) { DoSomething(&this, message, kvs); } } } ``` But it is so so. I do not plan to expose `X` type directly, so nobody but the library should be creating it, but user can technically assign it to variables (there are reasons for this), for future manipulation.
Oct 16 2023