Scope and Ref and Borrowing

Manu via Digitalmars-d digitalmars-d at puremagic.com
Thu Nov 13 19:41:50 PST 2014


On 14 November 2014 11:20, Walter Bright via Digitalmars-d
<digitalmars-d at puremagic.com> wrote:
> Thought I'd bring this up as deadalnix is working on a related proposal. It
> uses 'scope' in conjunction with 'ref' to resolve some long standing @safe
> issues.
> ---------------------------------------
>
> **Background
>
> The goal of @safe code is that it is guaranteed to be memory safe. This is
> mostly
> achieved, but there's a gaping hole - returning pointers to stack objects
> when those
> objects are out of scope. This is memory corruption.
>
> The simple cases of this are disallowed:
>
>   T* func(T t) {
>     T u;
>     return &t; // Error: escaping reference to local t
>     return &u; // Error: escaping reference to local u
>   }
>
> But are is easily circumvented:
>
>   T* func(T t) {
>     T* p = &t;
>     return p;  // no error detected
>   }
>
> @safe deals with this by preventing taking the address of a local:
>
>   T* func(T t) @safe {
>     T* p = &t; // Error: cannot take address of parameter t in @safe
> function func
>     return p;
>   }
>
> But this is awfully restrictive. So the 'ref' storage class was introduced
> which
> defines a special purpose pointer. 'ref' can only appear in certain
> contexts,
> in particular function parameters and returns, only applies to declarations,
> cannot be stored, and cannot be incremented.
>
>   ref T func(T t) @safe {
>     return t; // Error: escaping reference to local variable t
>   }
>
> Ref can be passed down to functions:
>
>   void func(ref T t) @safe;
>   void bar(ref T t) @safe {
>      func(t); // ok
>   }
>
> But the following idiom is far too useful to be disallowed:
>
>   ref T func(ref T t) @safe {
>     return t; // ok
>   }
>
> And if it is misused it can result in stack corruption:
>
>   ref T foo() @safe {
>     T t;
>     return func(t); // no error detected, despite returning pointer to t
>   }
>
> The purpose of this proposal is to detect these cases at compile time and
> disallow them.
> Memory safety is achieved by allowing pointers to stack objects be passed
> down the stack,
> but those pointers may not be saved into non-stack objects or stack objects
> higher on the stack,
> and may not be passed up the
> stack past where they are allocated.
>
> The:
>
>     return func(t);
>
> case is detected by all of the following conditions being true:
>
> 1. foo() returns by reference
> 2. func() returns by reference
> 3. func() has one or more parameters that are by reference
> 4. 1 or more of the arguments to those parameters are stack objects local to
> foo()
> 5. Those arguments can be @safe-ly converted from the parameter to the
> return type.
>    For example, if the return type is larger than the parameter type, the
> return type
>    cannot be a reference to the argument. If the return type is a pointer,
> and the
>    parameter type is a size_t, it cannot be a reference to the argument. The
> larger
>    a list of these cases can be made, the more code will pass @safe checks
> without requiring
>    further annotation.
>
> **Scope Ref
>
> The above solution is correct, but a bit restrictive. After all, func(t, u)
> could be returning
> a reference to non-local u, not local t, and so should work. To fix this,
> introduce the concept
> of 'scope ref':
>
>     ref T func(scope ref T t, T u) @safe {
>       return t; // Error: escaping scope ref t
>       return u; // ok
>     }
>
> Scope means that the ref is guaranteed not to escape.
>
>   T u;
>   ref T foo() @safe {
>     T t;
>     return func(t, u); // ok, u is not local
>     return func(u, t); // Error: escaping scope ref t
>   }
>
> This scheme minimizes the number of 'scope' annotations required.
>
> **Out Parameters
>
> 'out' parameters are treated like 'ref' parameters for the purposes of this
> document.
>
> **Inference
>
> Many functions can infer pure, @safe, and @nogc. Those same functions can
> infer
> which ref parameters are 'scope', without needing user annotation.
>
> **Mangling
>
> Scope will require additional name mangling, as it affects the interface of
> the function.
>
> **Nested Functions
>
> Nested functions have more objects available than just their arguments:
>
>   ref T foo() @safe {
>     T t;
>     ref T func() { return t; }
>     return func();  // should be disallowed
>   }
>
> On the plus side the body of the nested function is available to the
> compiler for
> examination.
>
> **Delegates and Closures
>
> This one is a little harder; but the compiler can detect that t is taken by
> reference,
> and then can assume that dg() is returning t by reference and disallow it.
>
>   ref T foo() @safe {
>     T t;
>     ref T func() { return t; }
>     auto dg = &func;
>     return dg();  // should be disallowed
>   }
>
>
> **Overloading
>
> Scope does not affect overloading, i.e.:
>
>    T func(scope ref a);
>    T func(ref T b);
>
> are considered the same as far as overloading goes.
>
> **Inheritance
>
> Overriding functions inherit any 'scope' annotations from their antecedents.
>
> **Limitations
>
> Arrays of references are not allowed.
> Struct and class fields that are references are not allowed.
> Non-parameter variables cannot be references.


I'm not quite clear; are you suggesting 'scope ref' would be a storage
class? (looks like it, but it also affects mangling?)

Please, consider Marc Schütz's existing proposal.
Please, please, don't make scope a storage class.

I have a lot of other issues with this proposal, but maybe I
misunderstand it in principle...?



More information about the Digitalmars-d mailing list