The next iteration of scope
via Digitalmars-d
digitalmars-d at puremagic.com
Mon Mar 16 10:00:11 PDT 2015
On Monday, 16 March 2015 at 15:25:45 UTC, Zach the Mystic wrote:
> If an RC'd struct is heap-allocated, but one of its members
> points to the stack, how is it ever safe to escape it?
It isn't.
> Why shouldn't the heap variable be treated as scoped too,
> inheriting the most restricted scope of any of its members?
See below.
> To me, the question is not how you can know that a member is
> scoped, so much as how you could know that it *isn't* scoped,
> i.e. that a sub-pointer was global while the parent was local.
> I think it would require a very complicated type system:
>
> struct S {
> T* p;
> }
>
> // note the elaborate new return signature
> T* fun(return!(S.p) S x) {
> return x.p;
> }
>
> T* run() {
> S s;
> s.p = new T; // s local, s.p global
> return fun(s);
> }
>
> The above is technically safe, but the question is whether it's
> too complicated for the benefit. In the absence of such a
> complicated system, the safe default is to assume a struct is
> always as scoped as its most scoped member (i.e. transitive
> scoping).
My old proposal treated scope as a type modifier. It would have
made something like your example possible. But it's now a storage
class, which means that we lose information when we go through an
indirection.
The solution is indeed a kind of transitivity. I describe this in
the section on multiple indirections:
http://wiki.dlang.org/User:Schuetzm/scope2#Multiple_indirections
> Your idea of `scope` members would only be valid in the absence
> of this safe default. But even then it would be of limited
> usefulness, because it would prevent all uses of global
> references in those members, even if the parent was global. For
> me, it comes down to that you can't know if anything is global
> or local until you define an instance of it, which you can't do
> in the struct definition.
I think there is a misunderstanding. scope members don't really
introduce anything new. They are exactly equivalent to an
accessor method:
struct S {
scope int* payload;
scope int** indirect;
}
behaves exactly the same as:
struct S {
private int* payload_;
ref int* payload() return {
return payload_;
}
}
By the rules for multiple indirections, it's not possible to do
unsafe things with them:
void foo() {
// reading (right hand side):
S s;
int* p = s.payload; // OK, `payload` lives at least as long
as `s`
p = *s.indirect; // ditto
{
S s2;
p = s2.payload; // NOT OK, we don't know how long
`payload`
// really lives, so we assume it is
// destroyed when `s2` goes out of scope
p = *s2.indirect;// ditto
}
// assignment (left hand side):
s.payload = new int; // OK, `new int` is on the heap
int x;
s.payload = &x; // NOT OK, `payload` is scope by its
`this`
// which is `s`, which outlives `x`
*s.indirect = new int*;
// OK: `new int*` is on the heap
*s.indirect = &p; // NOT OK: accessed through indirection
// must assume destination exists forever
// => cannot contain pointer to local
}
The only difference it makes whether a member is annotated as
scope or not is when it's accessed from inside a method, because
in this case, the compiler doesn't know anything about the
context in which the method is called.
BUt there is indeed still some confusion on my side. It's about
the question whether `this` should implicitly be passed as
`scope` or not. Because if it is, scope members are probably
useless, because they are already implied. I think I should
remove this suggestion, because it would break too much code (in
@system).
More information about the Digitalmars-d
mailing list