March 01, 2015
On Sunday, 1 March 2015 at 14:40:54 UTC, Marc Schütz wrote:
> I don't think a callee-based solution can work:
>
>     class T {
>         void doSomething() scope;
>     }
>     struct S {
>         RC!T t;
>     }
>     void main() {
>         auto s = S(RC!T()); // `s.t`'s refcount is 1
>         T t = s.t;          // borrowing from the RC wrapper
>         foo(s);
>         t.doSomething();    // oops, `t` is gone
>     }
>     void foo(ref S s) {
>         s.t = RC!T();       // drops the old `s.t`
>     }

I thought of this, and I disagree. The very fact of assigning to `T t` adds the reference count you need to keep `s.t` from disintegrating. As soon as you borrow, you increment the count.
March 02, 2015
On Sunday, 1 March 2015 at 23:56:02 UTC, Zach the Mystic wrote:
> On Sunday, 1 March 2015 at 14:40:54 UTC, Marc Schütz wrote:
>> I don't think a callee-based solution can work:
>>
>>    class T {
>>        void doSomething() scope;
>>    }
>>    struct S {
>>        RC!T t;
>>    }
>>    void main() {
>>        auto s = S(RC!T()); // `s.t`'s refcount is 1
>>        T t = s.t;          // borrowing from the RC wrapper
>>        foo(s);
>>        t.doSomething();    // oops, `t` is gone
>>    }
>>    void foo(ref S s) {
>>        s.t = RC!T();       // drops the old `s.t`
>>    }
>
> I thought of this, and I disagree. The very fact of assigning to `T t` adds the reference count you need to keep `s.t` from disintegrating. As soon as you borrow, you increment the count.

I'm sure many inc/dec can still be removed.
March 02, 2015
On Monday, 2 March 2015 at 00:06:52 UTC, deadalnix wrote:
> On Sunday, 1 March 2015 at 23:56:02 UTC, Zach the Mystic wrote:
>> On Sunday, 1 March 2015 at 14:40:54 UTC, Marc Schütz wrote:
>>> I don't think a callee-based solution can work:
>>>
>>>   class T {
>>>       void doSomething() scope;
>>>   }
>>>   struct S {
>>>       RC!T t;
>>>   }
>>>   void main() {
>>>       auto s = S(RC!T()); // `s.t`'s refcount is 1
>>>       T t = s.t;          // borrowing from the RC wrapper
>>>       foo(s);
>>>       t.doSomething();    // oops, `t` is gone
>>>   }
>>>   void foo(ref S s) {
>>>       s.t = RC!T();       // drops the old `s.t`
>>>   }
>>
>> I thought of this, and I disagree. The very fact of assigning to `T t` adds the reference count you need to keep `s.t` from disintegrating. As soon as you borrow, you increment the count.
>
> I'm sure many inc/dec can still be removed.

Do you agree or disagree with what I said? I can't tell.
March 02, 2015
On Monday, 2 March 2015 at 00:37:05 UTC, Zach the Mystic wrote:
> On Monday, 2 March 2015 at 00:06:52 UTC, deadalnix wrote:
>>> I thought of this, and I disagree. The very fact of assigning to `T t` adds the reference count you need to keep `s.t` from disintegrating. As soon as you borrow, you increment the count.
>>
>> I'm sure many inc/dec can still be removed.
>
> Do you agree or disagree with what I said? I can't tell.

I think I understand now. Yes, they can probably be optimized, but that's a different issue than whether you need to protect certain RC instances from the "tyranny" of a function call. My whole argument is that basically you don't. Only when you split pass directly in the call itself: "fun(x,x)", does this issue ever matter, and it's easy to deal with.
March 02, 2015
On Monday, 2 March 2015 at 00:37:05 UTC, Zach the Mystic wrote:
>> I'm sure many inc/dec can still be removed.
>
> Do you agree or disagree with what I said? I can't tell.

Yes, but I think this is overly conservative.
March 02, 2015
On Monday, 2 March 2015 at 08:59:11 UTC, deadalnix wrote:
> On Monday, 2 March 2015 at 00:37:05 UTC, Zach the Mystic wrote:
>>> I'm sure many inc/dec can still be removed.
>>
>> Do you agree or disagree with what I said? I can't tell.
>
> Yes, but I think this is overly conservative.

I'm arguing a rather liberal position: that only in a very exceptional case do you need to protect a variable for the duration of a function. For the most part, it's not necessary. What am I conserving?
March 02, 2015
On Monday, 2 March 2015 at 13:30:39 UTC, Zach the Mystic wrote:
> On Monday, 2 March 2015 at 08:59:11 UTC, deadalnix wrote:
>> On Monday, 2 March 2015 at 00:37:05 UTC, Zach the Mystic wrote:
>>>> I'm sure many inc/dec can still be removed.
>>>
>>> Do you agree or disagree with what I said? I can't tell.
>>
>> Yes, but I think this is overly conservative.
>
> I'm arguing a rather liberal position: that only in a very exceptional case do you need to protect a variable for the duration of a function. For the most part, it's not necessary. What am I conserving?

I let the night go over that one. Here is what I think is the best road forward :
 - triggering postblit and/or ref count bump/decrease is prohibited on borrowed.
 - Acquiring and releasing ownership does.

Now that we have this, let's get back to the exemple :
class C {
    C c;

    // Make ti refconted somehow, doesn't matter. Andrei's proposal for instance.
}

void boom() {
    C c = new C();
    c.c = new C();

    foo(c, c.c);
}

void foo(ref C c1, ref C c2) {
    // Here is where things get different. c1 is borrowed, so you can't
    // do c1.c = null before acquiring c1.c beforehand. That means the
    // compiler needs to get a local copy of c1.c, bump the refcount
    // to get ownership before executing c1.c = null and decrease
    // the refcount. The ownership expire when the function returns
    // so c2 is free when foo returns.
    c1.c = null;
    // c2 is dead.
}

The definition is a bit wonky ATM and most likely needs to be refined, but I think this is the way forward with that issue. It allow elision of a lot of ref increase/decrease by the compiler, which is very important to get refcounting works fast.
March 02, 2015
On Sunday, 1 March 2015 at 19:35:57 UTC, deadalnix wrote:
> On Saturday, 28 February 2015 at 11:12:23 UTC, Marc Schütz wrote:
>> Yes. Terminology is a problem here, I guess. When I talk about "the scope" of a variable, it means that only references to values can be stored there whose lifetimes are at least as large as the scope.
>>
>
> Make sure you explicit that. The variable itself has a scope, and this scope is different from the scope of indirections stored in the variable.
>
> Additionally, this naturally bring the question of multiple indirection in a variable (for a struct for instance).

Access to a struct member itself is not actually indirection, because the member is inside the structs memory. When we take the address of a member, we therefore know its lifetime statically: it's the lifetime of the struct variable.

For accesses through a pointer (slice, etc.) one level deep, we also know some information about the destination's lifetime (that's what I'm calling scope) by looking at the return annotations (whether inferred or explicit), which the caller will enforce for us. Anything we store there needs to have a longer lifetime than the scope, and anything we read from there must not be stored where it can outlife the scope.

For deeper levels of indirection, we have no such information, so we must assume the worst case: we may only store things there that we know will live indefinitely, and what we read from there could cease existing immediately after the reference to it disappears.

>
>>> You don't cover the lifetime of the address of operation, and I'm not how this is supposed to work in your proposal.
>>>
>>
>> It was in the examples, but it was wrong. I've corrected it: A dereference results in static lifetime.
>>
>
> Will do a second pass on the damn thing :)
>
>>>> I will also add examples how return and static annotations are handled.
>>>
>>> static annotation ? Seems like a bad idea and I'm sure we can do without.
>>
>> It's only necessary if parameters of `@safe` functions are automatically scoped; then we need a way to opt-out. This is actually optional and does not affect the consistency, but I thought it is a good idea, because it reduces the overall amount of annotations. And I assume that most @safe functions are already written in a way that conforms to this. We'd need to analyze some code bases to find out whether this is actually true.
>
> Ok I misunderstood what you meant by static anotation. Sounds good. Scope by default, and an optout.
>
> Problem is transition. We have a scope keyword what does it become ?

It's not yet spelled out clearly enough, but by default, any parameters not marked as `scope` are treated as having infinite lifetime. This means that `scope` annotations would need to appear everywhere. However, they are inferred for template functions, together with `return` annotations, removing a large part of explicit annotations. (They are also always inferred for local variables.) For @safe functions, no inference is done, unless they are templates; instead, all references implicitly get a `scope` annotation, but no `return` annotations. The latter can be added manually, and we can opt-out from `scope` by using `static`.

Ideally most code should be @safe, and there was even talk about @safe by default, therefore most code shouldn't need to be annotated manually.

The transition can then be just like for DIP25. `scope`, `static` and `return` are no-ops, and can be enabled by a command-line switch. Later, they are enabled by default, and can be disabled by a switch. Finally, the switch is removed.
March 02, 2015
On Sunday, 1 March 2015 at 23:56:02 UTC, Zach the Mystic wrote:
> On Sunday, 1 March 2015 at 14:40:54 UTC, Marc Schütz wrote:
>> I don't think a callee-based solution can work:
>>
>>    class T {
>>        void doSomething() scope;
>>    }
>>    struct S {
>>        RC!T t;
>>    }
>>    void main() {
>>        auto s = S(RC!T()); // `s.t`'s refcount is 1
>>        T t = s.t;          // borrowing from the RC wrapper
>>        foo(s);
>>        t.doSomething();    // oops, `t` is gone
>>    }
>>    void foo(ref S s) {
>>        s.t = RC!T();       // drops the old `s.t`
>>    }
>
> I thought of this, and I disagree. The very fact of assigning to `T t` adds the reference count you need to keep `s.t` from disintegrating. As soon as you borrow, you increment the count.

Sorry, my mistake, should have explained what I have in mind.

`S.t` has type `RC!T`, but we're assigning it a variable of type `T`. This is made possible because `RC!T` has an `alias this` wrapper that returns `scope T`. The effect is that we're implicitly borrowing the `T` reference, as if the variable were declared `scope T`. The borrow checker (which I will specify later, see the examples [1] for a foretaste) will prohibit any unsafe use that would make the reference `t` outlive `s`.

Therefore, no postblit is called, and no reference count is incremented.

[1] http://wiki.dlang.org/User_talk:Schuetzm/scope2
March 02, 2015
On Monday, 2 March 2015 at 20:04:49 UTC, deadalnix wrote:
> On Monday, 2 March 2015 at 13:30:39 UTC, Zach the Mystic wrote:
>> On Monday, 2 March 2015 at 08:59:11 UTC, deadalnix wrote:
>>> On Monday, 2 March 2015 at 00:37:05 UTC, Zach the Mystic wrote:
>>>>> I'm sure many inc/dec can still be removed.
>>>>
>>>> Do you agree or disagree with what I said? I can't tell.
>>>
>>> Yes, but I think this is overly conservative.
>>
>> I'm arguing a rather liberal position: that only in a very exceptional case do you need to protect a variable for the duration of a function. For the most part, it's not necessary. What am I conserving?
>
> I let the night go over that one. Here is what I think is the best road forward :
>  - triggering postblit and/or ref count bump/decrease is prohibited on borrowed.
>  - Acquiring and releasing ownership does.
>
> Now that we have this, let's get back to the exemple :
> class C {
>     C c;
>
>     // Make ti refconted somehow, doesn't matter. Andrei's proposal for instance.
> }
>
> void boom() {
>     C c = new C();
>     c.c = new C();
>
>     foo(c, c.c);
> }
>
> void foo(ref C c1, ref C c2) {
>     // Here is where things get different. c1 is borrowed, so you can't
>     // do c1.c = null before acquiring c1.c beforehand. That means the
>     // compiler needs to get a local copy of c1.c, bump the refcount
>     // to get ownership before executing c1.c = null and decrease
>     // the refcount. The ownership expire when the function returns
>     // so c2 is free when foo returns.
>     c1.c = null;
>     // c2 is dead.
> }
>
> The definition is a bit wonky ATM and most likely needs to be refined, but I think this is the way forward with that issue. It allow elision of a lot of ref increase/decrease by the compiler, which is very important to get refcounting works fast.

Interesting approach. I will have to think about that. But I think it does not really work. Your example hides the fact that there are actually two types involved (or can be): an RC wrapper, and the actual class. foo() would need to take at least `c1` as the wrapper type `RC!C`, not `C` itself, otherwise it couldn't copy it. But that defeats the purpose of borrowing, that it neutralizes the actual memory management strategy; foo() should know whether `c1` is reference counted or not.