Thread overview
D needs first-class lifetimes before it can get ownership and borrowing
Aug 10, 2019
Olivier FAURE
Aug 10, 2019
Dennis
Aug 10, 2019
Dennis
Aug 10, 2019
Mike Franklin
Aug 10, 2019
Olivier FAURE
Aug 10, 2019
Nick Treleaven
Aug 10, 2019
a11e99z
Aug 11, 2019
Nick Treleaven
August 10, 2019
I've been trying to nail down the semantics of my in-progress DIP for a `unique` qualifier, and I couldn't come up with coherent rules for double-indirection.

To give you an shortened example of the problem, contemplate this:

    int* gptr;

    struct SmartPtr {
        int* ptr;

    @safe:
        ~this() { reset(); }
    @trusted:
        void init() { ptr = cast(int*)malloc(int.sizeof); *ptr = 0; }
        void reset() { free(ptr); ptr = null; }

    @safe:
        ref int get() return { assert(ptr); return *ptr; }

        void borrow() { gptr = this.ptr; }
    }

    @safe
    void main() {
        SmartPtr s;
        s.init();
        s.borrow();
        s.reset();
        *gptr = 1; 		// Memory corruption
    }

The problem above is that, under DIP-1000, the compiler assumes `borrow()` doesn't escape any data, because it takes `this` by ref; and yet it escapes this.ptr, because there's no way to mark this.ptr as scope. As long as borrow() is @safe, there's no way reset() can be @trusted.

To my knowledge, the proposed @live semantics don't solve this problem (because they would also assume that `borrow()` doesn't escape data); so I think it's necessary to have lifetime annotations inside structs before any ownership-borrowing scheme can be achieved.
August 10, 2019
On Saturday, 10 August 2019 at 13:30:17 UTC, Olivier FAURE wrote:
> The problem above is that, under DIP-1000, the compiler assumes `borrow()` doesn't escape any data, because it takes `this` by ref; and yet it escapes this.ptr, because there's no way to mark this.ptr as scope.

If I change it to:
```
void borrow() scope { gptr = this.ptr; }
```

I get:
```
Error: scope variable this assigned to non-scope gptr
```

I don't know why it compiles without scope though.
August 10, 2019
On Saturday, 10 August 2019 at 16:48:26 UTC, Dennis wrote:
> I don't know why it compiles without scope though.

I think it's because you didn't declare it as `scope SmartPtr s;` and `reset` is wrongly @trusted.
August 10, 2019
On Saturday, 10 August 2019 at 13:30:17 UTC, Olivier FAURE wrote:
> I've been trying to nail down the semantics of my in-progress DIP for a `unique` qualifier, and I couldn't come up with coherent rules for double-indirection.
>
> To give you an shortened example of the problem, contemplate this:
>
>     int* gptr;
>
>     struct SmartPtr {
>         int* ptr;
>
>     @safe:
>         ~this() { reset(); }
>     @trusted:
>         void init() { ptr = cast(int*)malloc(int.sizeof); *ptr = 0; }
>         void reset() { free(ptr); ptr = null; }
>
>     @safe:
>         ref int get() return { assert(ptr); return *ptr; }
>
>         void borrow() { gptr = this.ptr; }
>     }
>
>     @safe
>     void main() {
>         SmartPtr s;
>         s.init();
>         s.borrow();
>         s.reset();
>         *gptr = 1; 		// Memory corruption
>     }
>
> The problem above is that, under DIP-1000, the compiler assumes `borrow()` doesn't escape any data, because it takes `this` by ref; and yet it escapes this.ptr, because there's no way to mark this.ptr as scope.

We could allow scope fields:

struct SmartPtr {
  scope int* ptr

> As long as borrow() is @safe, there's no way reset() can be @trusted.

That use of @trusted requires @safe code inside the struct (and in the defining module due to field access) to form part of the @trusted contract.

scope fields would lessen the burden of @trusted and allow encapsulated pointers. But there still won't be an error if you don't use scope for the ptr field.
August 10, 2019
On Saturday, 10 August 2019 at 17:38:43 UTC, Nick Treleaven wrote:
> On Saturday, 10 August 2019 at 13:30:17 UTC, Olivier FAURE
>
> struct SmartPtr {
>   scope int* ptr
>

should there be an opportunity to transfer ownership?
SmartPtr {
  int* transfer() [@forget, @transfer, @orsomethingelse] { return ptr; /*ptr = null;*/ }
}

and then need attr @nodiscard with error when returned value is discarded cuz memory/handle leak can be.
August 10, 2019
On Saturday, 10 August 2019 at 16:48:26 UTC, Dennis wrote:
> If I change it to:
> ```
> void borrow() scope { gptr = this.ptr; }
> ```
>
> I get:
> ```
> Error: scope variable this assigned to non-scope gptr
> ```

... Huh.

Okay, this is... interesting. I'm not finding obvious holes either (though I wonder if you can game it by having a SmartPtr!SmartPtr). Too bad the semantics for combining ref and scope in dip1000 aren't documented.

> I don't know why it compiles without scope though.

Because without scope, borrow takes ref this, and with scope, it takes ref scope this.

Similarly, foo(ref scope int*) and foo(scope int**) have different semantics.

I need to tweak with this. My understanding is that ref scope is a special case where the language enforces lifetime on two levels instead of the default one, so this might be enough to cobble together a working OB system.
August 10, 2019
> I think it's because you didn't declare it as `scope SmartPtr s;` and `reset` is wrongly @trusted.

`SmartPtr` is a struct so it's implicitly `scope`.  Also, I think the `@trusted` annotation is only to allow the usage of `malloc` and `free` which aren't safe.  I think for this illustration, the `@trusted` annotation is OK.

Mike
August 11, 2019
On Saturday, 10 August 2019 at 18:11:18 UTC, a11e99z wrote:
>
> should there be an opportunity to transfer ownership?
> SmartPtr {
>   int* transfer() [@forget, @transfer, @orsomethingelse] { return ptr; /*ptr = null;*/ }
> }

It would just use a trusted move constructor (or copy constructor if doing ref counting).