November 13, 2019
On Sunday, 27 October 2019 at 22:36:30 UTC, Timon Gehr wrote:
> I finally got around to writing up some thoughts on @safe borrowing and ownership in D.

Thanks! I'd been wanting to understand your thoughts on this for a while. A few questions:

> ref implies scope

Is that true now??

> `scope` should apply to all types of data equally

And how would the application of `scope` to, say, `int` affect it? What would the compiler do with that?

> Non-immutable non-scope values may not be assigned to `scope` values
> A non-`scope` pointer cannot be dereferenced if that would yield a `scope` value
> `scope` has to be a type constructor.

Could you please expand on the rationale for these rules?

Other than that, I wonder about the teachability of these rules. I had to read the proposal several times myself.

November 13, 2019
On 13.11.19 09:08, Atila Neves wrote:
> On Sunday, 27 October 2019 at 22:36:30 UTC, Timon Gehr wrote:
>> I finally got around to writing up some thoughts on @safe borrowing and ownership in D.
> 
> Thanks! I'd been wanting to understand your thoughts on this for a while. A few questions:
> 
>> ref implies scope
> 
> Is that true now??
> ...

Yes. (What I mean is that it applies to the implicit pointer that is generated, not the value itself.)

For example (compiled with -dip1000 switch):

int* p;
void foo(ref int x)@safe{
    x=2;
    p=&x; // error
}

void main()@safe{
    int x;
    foo(x);
}

error: address of variable `x` assigned to `p` with longer lifetime

>> `scope` should apply to all types of data equally
> 
> And how would the application of `scope` to, say, `int` affect it? What would the compiler do with that?
> ...

The same things it would do with other types. You can't escape the value or make non-borrowed copies. It's unlikely to have useful applications for a plain integer, but you could have a `struct Handle{ private int handle; ... }` that carries the invariant that the handle is valid.

Then you can do things like:

void workWithHandle(scope Handle handle)@safe{ ... }

void unsafe()@trusted{
    auto handle=getHandle();
    workWithHandle(handle);
    disposeHandle(handle);
}

If `scope` was restricted to pointers, you would have to pass pointers to handles in order to get any of that type checking. (IIRC, this complaint was on this newsgroup not too long ago.) In general, it would make the rules less useful for @trusted libraries that need to restrict access patterns from @safe code.


> Could you please expand on the rationale for these rules?
> ...

As noted, with DIP 1021 accepted, it's pretty clear that mutable `scope` pointers won't be able to alias (as that DIP attempts to restrict aliasing of `ref` parameters). I would prefer to keep aliasing and lifetime restrictions separate (because you can always assign something with higher lifetime to something with smaller lifetime, but aliasing restrictions are incompatible both ways), but it's unlikely to happen.

Note that Walter's vision for @live is to restrict aliasing in this way for _all_ pointers.

>> Non-immutable non-scope values may not be assigned to `scope` values

Otherwise you could very easily introduce aliasing between scoped pointers:

void foo(int* p)@safe{
    scope int* q=p;
    scope int* r=p;
    assert(q !is r); // fail
}

The type system is supposed to guarantee that if this assertion compiles, it passes.

>> A non-`scope` pointer cannot be dereferenced if that would yield a `scope` value

A non-scope pointer doesn't satisfy any aliasing restrictions, hence if you dereference it and get a `scope` value, you could have multiple paths to access a single `scope` value, which has to be ruled out.

void foo(scope(int*)* p, scope(int*)* q)@safe{
    // p and q could alias
    scope int* r=*p;
    scope int* s=*q;
    assert(r !is s); // can fail
}

>> `scope` has to be a type constructor.
> 

One example:

import std.typecons;
int* y;
int* foo(){
    int x;
    auto t=tuple(&x,y); // type has to be Tuple!(scope(int*),int*)
    return t[1];
}

(Another key use case for type constructors is to distinguish the return value and the receiver, as in `const(int*) foo()const`;, but for `scope`, we can actually write `int* foo()scope return` for this purpose.)

> Other than that, I wonder about the teachability of these rules.

The rules follow from the simple principles lined out at the beginning of the post, so that should not be a problem. Note that my post was pretty condensed. Unfortunately, I don't have enough spare time at the moment.

> I had to read the proposal several times myself.
> 

Thanks for taking the time.
November 13, 2019
On Wednesday, 13 November 2019 at 09:52:24 UTC, Timon Gehr wrote:
> On 13.11.19 09:08, Atila Neves wrote:
>> And how would the application of `scope` to, say, `int` affect it? What would the compiler do with that?
>> ...
>
> The same things it would do with other types. You can't escape the value or make non-borrowed copies. It's unlikely to have useful applications for a plain integer, but you could have a `struct Handle{ private int handle; ... }` that carries the invariant that the handle is valid.
>
> Then you can do things like:
>
> void workWithHandle(scope Handle handle)@safe{ ... }
>
> void unsafe()@trusted{
>     auto handle=getHandle();
>     workWithHandle(handle);
>     disposeHandle(handle);
> }
>
> If `scope` was restricted to pointers, you would have to pass pointers to handles in order to get any of that type checking. (IIRC, this complaint was on this newsgroup not too long ago.) In general, it would make the rules less useful for @trusted libraries that need to restrict access patterns from @safe code.

Yes, yes, yes.

It would happen anytime you refer to an external resource by index that you need to release after use. (e.g. a DB cursor index).

I currently use this strategy in spasm to release objects held on the JS side. The object is released automatically whenever the handle goes out of scope.

The benefit is that you don't have to do reference counting, but you still can wrap it in one if you need to.
1 2 3
Next ›   Last »