On Friday, 5 December 2014 at 23:58:41 UTC, Walter Bright wrote:
On 12/5/2014 8:48 AM, "Marc Schütz" <[email protected]>" wrote:
1) Escape detection is limited to `ref`.
T* evil;
ref T func(scope ref T t, ref T u) @safe {
return t; // Error: escaping scope ref t
return u; // ok
evil = &u; // Error: escaping reference
}
vs.
T[] evil;
T[] func(scope T[] t, T[] u) @safe {
return t; // Error: cannot return scope
return u; // ok
evil = u; // !!! not good
right, although:
evil = t; // Error: not allowed
}
As can be seen, `ref T u` is protected from escaping (apart
from returning it),
while `T[] u` in the second example is not. There's no general
way to express
that `u` can only be returned from the function, but will not
be retained
otherwise by storing it in a global variable. Adding `pure`
can express this in
many cases, but is, of course, not always possible.
As you point out, 'ref' is designed for this.
I wouldn't call it "designed", but "repurposed"...
struct Container(T) {
scope ref T opIndex(size_t index);
}
void bar(scope ref int a);
Container c;
bar(c[42]); // ok
scope ref tmp = c[42]; // nope
Both cases should be fine theoretically; the "real" owner
lives longer than
`tmp`. Unfortunately the compiler doesn't know about this.
Right, though the compiler can optimize to produce the
equivalent.
??? This is a problem on the semantic level, unrelated to
optimization:
// contrived example to illustrated the point
Container c;
scope ref x = c[42]; // not
scope ref y = c[44]; // ...
scope ref z = c[13]; // allowed
foo(x, y, z, x+y, y+z, z+x, x+y+z);
// workaround, but error-prone and has different semantics
// (opIndex may have side effects, called multiple times)
foo(c[42], c[44], c[13], c[42]+c[44], c[44]+c[13],
c[13]+c[42], c[42]+c[44]+c[13]);
// another workaround, same semantics, but ugly and unreadable
(scope ref int x, scope ref int y, scope ref int z) {
foo(x, y, z, x+y, y+z, z+x, x+y+z);
}(c[42], c[44], c[13]);
3) `scope` cannot be used for value types.
I can think of a few use cases for scoped value types (RC and
file descriptors),
but they might only be marginal.
I suspect that one can encapsulate such values in a struct
where access to them is strictly controlled.
They can, but again at the cost of an indirection (ref).
4) No overloading on `scope`.
This is at least partially a consequence of `scope` inference.
I think
overloading can be made to work in the presence of inference,
but I haven't
thought it through.
Right. Different overloads can have different semantic
implementations, so what should inference do? I also suspect it
is bad style to overload on 'scope'.
It only makes sense with scope value types (see the RC example).
For references, I don't know any useful applications.
6) There seem to be problems with chaining.
scope ref int foo();
scope ref int bar1(ref int a) {
return a;
}
scope ref int bar2(scope ref int a) {
return a;
}
ref int bar3(ref int a) {
return a;
}
ref int bar4(scope ref int a) {
return a;
}
void baz(scope ref int a);
Which of the following calls would work?
foo().bar1().baz();
yes
foo().bar2().baz();
no - cannot return scope ref parameter
foo().bar3().baz();
yes
foo().bar4().baz();
no, cannot return scope ref parameter
I'm not sure I understand this fully yet, but it could be that
none of them work...
Well, you're half right :-)
Ok, so let's drop bar2() and bar4().
scope ref int foo();
scope ref int bar1(ref int a) {
return a;
}
ref int bar3(ref int a) {
return a;
}
ref int baz_noscope(/*scope*/ ref int a);
foo().bar1().baz_noscope();
foo().bar3().baz_noscope();
And now? In particular, will the return value of `bar3` be
treated as if it were `scope ref`?