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)
{}
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 sinceSpan<T>
works much like aref 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: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.
More or less this is by design, since it depends heavily if the underlying
struct
holds any fields itself.This code compiles for example:
But this code doesn't:
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.
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:
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 :(