Index » General » ref is unsafe (page 7)

January 07, 2013
On Friday, 4 January 2013 at 20:20:08 UTC, Jonathan M Davis wrote:
> On Friday, January 04, 2013 17:26:59 Zach the Mystic wrote:
>> > Honestly though, I'm inclined to argue that functions which
>> > return by ref and
>> > have a ref parameter of that same type just be considered
>> > @system.
>> 
>> Structs mess that up as well:
>> struct S { int i; }
>> ref int d(ref S s)
>> {
>> return s.i;
>> }
>
> Yes. That's a function which takes a ref and returns by ref just like I said.
> It's just that in this case, the ref returned isn't the full object that was
> passed by ref but just a portion of it. What that means is that you can't
> assume that the ref being returned is safe just because the type of the
> parameter and the return type aren't the same. But it doesn't change the
> statement that a function which takes a parameter by ref and returns by ref
> can't be considered @safe without additional constraints of some kind. It just
> shows why you don't have an easy way out to make many of them @safe based on
> the differing types involved.
>
> - Jonathan M Davis

Why this won't work?
1. If the function code is available at ct, we can check for escaping locals.
2. Otherwise, we want to statically say to the compiler that the returned ref is safe exactly in these lines in which the particular function argument, from which the ref has been extracted, has not yet gone out of scope. So the returned ref safety guarantee tracks the argument safety guarantee.
Something like this:

ref int f(@infer_safe_from ref int a);

With such an annotation on an argument, the compiler will be able to infer the safety of a function when used in cases in which a is in scope whenever the returned ref is referenced.

Now, if f is used in this manner:
{

January 09, 2013
I felt confident enough about my proposal to submit it as enhancement request:

http://d.puremagic.com/issues/show_bug.cgi?id=9283
January 09, 2013
On Wednesday, 9 January 2013 at 04:33:21 UTC, Zach the Mystic wrote:
> I felt confident enough about my proposal to submit it as enhancement request:
>
> http://d.puremagic.com/issues/show_bug.cgi?id=9283

I like it. One issue though, like you also indicated by putting question marks on it:

ref T get(T)()
{
  T local;
  return cast(out) local; // This shouldn't compile
}

Because, wouldn't returning a local variable as a reference be a dangling reference in all cases? No matter if the programmer claims it's correct by saying cast(out)... it just can't be correct.

And T can be a type that has reference semantics or value semantics, it doesn't matter. That function would always return a dangling reference, were it allowed to compile.
January 09, 2013
On Wednesday, 9 January 2013 at 04:33:21 UTC, Zach the Mystic wrote:
> I felt confident enough about my proposal to submit it as enhancement request:
>
> http://d.puremagic.com/issues/show_bug.cgi?id=9283

By the way, what do you propose is the correct placement of this "new" out keyword:

#1: out ref int get(ref int a);
#2: ref out int get(ref int a);
#3: ref int get(ref int a) out;

I wouldn't allow #3.
January 09, 2013
On Sunday, 30 December 2012 at 22:02:16 UTC, Jonathan M Davis wrote:
>  But that's
> very different from any attribute that we currently have. It would be like
> having a throw attribute instead of a nothrow attribute. I suppose that it is
> a possible solution though. I could also see an argument that the attribute
> should go on the parameter rather than the function, in which case you could
> have more fine-grained control over it, but it does complicate things further.

I think this is the most reasonable thing to do and I can argue that the complications are not a valid argument against this. I've came out with roughly the same idea some days ago. Comparing this with nothrow is a nice point, but I don't see it as an argument against it. This is the most logical thing to do, and solves problems.

So, the general notion that we want to (statically) express is that the ref result of a function __can__ depend on one (or more - depending on a condition for example) ref function parameters. Now, if the result is used when all the annotated arguments are still in scope, that usage can be considered @safe.

So a function declaration will (conceptually) look like this:
ref int min(@result_tracks_scope_of ref int a, @result_tracks_scope_of ref int b) {
    return a < b ? a : b;
}

Now the interface provides enough information for itself to infer when the usage is safe and when not.

This will work equally well when we refer to members of the ref parameters.

ref int a(@result_tracks_scope_of A a) {
    return a.la.bala;
}

The crucial thing is that the compiler can simply infer these attributes when the implementation is available, so we won't have to issue errors when the user has not added them (if the code is available).

January 10, 2013
On Thursday, 3 January 2013 at 21:56:22 UTC, David Nadlinger wrote:
> I must admit that I haven't read the rest of the thread yet, but I think the obvious and correct solution is to disallow passing locals (including non-ref parameters, which are effectively locals in D) as non-scope ref arguments.
>
> The scope attribute, once properly implemented, would make sure that the reference is not escaped. For now, we could just make it behave overly conservative in @safe code.
>
> David

If you disallow passing local variables as non-scope ref arguments, then you effectively disallow all method calls on local variables. My reasoning is as follows:

struct T
{
    int get(int v) const;
    void set(int v);
}

Those methods of T can be thought of as free functions with these signatures:

int get(ref const T obj, int v);
void set(ref T obj, int v);

And these kinds of method calls:

T obj;
int n = obj.get(v);
obj.set(n);

...can be thought of as being converted to these free function calls:

T obj;
int n = .get(obj, v);
.set(obj, n);

I don't know what the compiler does or doesn't do, but it is *as_if* the compiler did this conversion from method calls to free functions.

Now it's obvious, given those free function signatures, that if you disallow passing function-local variables as non-scope references, you also disallow this code:

void func()
{
    T obj;
    obj.set(123);
}

Because that would effectively be the same as:

void func()
{
    T obj;          // obj is a local variable
    .set(obj, 123); // obj is passed as non-scope ref
}

Then, you might ask, why don't those methods of T correspond to these free function signatures:

int get(scope ref const T obj, int v);
void set(scope ref T obj, int v);

And the answer is obviously that it would prevent these kinds of methods:

struct T
{
    int v;

    ref T increment()
    {
        v++;
        return this;
    }
}

...because that would then convert to this free function signature:

ref T increment(scope ref T obj)
{
    obj.v++;
    return obj; // Can't return a reference to a scope argument
}
January 10, 2013
...Although, I should add that my analogy between methods and free functions seems to break when the object is an rvalue. Like in:

struct T
{
    int v;

    this(int a)
    {
        v = a;
    }

    int get()
    {
        return v;
    }
}

int v = T(4).get();

Given my analogy, the method get() should be able to be thought of as a free function:

int gget(ref T obj)
{
    return obj.v;
}

But then the above method call should be able to thought of as:

int v = gget(T(4));

...which won't compile because T(4) is an rvalue, and according to D, rvalues can't be passed as ref (nor const ref). I don't know which one is flawed, my analogy, or the logic of how D is designed.

January 10, 2013
On Thursday, 10 January 2013 at 16:42:09 UTC, Tommi wrote:
> ...which won't compile because T(4) is an rvalue, and according to D, rvalues can't be passed as ref (nor const ref). I don't know which one is flawed, my analogy, or the logic of how D is designed.

My analogy is a bit broken in the sense that methods actually see their designated object as a reference to lvalue even if it is an rvalue. But I don't think that affects the logic of my main argument about scope arguments.

A more strict language logic would be inconvenient.

But, this logic does introduce a discrepancy between non-member operators and member operators in C++ (which D actually side-steps by disallowing non-member operators... and then re-introduces by providing UFCS):

// C++:

struct T
{
    int val = 10;

    T& operator--()
    {
        --val;
        return *this;
    }
};

T& operator++(T& t)
{
    ++t.val;
    return t;
}

int main()
{
    _cprintf("%d\n", (--T()).val); // Prints: 9
    _cprintf("%d\n", (++T()).val); // Error: no known conversion
                                   // from 'T' to 'T&'
    return 0;
}

// D:

import std.stdio;

struct T
{
    int val;

    int get()
    {
        ++val;
        return val;
    }
}

int het(ref T t)
{
    ++t.val;
    return t.val;
}

void main()
{
    writeln(T().get());
    writeln(T().het()); // Error: T(0) is not an lvalue
}
Next ›   Last »
1 2 3 4 5 6 7
Top | Discussion index | About this forum | D home