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









Witold <witold.baryluk+dlang gmail.com> 