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

Ref returns can't easily return null #1201

Closed
ghost opened this issue Dec 18, 2017 · 32 comments
Closed

Ref returns can't easily return null #1201

ghost opened this issue Dec 18, 2017 · 32 comments

Comments

@ghost
Copy link

ghost commented Dec 18, 2017

Ref Returns Docs says:
"The return value cannot be a null. Attempting to return null generates compiler error CS8156, "An expression cannot be used in this context because it may not be returned by reference."
If a method with a ref return needs to return a null value, you can either return a null (uninstantiated) value for a reference type or a nullable type for a value type."

So I have to do this:

static string[] Students = { "Ali", "Hani", "Shady", "Hala" };
static string s;

static ref string FindStudent(string Name)
{
    for (int i = 0; i < Students.Length; i++)
        if (Students[i] == Name)
            return ref Students[i];
           
    return ref s ;    
}

I have to declare a non local variable without any purpose except to allow my function to return null!! It can't be a local variable because it can't be used as a ref return!
What on earth is that? Why can't I simply write:
return null
?!
Please fix this ugliness!

EDIT:
Suddenly, I realized why this is not a problem to solve easily.
Returning null doesn't mean a null reference. This is a high level ref not a pointer, and when we read it's value, we get the actual stored value not the storage address(pointer)! So, when we check the return value to be null, this means the reference carries a null value, not itself is null.
So, this can't be a valid test for a function failure, because if the function deals with objects, and returned the ref of one of them, it is possible that this object is null and this is a valid result not an indication for not finding a result!!
Oh, man! It is very confusing. We lived happily ever before ref returns!

@mikedn
Copy link

mikedn commented Dec 18, 2017

Ha ha, people are willing to stand on their heads to get rid of nulls and you want to add more?

@jnm2
Copy link
Contributor

jnm2 commented Dec 18, 2017

#497

@ghost
Copy link
Author

ghost commented Dec 18, 2017

@mikedn
In this sample I search for a name in the array. I return the ref of the element if found, but if not found what can I return?.. Yes, that's right: null!
In microsft docs they raise an exception. This is a bad choice and I don't prefer it!.. null is a lesss hell!

@HaloFour
Copy link
Contributor

@MohammadHamdyGhanem

Why are you returning a ref in this sample? What do you hope to gain from trying to use that feature in this context?

@ghost
Copy link
Author

ghost commented Dec 18, 2017

@jnm2
I read it, but I don't agree to add ?.. we shold be able to write:
return null;
and C# compiler can do what ever it likes to make it possible.

@ghost
Copy link
Author

ghost commented Dec 18, 2017

@HaloFour
I am not a fan of ref returns and i don't plan to use them. This is a sample pased on the c# docs sample than explains this feature. Your question must be redirected to C# team itself: What they are trying to gain with ref returns?
Anyway: When you write a function, you must suplay an indication of success and failure. Without a null refrence, you have to return a tuple like this:
static (bool Sucess, ref string Element) FindStudent2(string Name)
{
}
But even this is not supported!
So, I ask you back:
Why should any one use the hell of ref returns?

@jnm2
Copy link
Contributor

jnm2 commented Dec 18, 2017

I am not a fan of ref returns and i don't plan to use them.

Why do you have an opinion on whether they should be allowed to be null, then?

@HaloFour
Copy link
Contributor

@MohammadHamdyGhanem

ref returns (and most of the ref changes in C# 7.0) are an advanced feature primarily for a narrow set of specific performance issues. Unless you're working on the CLR or a game engine odds are you'll never need it or see it. They are intended to represent a very strict subset of pointer operations that can be verified as safe. I'm pretty sure that adding null into the mix crosses the line from safe into the unsafe and would greatly complicate working with references throughout the language.

@ghost
Copy link
Author

ghost commented Dec 18, 2017

@HaloFour
If you had to raise Exception and then try catch it, I don't think this will be a better performance . There should be a way to flag the absense of a result .

@HaloFour
Copy link
Contributor

@MohammadHamdyGhanem

The safe way to do this is to have the caller pass a value for the default value:

static string[] Students = { "Ali", "Hani", "Shady", "Hala" };

static ref string FindStudent(string name, ref string defaultValue)
{
    for (int i = 0; i < Students.Length; i++)
    {
        ref string student = Students[i];
        if (string.Equals(student, name))
        {
            return ref student;
        }
    }
    return ref defaultValue;    
}

The reason to do this is that the caller can directly mutate the return value since it is a ref. If the caller has to pass its own ref it can easily detect if it is the sentinel value, and mutations are safely isolated.

@mikedn
Copy link

mikedn commented Dec 18, 2017

There should be a way to flag the absense of a result .

Why? ref is more or less an optional feature of the language. If it solves a problem, great. If it doesn't solve a problem, well, there are other solutions that do not need ref. Java doesn't have ref and it's alive and well without it (well, there are a few scenarios where the lack of ref results in some really crazy code but fortunately those are rare). And C++ has references but like C# it doesn't have null references.

Besides, if you try hard enough you may find that a solution to this problem actually exists. I suspect (I haven't tried) that it's possible to create a ref struct OptionRef<T> that wraps a Span<T>. An empty span would indicate that there's no result.

In any case, allowing null refs would be pretty terrible. This might have been an option in C# 1.0 but adding this now would be pretty dubious. No existing method that takes a ref parameter expect that the reference can ever be null.

@ghost
Copy link
Author

ghost commented Dec 18, 2017

@HaloFour
That's a valid workarround although one expected that Tuples would end ref parameters era!
May ba an out parameter is better:

        static ref string FindStudent(string Name, out bool Found)
        {
            for (int i = 0; i < Students.Length; i++)
                if (Students[i] == Name)
                {
                    Found = true;
                    return ref Students[i];
                }

            Found = false;
            return ref Students[0]; // any return value just to avoid the errror
        }

It can be used like this:

            ref var std = ref FindStudent("Ali", out var found);
            if (found)
            {
                std = "Mohammad"; // For testing
                Console.WriteLine(Students[0]); //Mohammad
            }
            else
                Console.WriteLine("Not Found");

Thanks.

@HaloFour
Copy link
Contributor

@MohammadHamdyGhanem

No, tuples were supposed to end the era of using out parameters to return multiple values, which is what you're doing. 😁

Either way works. Although I prefer specifying that default value.

@mikedn
Copy link

mikedn commented Dec 18, 2017

return ref Students[0]; // any return value just to avoid the error

That's a pretty bad idea. Imagine what happens if people forget to check if found is true...

@HaloFour
Copy link
Contributor

Or if the array is empty ...

If anyone has any doubt to the complexity of this subject just enjoy the diatribe from Eric Lippert in the comments of the MSDN docs. He ain't wrong, but explaining this feature to a general purpose audience is going to be inherently tricky.

@ghost
Copy link
Author

ghost commented Dec 18, 2017

@HaloFour
I am aware of the special cases, but obviously ref returns don't belong to the realm of modern programming, and one should take care! Besides, writing the ref word 2 times in the function, and two times in its call seems annoying! Why can't the compiler be smart and use one ref only at each side?

@alrz
Copy link
Member

alrz commented Dec 18, 2017

Shouldn't this work with "ref-reassignment" and "declaration expression" features?

static bool TryFind(string name, ref Student result) {
    foreach (ref Student student in array) {
        if (student.Name != name) continue;
        result = ref student;
        return true;
    }

    return false;
}

if (TryFind(name, ref var result = null) {
  // found
}

@ghost
Copy link
Author

ghost commented Dec 18, 2017

Suddenly, I realized why this is not a problem to solve easily.
Returning null doesn't mean a null reference. This is a high level ref not a pointer, and when we read it's value, we get the actual stored value not the storage address(pointer)! So, when we check the return value to be null, this means the reference carries a null value, not itself is null.
So, this can't be a valid test for a function failure, because if the function deals with objects, and returned the ref of one of them, it is possible that this object is null and this is a valid result not an indication for not finding a result!!
Oh, man! It is very confusing. We lived happily ever before ref returns!

@HaloFour
Copy link
Contributor

@MohammadHamdyGhanem

but obviously ref returns don't belong to the realm of modern programming, and one should take care!

Sure it does. It solves a problem that can't be solved efficiently otherwise. "Modern" programming still relies on efficiency. The use cases which pushed the implementation of these features came primarily from CoreCLR in implementing the CLR on platforms other than Windows. So you might not rely on these features directly, but you (and everyone) benefits from the fact that critical components in the underlying frameworks do take advantage of them.

@jnm2
Copy link
Contributor

jnm2 commented Dec 18, 2017

@MohammadHamdyGhanem Please don't post identical content on multiple issues. #497 (comment)

@tannergooding
Copy link
Member

There are two types of null ref returns.

The first, where the content of the ref is null, which requires you to declare some variable with its value set to null and then return a ref to that (as per the OP).

The second is where the ref itself is null, which today can be simulated using System.Runtime.CompilerServices.Unsafe.AsRef<T>(null) (this converts a null pointer to a managed ref).

The latter is not expressible directly in C# today, but is still useful in a number of scenarios, such as Interop code.

@jnm2
Copy link
Contributor

jnm2 commented Dec 19, 2017

Ah, so we're already in a world where any ref could be null.

@mikedn
Copy link

mikedn commented Dec 19, 2017

Ah, so we're already in a world where any ref could be null.

Not really. The language doesn't have such a concept. And somewhat strangely, the ECMA spec too claims that byrefs cannot be null.

System.Runtime.CompilerServices.Unsafe.AsRef(null)

That's an abomination. Or in the more civilized C++ speak (C++ too doesn't support null references but you can produce one by dereferencing a null pointer) it is "undefined behavior".

@jnm2
Copy link
Contributor

jnm2 commented Dec 19, 2017

Ah, so we're already in a world where any ref could be null.

Not really. The language doesn't have such a concept.

Doesn't matter if the language has syntax for it. It was never possible before to make UseRef NRE:

public unsafe static void Main()
{
    ref var x = ref System.Runtime.CompilerServices.Unsafe.AsRef<object>(null);
    UseRef(ref x);
}

public static void UseRef(ref object someRef)
{
    var local = someRef;
}

The only protection is to do something like this:

public static void UseRef(ref object someRef)
{
    unsafe
    {
        if (System.Runtime.CompilerServices.Unsafe.AsPointer<object>(ref someRef) == null)
            throw new ArgumentNullRefException(nameof(someRef));
    }

    var local = someRef;
}

@mikedn
Copy link

mikedn commented Dec 19, 2017

It was never possible before to make UseRef NRE:

It was always possible, it was only more difficult (you likely needed to write something like AsRef yourself and use IL for that). The fact that the underlying runtime and framework provide a way to produce a null ref doesn't imply that the language has this concept. Just like the existence of T As<T>(object o) where T : class doesn't imply that the language (or the runtime) tolerates casting an object to an arbitrary/unrelated type.

@jnm2
Copy link
Contributor

jnm2 commented Dec 19, 2017

Of course the language doesn't have the concept. Like I said, that's not the point I was making. We're in a world where such a method is in the BCL and such null references are for the first time likely to start happening in the wild.

@mikedn
Copy link

mikedn commented Dec 19, 2017

We're in a world where such as method is in the BCL and such null references are for the first time likely to start happening in the wild.

Yes, I understand that. And the point I was trying to make is that it probably doesn't make sense to worry about null refs too much. Just like the existence of As<T> doesn't mean that you need write code like:

void foo(string s) {
    if (s.GetType() != typeof(string)) {
        throw new ArgumentException("?!"); // A suitable error message for a suitable situation
    }
}

@jnm2
Copy link
Contributor

jnm2 commented Dec 19, 2017

@mikedn Oh, that's insane. Okay. I agree with the point that such checks don't scale and won't have ROI, so we may as well not do them. But we'll still need to know about them more often now that a BCL method call in a non-/unsafe project can also do stupid things and make it through the runtime type system.

@tannergooding
Copy link
Member

@mikedn, @jnm2. I think that a null ref is a bit different from lying about a type.

Receiving a ref that is null can easily happen by writing P /Invoke or COM Interop method that uses ref instead of pointers on a blittable type. It doesn't necessarily require you to use S.R.CS.Unsafe (where-as there are actual checks for managed types that would prevent you from doing the equivalent of As{T} since they require actual marshaling).

@tannergooding
Copy link
Member

So there are actual places where checking if a ref is null may be required and where they make sense. Although I am not sure if that qualifies it for needing language support.

@mikedn
Copy link

mikedn commented Dec 19, 2017

Receiving a ref that is null can easily happen by writing P /Invoke or COM Interop method that uses ref instead of pointers on a blittable type

Examples? The claim is dubious as until recently ref returns did not exist so I don't see how a P/Invoke or COM Interop method could ever return a null ref, they cannot return ref at all. And even if now P/Invoke perhaps supports ref returns it still doesn't mean that they should be used in cases where the native method can actually return null.

@MkazemAkhgary
Copy link

return by reference needs a place in memory (valid reference) so if caller wants to assign to returned reference it can do so.

    object value = null;
    ref object RefReturnNull() => ref value;
    void SetValue(object value) => RefReturnNull() = value;

If you call SetValue for first time, RefReturnNull returns a valid reference, even though value of object is null but reference is not null.

if you were to return null reference then you could get lot of unexpected exceptions. you can not assign. you cant access to any member of that object simply because there is no reference.

@ghost ghost closed this as completed Mar 4, 2019
This issue was closed.
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

7 participants