Jump to page: 1 2
Thread overview
“Out” parameters and destructors
Mar 14
Ogion
Mar 15
Ogi
Mar 15
Ogion
Mar 18
Ogion
Mar 18
matheus
Mar 18
Basile B.
March 14

Functions with out parameters simply initialize the parameter with .init, instead of properly destroying it.

import std.stdio;

int count;

struct S {
    bool initialized;
    this(int) {
        writeln("S()");
        initialized = true;
        count++;
    }
    ~this() {
        if (initialized) {
            writeln("~S()");
            count--;
        }
    }
}

void foo(out S s) {
    s = S(42);
}

void bar() {
    S s;
    foo(s);
    foo(s);
}

void main() {
    bar();
    writeln(count);
}

Output:

S()
S()
~S()
1

Looks like a serious oversight.

March 14
On 3/14/25 12:30 AM, Ogion wrote:
> Functions with `out` parameters simply initialize the parameter with
> `.init`, instead of properly destroying it.

It took me a while to understand the point. In other words:

    auto s = S(43);
    foo(s);

The destructor of 'S(43)' is never called. Although understandably undesirable, this is according to spec:

  https://dlang.org/spec/function.html#ref-params

"An out parameter [...] is initialized with x.init upon function invocation."

Perhaps the compiler should not allow using constructed objects as 'out' parameter arguments.

Ali

March 15

On Friday, 14 March 2025 at 16:54:10 UTC, Ali Çehreli wrote:

>

Although understandably undesirable, this is according to spec:

https://dlang.org/spec/function.html#ref-params

"An out parameter [...] is initialized with x.init upon function invocation."

I want to clarify if this is intentional or not, and what is the intention if it is. Currently, it turns out that the variable has to be destroyed before passing it as an “out” argument. It’s counter-intuitive, and it defeats the purpose of “out” parameters.

>

Perhaps the compiler should not allow using constructed objects as 'out' parameter arguments.

In this aspect, current implementation differs from spec, so it handles constructed objects correctly. The “out” parameter is not initialized with .init, it’s default-initialized:

void main() {
    import std.stdio;
    int x;
    struct S {
        void bumpX() { x++; }
    }
    void foo(out S s) {}
    S s = S.init;
    foo(s);
    s.bumpX();
    writeln(x);
}

If S would be initialized with .init, the hidden context pointer in the nested struct would be null, and calling to bumpX() would crash the program. But it doesn’t crash and prints: 1.

March 15

On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:
[...]

>
~this() {
    if (initialized) {
        writeln("~S()");

If one moves the line containing 'writeln´ one line up before the 'if´-condition, then one might be notified of all calls---and one might be able to recognize more than one call of the destructor.

[...]

>
s = S(42);

The docs at 15.20.2 look very similar to:

>

Struct assignment 't=s´ is defined to be semantically equivalent to:

>

t.opAssign(s);

If there is no 'opAssign´ in 'S´ the compiler might insert a default. If a default is inserted, does it suffice?

March 15

On Saturday, 15 March 2025 at 20:21:41 UTC, Manfred Nowak wrote:

>

If one moves the line containing 'writeln´ one line up before the 'if´-condition, then one might be notified of all calls---and one might be able to recognize more than one call of the destructor.

One who has eyes, let one see:

import std.stdio;
struct S {
    int x;
    this(int x) {
        this.x = x;
        writeln("S(", x, ")");
    }
    ~this() {
        writeln("~S(", x, ")");
    }
}

void foo(out S s) {}

void main() {
    S s = S(42);
    foo(s);
}

Output:

S(42)
~S(0)

Alas, the object instantiated with S(42) was not given the proper burial. The treacherous function replaced its body with an empty shell. Its restless spirit will wander the earth for all eternity.

March 16

On Saturday, 15 March 2025 at 23:39:36 UTC, Ogion wrote:
[...]

>

Its restless spirit will wander the earth for all eternity.

Clear proof that the 'opAsssign´, specified in the docs at 15.20, was not called---or the call of the '__dtor´ within that 'opAssign´ does not give any due respect to the specified '~this´.

-manfred

March 17

On Saturday, 15 March 2025 at 11:44:14 UTC, Ogi wrote:

>

In this aspect, current implementation differs from spec, so it handles constructed objects correctly. The “out” parameter is not initialized with .init, it’s default-initialized:

Thanks, PR:
https://github.com/dlang/dlang.org/pull/4190

March 17

On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:

>

Functions with out parameters simply initialize the parameter with .init, instead of properly destroying it.

See https://github.com/dlang/dmd/issues/18348.

I think the solution is to always call the destructor before calling the function. However that was not implemented because it would break code that uses S s = void;, as s might not be a destructible value. The = void case could perhaps be supported if the compiler was smart enough to detect simple cases where s wasn't constructed. In those cases calling the destructor could be omitted (the spec might need updating to allow that). Given that all this could still break existing code with non-simple control flow, it would probably need to be done in the next edition rather than current edition of the language.

March 18

On Monday, 17 March 2025 at 11:40:59 UTC, Nick Treleaven wrote:

>

See https://github.com/dlang/dmd/issues/18348.

I think the solution is to always call the destructor before calling the function. However that was not implemented because it would break code that uses S s = void;, as s might not be a destructible value.

Understandable.

I should note that spec never said that the parameter is expected to be uninitialized. However, the fact that some some code relies on this behavior can’t be ignored.

I don’t think that “out” parameters worth fixing though. They only exist because we don’t have first-class tuples. Once we sort this out, we’ll just deprecate the “out” parameters with all their warts.

March 18
On Monday, 17 March 2025 at 11:40:59 UTC, Nick Treleaven wrote:
> On Friday, 14 March 2025 at 07:30:14 UTC, Ogion wrote:
>> Functions with `out` parameters simply initialize the parameter with `.init`, instead of properly destroying it.
>
> See https://github.com/dlang/dmd/issues/18348.
>
> I think the solution is to always call the destructor before calling the function. However that was not implemented because it would break code that uses `S s = void;`, as `s` might not be a destructible value. The `= void` case could perhaps be supported if the compiler was smart enough to detect simple ...

Wondering if that's the reason why in C# you need to construct an object before passing to an out parameter.

Matheus.
« First   ‹ Prev
1 2