Thread overview
`return ref`, DIP25, and struct/class lifetimes
May 16, 2016
Dicebot
May 16, 2016
Dicebot
May 16, 2016
Hello all,

Consider a struct that wraps a pointer to some other piece of data as follows:

    struct MyWrapper(T)
    {
        private T* data;

        public this(ref T input)
        {
            this.data = &input;
        }

        ... other methods that use `this.data` ...
    }

Is there any way to guarantee at compile time that the input data will outlive the wrapper struct?

I'd assumed (dangerous thing to do...) that DIP25 would allow this to be guaranteed by `return ref`, but compiling/running the following program, with or without the --dip25 flag, would appear to suggest otherwise:

////////////////////////////////////////////////////////////

struct MyWrapper(T)
{
    private T* data;

    public this(return ref T input)
    {
        this.data = &input;
    }

    public T get() return
    {
        return *(this.data);
    }

    invariant()
    {
        assert(this.data !is null);
    }
}

auto badWrapper()
{
    double x = 5.0;
    return MyWrapper!double(x);
}

void main()
{
    import std.stdio;
    auto badWrap = badWrapper();
    writeln(badWrap.get());
}

////////////////////////////////////////////////////////////

Is there any current way to achieve what I'm looking for here, or is this all on a hiding to nothing? :-(

N.B. for motivation behind this request, see:
https://github.com/WebDrake/dxorshift/pull/1
May 16, 2016
tl; dr: DIP25 is so heavily under-implemented in its current shape it can be considered 100% broken and experimenting will uncover even more glaring holes.

To be more precise, judging by experimental observation, currently dip25 only works when there is explicitly a `ref` return value in function or method. Any escaping of reference into a pointer confuses it completely:

ref int wrap ( return ref int input )
{
    return input;
}

ref int badWrapper()
{
    int x = 5;
    return wrap(x); // Error: escaping reference to local variable x
}

void main()
{
    auto badWrap = badWrapper();
}

vs

struct S
{
    int* ptr;
}

S wrap ( return ref int input )
{
    return S(&input);
}

S badWrapper()
{
    int x = 5;
    return wrap(x); // OK!
}

void main()
{
    auto badWrap = badWrapper();
}

vs

struct S
{
    int* ptr;
}

ref S wrap ( return ref int input )
{
    static S tmp; // spot another hole :)
    tmp = S(&input);
    return tmp;
}

ref S badWrapper()
{
    int x = 5;
    return wrap(x); // Error: escaping reference to local variable x
}

void main()
{
    auto badWrap = badWrapper();
}

You can probably spot the pattern here - compiler matches `return ref` in parameter declaration to `ref` in return value and enforces identical lifetime for those. No `ref` in return value - no enforcing, but it will also happily accept nonsense `return ref` annotations.

Theoretically it was supposed to be protected by `@safe` attribute as one can't take address of local variable in safe code. But there isn't any way to write such wrapper struct without using pointers AFAIK.

In your actual example putting `return` on `get` method annotation is additionally very misleading because it only implies ensuring result does not outlive struct instance itself - but it gets passed around by value anyway.
May 16, 2016
On Monday, 16 May 2016 at 15:33:09 UTC, Dicebot wrote:
> tl; dr: DIP25 is so heavily under-implemented in its current shape it can be considered 100% broken and experimenting will uncover even more glaring holes.

Well, it's always fun to find the holes in things ... :-)

> To be more precise, judging by experimental observation, currently dip25 only works when there is explicitly a `ref` return value in function or method. Any escaping of reference into a pointer confuses it completely:

To be fair, this is all in line with the DIP25 spec that I re-read after running into these issues with my wrapper struct.  AFAICS pretty much the only case where it really relates to structs is when a struct method is returning a reference to an internal variable.

It's just frustrating there _isn't_ any thought for the kind of wrapper I have in mind, because as you say,

> But there isn't any way to write such wrapper struct without using pointers AFAIK.

As for your point:

> In your actual example putting `return` on `get` method annotation is additionally very misleading because it only implies ensuring result does not outlive struct instance itself - but it gets passed around by value anyway.

I thought much the same, but thought I'd try it on the off chance it would make a difference to detection of the problem.

Worst part of all this is that even an invariant to assert(this.data !is null) won't protect against issues: the pointer doesn't get reset to 0 after the data it points to goes out of scope, it just now points to potentially garbage data.

In fact, it's only with compiler optimizations enabled that the example I posted even generates the wrong result in its `writeln()` call :-P

Basically, it sounds to me like there _is_ no way to guarantee the safety/validity of wrapping data via pointer in this way ... ? :-(
May 16, 2016
There is also another counter-obvious bit regarding current implementation - it only tracks lifetime of actual references. Check this example:

ref int wrap ( return ref int input )
{
    return input;
}

int badWrapper()
{
    int z;
    {
        int x = 42;
        z = wrap(x);
    }
    return z;
}


it looks obvious that this compiles OK with dip25 check because once value assignment happens, there is no more reference to track. However it is very common to expect different semantics if return type contains reference types - probably because it would be very useful and because Rust has changed expectations of what lifetime control can do :) And yet it will still work exactly the same with no warnings from compiler, creating false sense of correctness.