On Thursday, 25 June 2015 at 10:10:42 UTC, Jonathan M Davis wrote:
Whether a reference escapes is an orthogonal issue. The return attribute is for dealing with that. The function should be able to return by ref or not and still accept both rvalues and lvalues for its parameters. As long as the temporary variable created for holding an rvalue exists for the duration of the statement that the function call is in, it doesn't matter.

When I was talking about escaping references, I didn't mean returned references; I meant storing a pointer to a ref parameter somewhere outside (global/instance variable).

This snippet is a rough sketch for an approach based on `scope ref`, to prevent escaping rvalues:

<CODE>

struct S
{
    int a;
ref S chain() return { return this; } // returns a `scope ref` for rvalues, `ref` for lvalues
}

// pointers derived from `scope ref` params can safely be passed down to scope pointer params only void manipulateWithoutEscaping(scope ref S s) { manipulate(&s, S.sizeof); }

// doesn't escape any part of its `s` reference, hence `scope ref`
// analog to S.chain(), returns a `scope ref` for rvalues, `ref` for lvalues
ref S forward(return scope ref S s) { return s; }

// low-level manipulation functions not escaping any parts of their pointees void manipulate(scope void* ptr, size_t size) { ptr[0..size] = 0; } void manipulate(scope void[] data) { manipulate(data.ptr, data.length); }

struct State
{
S* s; // external ownership; should most likely actually be shared ownership
    void[] data; // ditto
    void initByEscaping(ref S s)
    {
        this.s = &s; // obvious
        data = (cast(void*)&s.a)[0 .. int.sizeof]; // less obvious
    }
    void manipulate() { if (s) manipulate(data); }
}

void foo()
{
// forward rvalue ref and invoke chain() on it twice (each call forwarding the rvalue ref)
    // rvalue is destructed at the end of the statement
    forward(S(1)).chain().chain();

// forward rvalue ref and let it be manipulated before destruction
    manipulateWithoutEscaping(forward(S(2)));

    // promote forwarded rvalue ref to lvalue
    S s3 = forward(S(3));
    // let the lvalue escape and manipulate it indirectly
    auto state = new State();
state.initByEscaping(forward(s3)); // requires lvalue `ref`, forward() returns that for lvalue arg
    state.manipulate();
// GC-managed `state` outlives `s3` => dangling pointers `state.s` and `state.data.ptr`!
}

</CODE>

The first observation is that a `scope ref` system would probably need to introduce scope pointers/slices too, to allow taking the address of `scope ref` params. There'd probably be quite a few `scope` usages, cluttering code and introducing additional complexity.

I do see a point in trying to let `ref` alone and the compiler work it out. After all, that scope system would only allow us to prevent ourselves from escaping rvalues (if we cared to type 'scope'). With escape analysis, the compiler could disallow attempts to bind rvalues to escapable references, by inferring scope-ness for ref and pointer params automatically. In the future, escaping params could be disallowed in @safe functions, and otherwise require an explicit `ref` keyword when supplying the lvalue argument, hinting at the potential for dangling pointers.

In the meantime, what about making all `ref` params accept rvalues via lowering (at least for @system)? As a first step until escape analysis is sufficiently implemented and the compiler will help us out with compile errors for escaping rvalues..

Reply via email to