Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't a local variable of type CancellationToken need initialization? [duplicate]

I notice that the following will compile and execute even though the local variables are not initialized. Is this a feature of Span?

void Uninitialized()
{
  Span<char> s1;
  var l1 = s1.Length;

  Span<char> s2;
  UninitializedOut(out s2);
  var l2 = s2.Length;
}

void UninitializedOut(out Span<char> s)
{}
like image 292
jyoung Avatar asked Apr 04 '18 14:04

jyoung


People also ask

How do you use unassigned local variables?

TLDR: Initialize a variable to null to indicate to the compiler that you plan to assign it later. Suppose you have a situation where you need to create a variable but you will be assigning a value to it within a conditional statement (if, for, foreach, while, etc).

What is an unassigned variable?

In computing, an uninitialized variable is a variable that is declared but is not set to a definite known value before it is used.

How do I fix CS0165?

The CS0165 error is caused when a variable created within a method is not assigned with a value using the new keyword. The error CS0165 is resolved by assigning the local variable with a new instance of it's type or as a reference to a variable of the same type.


3 Answers

Marc has a great answer. I wanted to elaborate a bit on the history / context.

First of all this is definitely a compiler bug. By rules of definite assignment this local is not definitely assigned and any usage should be an error. Unfortunately this bug is hard to fix for number of reasons:

  • This bug is old and goes back to at least C# 4.0. That gives customers 7+ years to inadvertently take a dependency on it
  • There are a number of structs in the BCL which have this basic structure. For example CancellationToken.

Those taken together mean fixing this would likely break a large amount of existing code. Despite this the C# team attempted to fix the bug in C# 6.0 when the bug was much younger. But an attempt at compiling the Visual Studio source with this fix showed that the fears around customers taking a dependency on this bug were well founded: there were a number of build breaks. Enough to convince us it would have a negative impact on a significant amount of code. Hence the fix was undone.

The second problem here is this bug wasn't known to all the compiler team members (before today at least). Been ~3 years since the fix was undone and had a bit of turn over since then. The team members who verified how we were generating reference assemblies for Span<T> weren't aware of this bug and recommended the current design based on the language spec. I'm one of those developers :(

Still discussing this but most likely we're going to update the reference assembly strategy for Span<T>, and other types, so that it avoids this compiler bug.

Thanks for reporting this. Sorry about the confusion caused :(

like image 110
JaredPar Avatar answered Oct 16 '22 14:10

JaredPar


More or less this is by design, since it depends heavily if the underlying struct holds any fields itself.

This code compiles for example:

public struct MySpan<T>
{
    public int Length => 1;
}

static class Program
{
    static void Main(string[] args)
    {
        MySpan<char> s1;
        var l1 = s1.Length;
    }
}

But this code doesn't:

public struct MySpan<T>
{
    public int Length { get; }
}

static class Program
{
    static void Main(string[] args)
    {
        MySpan<char> s1;
        var l1 = s1.Length;
    }
}

It seems that in that case, the struct is defaulted, and that is why it doesn't complain about a missing assignment. That it doesn't detect any fields is a bug, as explained in Marc's answer.

like image 34
Patrick Hofman Avatar answered Oct 16 '22 15:10

Patrick Hofman


This looks like an issue caused by reference assemblies, required because of the way that Span<T> has framework-specific internals.

This means that in the reference assembly: there are no fields (edit: this isn't quite true - see footnote).

A struct is considered assigned (for the purposes of "definite assignment") if all fields are assigned, and in this case the compiler is seeing "all zero of zero fields have been assigned: all good - this variable is assigned". But the compiler doesn't seem to know about the actual fields, so it is being misled into allowing something that is not technically valid.

You definitely shouldn't rely on this behaving nicely! Although in most cases .locals init should mean you don't actually get anything too horrible. However, there is currently some work in progress to allow people to suppress .locals init in some cases - I dread to think what could happen in that scenario here - especially since Span<T> works much like a ref T - that could get very very dangerous if the field really isn't initialized to zero.

Interestingly, it might already be fixed: see this example on sharplab. Alternatively, maybe sharplab is using a concrete target framework, rather than reference assemblies.


Edit: very oddly, if I load the reference assembly into ildasm or reflector, I can see:

.field private initonly object _dummy

which is the spoofed field in the reference assembly that is meant to stop this from happening, but... it looks like it isn't working very reliably right now!


Update: apparently the difference here is a subtle but known compiler issue that remains for compatibility reasons; definite assignment of structs considers private fields of types that are known locally, but does not consider private reference-type fields of types in external assemblies.

like image 26
Marc Gravell Avatar answered Oct 16 '22 15:10

Marc Gravell