July 09, 2014
On Wed, Jul 09, 2014 at 11:33:09PM +0200, Johannes Pfau via Digitalmars-d wrote: [...]
> For delegates scope can prevent closure heap allocation. For all other types it does nothing. Example:
> 
> import std.stdio;
> 
> void testA(void delegate() cb)
> {
>     cb();
> }
> void testB(scope void delegate() cb)
> {
>     cb();
> }
> 
> void main()
> {
>     int a;
>     void callback() {a = 42;}
> //Callback accesses a, testA might store a reference to callback
> //->a might be accessible after this main function returns
> //->can't keep it on the stack. Allocate a on the heap
>     testA(&callback);
> 
> //Callback accesses a, but testB does not store a reference
> //as it tells us by using scope
> //So as soon as testB returns, there's no reference to a floating
> //around and we can allocate a on the stack.
> //(Of course as long as we call testA in this function, a is always on
> // the heap. but if we only call testB it can be on the stack)
>     testB(&callback);
> }

Unfortunately, it seems that this is not enforced by the compiler at all. For example:

	int delegate() globDg;

	void func(scope int delegate() dg) {
		globDg = dg;	// shouldn't compile, but does
		globDg();
	}

	void sub() {
		int x;
		func(() { return ++x; }); // oops
	}

	void trashme() {
		import std.stdio;
		writeln(globDg()); // prints garbage
	}

	void main() {
		sub();
		trashme();
	}

If 'scope' is commented out, then it works as expected (i.e., x gets
allocated on the heap).

	https://issues.dlang.org/show_bug.cgi?id=13085


T

-- 
Dogs have owners ... cats have staff. -- Krista Casada
July 09, 2014
On Wednesday, 9 July 2014 at 20:58:11 UTC, Sean Kelly wrote:
> On Wednesday, 9 July 2014 at 19:47:02 UTC, Walter Bright wrote:
>> On 7/9/2014 3:20 AM, Dicebot wrote:
>>>
>>> I'd state it differently: "Marketing fuss about goroutines is the killer feature
>>> of Go" :) It does not have any fundamental advantage over existing actor model
>>> and I doubt it will matter _that_ much.
>>
>> Much of the froth about Go is dismissed by serious developers, but they nailed the goroutine thing. It's Go's killer feature.
>
> I think it still mostly comes down to marketing :-)  That said,
> we're pretty close in D once my Scheduler pull request is
> accepted.  Between that and the Generator type in the same pull
> request, we're not missing much but the weird switch syntax they
> have.  There's still some work to do, but I think a lot of it
> really comes down to showing people what's already possible in D
> rather than writing more code.

Might also be a win to make it _visible_ in the language as a first-class construct by also adding async/await syntax sugar.

Joseph
July 09, 2014
On 7/9/2014 2:47 PM, Andrei Alexandrescu wrote:
> On 7/9/14, 1:51 PM, Walter Bright wrote:
>> On 7/9/2014 1:35 PM, Andrei Alexandrescu wrote:
>>> Hmmm... how about using u after that?
>>
>> Using u after that would either cause an exception to be thrown, or
>> they'd get T.init as a value. I tend to favor the latter, but of course
>> those decisions would have to be made as part of the design of Unique.
>
> That semantics would reenact the auto_ptr disaster so probably wouldn't be a
> good choice. -- Andrei
>

Is there a good reference on that disaster?
July 09, 2014
On 7/9/2014 1:42 PM, bearophile wrote:
> Is it possible & useful & good to put something like a "@not_null_references:"
> (or a similar pragma) at the top of a module? How are differently defaulting
> modules going to interact with each other?

Exactly. I'm not seeing how this can work that well.

July 09, 2014
On 7/9/2014 1:58 PM, Sean Kelly wrote:
> I think it still mostly comes down to marketing :-)  That said,
> we're pretty close in D once my Scheduler pull request is
> accepted.  Between that and the Generator type in the same pull
> request,

Ah, I didn't know you had a PR request already! Awesome!


> we're not missing much but the weird switch syntax they have.

I'm not concerned about the syntax. Any syntax we use should be "D style" syntax, not "Go style".


> There's still some work to do, but I think a lot of it
> really comes down to showing people what's already possible in D
> rather than writing more code.

I agree.
July 09, 2014
On 7/9/2014 1:58 PM, Sean Kelly wrote:
> we're pretty close in D once my Scheduler pull request is
> accepted.

I couldn't find it. Can you please add the link to the PR to:

https://issues.dlang.org/show_bug.cgi?id=13086

?
July 09, 2014
On 7/9/14, 3:25 PM, Walter Bright wrote:
> On 7/9/2014 2:47 PM, Andrei Alexandrescu wrote:
>> On 7/9/14, 1:51 PM, Walter Bright wrote:
>>> On 7/9/2014 1:35 PM, Andrei Alexandrescu wrote:
>>>> Hmmm... how about using u after that?
>>>
>>> Using u after that would either cause an exception to be thrown, or
>>> they'd get T.init as a value. I tend to favor the latter, but of course
>>> those decisions would have to be made as part of the design of Unique.
>>
>> That semantics would reenact the auto_ptr disaster so probably
>> wouldn't be a
>> good choice. -- Andrei
>>
>
> Is there a good reference on that disaster?

https://www.google.com/search?q=std%20auto_ptr%20sucks&gws_rd=ssl

:o)

It was the one library artifact that was deemed sufficiently bad for C++ to be straight deprecated. They didn't attempt that even with iostreams.


Andrei

July 09, 2014
On 7/9/2014 3:24 PM, Joseph Cassman wrote:
> Might also be a win to make it _visible_ in the language as a first-class
> construct by also adding async/await syntax sugar.

async/await are great ideas, but are something quite different from goroutines.

July 09, 2014
On 07/10/14 00:16, H. S. Teoh via Digitalmars-d wrote:
> On Wed, Jul 09, 2014 at 11:33:09PM +0200, Johannes Pfau via Digitalmars-d wrote: [...]
>> For delegates scope can prevent closure heap allocation. For all other types it does nothing. Example:
>>
>> import std.stdio;
>>
>> void testA(void delegate() cb)
>> {
>>     cb();
>> }
>> void testB(scope void delegate() cb)
>> {
>>     cb();
>> }
>>
>> void main()
>> {
>>     int a;
>>     void callback() {a = 42;}
>> //Callback accesses a, testA might store a reference to callback
>> //->a might be accessible after this main function returns
>> //->can't keep it on the stack. Allocate a on the heap
>>     testA(&callback);
>>
>> //Callback accesses a, but testB does not store a reference
>> //as it tells us by using scope
>> //So as soon as testB returns, there's no reference to a floating
>> //around and we can allocate a on the stack.
>> //(Of course as long as we call testA in this function, a is always on
>> // the heap. but if we only call testB it can be on the stack)
>>     testB(&callback);
>> }
> 
> Unfortunately, it seems that this is not enforced by the compiler at all.

Yes, scope is not enforced at all.

Also:

void main()
{
    int a;
    auto callback = {a = 42;};  // heap alloc
    scope callback = {a = 42;}; // stack (ie normal) alloc
    testA(callback);
    testB(callback);
}

Then there are lazy args, which are basically scope delegates, but with no way to turn off 'scope' and no escape protection.


Trying to tack 'borrowed' on 'ref' is not a good idea. (would require new restrictions, hence not backwards compatible; it can't be a @safe-only thing, `borrowed` affects lifetimes etc)

artur
July 09, 2014
On Wed, Jul 09, 2014 at 03:16:37PM -0700, H. S. Teoh via Digitalmars-d wrote: [...]
> 	https://issues.dlang.org/show_bug.cgi?id=13085
[...]

Hmm, apparently, this is a long-standing known issue:

	https://issues.dlang.org/show_bug.cgi?id=5270

Judging from this, a big missing piece of the current implementation is the actual enforcement of 'scope'.

So here's a first stab at refining (and extending) what 'scope' should
be:

- 'scope' can be applied to any variable, and is part of its type. Let's
  call this a "scoped type", and a value of this type a "scoped value".

- Every scoped type has an associated lifetime, which is basically the
  scope in which it is declared.

   - The lifetime of a scoped variable is PART OF ITS TYPE.

   - An unscoped variable is regarded to have infinite lifetime.

   - For function parameters, this lifetime is the scope of the function
     body.

   - For local variables, the lifetime is the containing lexical scope
     where it is declared.

- Taking the address of a scoped value returns a scoped pointer, whose
  lifetime is the lexical scope where the address-of operator is used.

- A scoped type can only be assigned to another scoped type of identical
  or narrower lifetime.  Basically, the idea here is that a scoped value
  can only have its scope narrowed, never expanded. In practice, this
  means:

   - If a scoped type is a reference type (class or pointer or ref), it
     can only be assigned to another scoped type whose associated
     lifetime is equal or contained within the source value's associated
     lifetime.

   - If a scoped type is a value type with indirections, it can only be
     assigned to an lvalue of the same scoped type (with the same
     associated lifetime).

   - If a scoped type is a value type with no indirections, it's freely
     assignable to a non-scoped lvalue of compatible type.

   - A function's return type can be scoped (not sure what syntax to use
     here, since it may clash with scope delegates).

      - The lifetime of the return value is the containing scope of the
        function definition -- if it's a module-level function, then its
        lifetime is infinite. If it's an inner function, then its
        lifetime is the containing lexical scope of its definition.
        Example:

            class C {}
            void func() {
                // return type of helper is scope(C) with lifetime up to
                // the end of func's body.
                scope(C) helper() { ... }
            }

      - Returning a value from a function is considered to be equivalent
        to assigning the value to a variable of the return type of the
        function. Thus:

            class C {}
            void func() {
                scope(C) c1;

                // helper's return type has lifetime == func's body
                scope(C) helper() {
                    scope(C) c2;
                    if (cond)
                        return c1; // OK, c1's lifetime == func's body
                    else
                        return c2; // ILLEGAL: c2's lifetime < func's body
                }
            }

      - Since a scoped return type has its lifetime as part of its type,
        the type system ensures that scoped values never escape their
        lifetime. For example, if we are sneaky and return a pointer to
        an inner function, the type system will prevent leakage of the
        scoped value:

            class C {}
            auto func() {
                scope(C) c1;

                // Return type of sneaky is scope(C) with lifetime =
                // body of func.
                scope(C) sneaky() {
                    // This is OK, because c1's lifetime == body of
                    // func, which is compatible with its return type.
                    return c1;
                }

                // Aha! we now we have broken scope... or have we?
                return &sneaky;
            }

            void main() {
                // Let's see. Get a function pointer to a function that
                // "leaks" a scoped value...
                auto funcptr = func();

                // But this doesn't compile, because the return type of
                // funcptr() is a scoped variable whose lifetime is
                // inside the body of func(), but since we're outside of
                // func here, the lifetime of x doesn't match the
                // lifetime of funcptr()'s return value, so the
                // following assignment is rejected as having
                // incompatible types:
                auto x = funcptr();

                // This will actually work... but it's OK, because we
                // aren't actually storing the return value of
                // funcptr(), so the scoped value is actually not leaked
                // after all.
                funcptr();
            }

- Aggregates:

   - It's turtles all the way down: members of scoped aggregates also
     have scoped type, with lifetime inherited from the parent
     aggregate. In other words, the lifetime of the aggregate is
     transitive to the lifetime of its members. For example:

        class C {}
        struct S {
            C c;
            int x;
        }
        int func(scope S s) {
            // N.B. lifetime of s is func's body.
            auto c1 = s.c; // typeof(c1) == scope(C) with lifetime = func's body
            C d;
            d = c1; // illegal: c1 has shorter lifetime than d.
            return s.x; // OK, even though typeof(s.x) has lifetime =
                        // func's body, it's a value type so we're
                        // actually copying it to func's return value,
                        // not returning the actual scoped int.
        }

- Passing parameters: since unscoped values are regarded to have
  infinite lifetime, it's OK to pass unscoped values into scoped
  function parameters: it's a narrowing of lifetime of the original
  value, which is allowed. (What's not allowed is expanding the lifetime
  of a scoped value.)

I'm sure there are plenty of holes in this proposal, so destroy away. ;-)


T

-- 
If I were two-faced, would I be wearing this one? -- Abraham Lincoln