Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please add new syntax keyword "ref?" to represent that "ref return" result might be null #16670

Closed
ygc369 opened this issue Jan 21, 2017 · 15 comments

Comments

@ygc369
Copy link

ygc369 commented Jan 21, 2017

Now the C#7.0 feature "ref return" doesn't allow null as return value, but null as return value is sometimes useful. For example:

static ref? int findvalue(int[] arr, int value){
    int i;
    for(i=0;i<arr.Length;i++){
        if(value==arr[i])return ref arr[i];
    }
    return null; //not found
}

int[] array=new int[]{1,2,3};
ref? int e=findvalue(array, 4);
if(e!=null)e=5;
ref? int e1=finevalue(array,3);
if(e1!=null)e1=0;
..........
@gordanr
Copy link

gordanr commented Jan 21, 2017

http://mustoverride.com/refs-not-ptrs/

@ygc369
Copy link
Author

ygc369 commented Jan 22, 2017

@VSadov
I've seen your blog. You answered the question "Can ref return be null?", but you didn't answer "How do I test for nulls?"

@HaloFour
Copy link

@ygc369

Given that the answer was, "Not possible. Not, unless something is fundamentally broken," why do you think there would need to be a way to test for null? A ref return can't be null, just like ref parameters can't be null, and there's never been a way to test them for some state that they cannot/shouldn't be.

@ufcpp
Copy link
Contributor

ufcpp commented Jan 22, 2017

@ygc369

You can return null as ref int by using the Unsafe class

using System;
using System.Runtime.CompilerServices;

class Program
{
    static void Main()
    {
        int[] array = new int[] { 1, 2, 3 };

        {
            ref int e = ref FindValue(array, 4);
            if (!IsNull(ref e)) e = 5;
        }

        {
            ref int e = ref FindValue(array, 3);
            if (!IsNull(ref e)) e = 0;
        }

        foreach (int x in array)
        {
            Console.WriteLine(x); // 1, 2, 0
        }
    }

    static ref int FindValue(int[] arr, int value)
    {
        int i;
        for (i = 0; i < arr.Length; i++)
        {
            if (value == arr[i]) return ref arr[i];
        }
        return ref NullRef<int>();
    }

    unsafe static ref T NullRef<T>() where T : struct => ref Unsafe.AsRef<T>((void*)0);
    unsafe static bool IsNull<T>(ref T r) where T : struct => Unsafe.AsPointer(ref r) == (void*)0;
}

@gafter gafter added this to the 2.1 milestone Jan 22, 2017
@ygc369
Copy link
Author

ygc369 commented Jan 23, 2017

@ufcpp
Good answer!

@Thaina
Copy link

Thaina commented Jan 23, 2017

I was propose it once #11552

@jnm2
Copy link
Contributor

jnm2 commented Jan 24, 2017

@HaloFour

Given that the answer was, "Not possible. Not, unless something is fundamentally broken," why do you think there would need to be a way to test for null? A ref return can't be null, just like ref parameters can't be null, and there's never been a way to test them for some state that they cannot/shouldn't be.

I can see the value in being able to either return either null or a ref. What's the workaround? A marker ref passed in called nullMarkerRef which the caller compares to the return to see whether to treat the return as null?

@ufcpp
Copy link
Contributor

ufcpp commented Jan 24, 2017

Here is a type representing either null or a ref.

using System;
using System.Runtime.CompilerServices;

unsafe struct NullableRef<T>
{
    void* _ref;
    public NullableRef(ref T r) => _ref = Unsafe.AsPointer(ref r);
    public bool IsNull => _ref == (void*)0;
    public ref T Reference => ref Unsafe.AsRef<T>(_ref);
}

class Program
{
    static void Main()
    {
        var array = new[] { 1, 2, 3, 4, 5 };

        var x = Find(array, 3);
        if (!x.IsNull) x.Reference = 0;

        var y = Find(array, 6);
        if (!y.IsNull) y.Reference = 0;

        Console.WriteLine(string.Join(", ", array));
    }

    static NullableRef<int> Find(int[] array, int value)
    {
        for (int i = 0; i < array.Length; i++)
        {
            if (array[i] == value) return new NullableRef<int>(ref array[i]);
        }
        return default(NullableRef<int>);
    }
}

@HaloFour
Copy link

@jnm2

What's the workaround?

Returning ref int? is probably as close as you get, short of digging into unsafe territory at which point you'd probably be better off skipping ref and diving straight into pointers.

From the blog post above it sounds very much like this request to be able to work with ref as if it's a "pointer-lite" was never intended. That ref as a modifier doesn't change what the type is, only where it is. But given the milestone tag maybe they're willing to reconsider it. My question would be what additional work might need to be done with the verifier to enable such scenarios, as I understand that ref returns already ran afoul of some of the code safety enforcement features.

@benaadams
Copy link
Member

@ufcpp as the _ref is stored as a void* I think that type has a GC hole...

@ufcpp
Copy link
Contributor

ufcpp commented Jan 24, 2017

@benaadams
Yes. ref field would be needed. T might have to be constrained asstruct.
To begin with, I think this is an abuse.

@Thaina
Copy link

Thaina commented Jan 24, 2017

unsafe would be a working approach but I think it is wrong way to go

I think we should revise what we really want from returning nullable ref (and nullable in general?)

In my opinion. What we really want is, we just want to return reference of things that exist. And if not, most of the times, we will write a logic to branch off doing other things that will not touch that thing

So I was propose #11552 which I think what we just want is throwing compile error unless we make a branch to not touch it when it is null

Which actually the same idea as #5032 about nullable reference type. We could make string? type and compiler will throw error when you access it without any null checking

And that is what we really want out of it. We actually never want NullableRef to wrap things. We actually want branching enforcement, something like try/catch/finally or pattern matching if(obj is int i) something like that

@VSadov
Copy link
Member

VSadov commented Feb 2, 2017

ref return is a variable, just like a ref parameter. Having underlying managed pointer be null would be equivalent of a variable not bound to any storage. That is not possible in C# except through some unsafe/reflection tricks. A given variable may have a value null, but there is no concept of "variable just does not exist".

I am wondering why the given scenario is a problem with ref returns and was not a problem with ref parameters...
In either case a possible solution could be to pass some bool flag indicating whether a variable was bound to a desirable location. In a failure case you can refer to some dummy variable. A static field would do.

It may be worth looking into this scenario if it becomes very common.

@svick
Copy link
Contributor

svick commented Feb 3, 2017

I am wondering why the given scenario is a problem with ref returns and was not a problem with ref parameters...

With ref parameters, if I don't care about some parameter, using a dummy local variable with a default value is probably enough and then I can just ignore it after the call.

With ref returns, if I don't have any variable to return, the caller probably has to be be told about it somehow (possibly using a bool flag, as you suggested) and I have to somehow allocate storage for the dummy variable on the heap (or it could be a static field).

So I think it indeed is a bigger issue with ref returns than with ref parameters.

@gafter gafter closed this as completed Apr 24, 2017
@gafter
Copy link
Member

gafter commented Apr 24, 2017

Issue moved to dotnet/csharplang #497 via ZenHub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests