Open lucasmeijer opened 6 years ago
I feel like you're trying too hard to use ref struct
everywhere. Always keep in mind:
@Joe4evr I don't think that comment is necessarily apropos.
ref fields are not directly expressible in IL.
You can very close to the desired behavior if there is a public type like: https://github.com/dotnet/corert/blob/796aeaa64ec09da3e05683111c864b529bcc17e8/src/System.Private.CoreLib/src/System/ByReference.cs
The bigger problem (and one of the reasons the type is not public) is tracking of life times.
It is assumed right now that that methods that take a ref
and return a ref-like
cannot return that same ref wrapped in a ref-like. Allowing/assuming such wrapping would make use of ref
and ref-likes
very difficult.
Such wrapping could be ok though, if wrapped ref is guaranteed to refer to a heap location. There are ideas how this could be allowed via some specialized syntax like:
ByReference<int> r = ref inArray[0]; // ok since array element is definitely on the heap
Just wondering, if the feature comes with "only on the heap" restriction, would it be acceptable limitation?
You can very close to the desired behavior if there is a public type like:
That is possible but we intend to use this for simplifying our API's significantly for our upcoming new component system. We want the syntax to be straightforward, and typing .Value is quite annoying for what is a very common pattern in that Component system.
For our use case:
For our particular use case, we can live with any restriction on what you can assign to it. Internally we are patching a pointer to the data in by writing to memory at an offset relative to the adress of the Group struct.
But certainly for the feature to be useful in general beyond our use case it seems useful to be able to assign to the ref fields from the constructor
Stack-only structs were introduced in C# 7.2. See https://github.com/dotnet/csharplang/issues/666
This (a ref stored in a struct) would need to be supported by the CLR before/when it could be supported by C#.
Can you expand the sample to include a couple of other use cases? In particular:
class
or interface
fields)? Our full example usage code looks like this:
struct BoidData : IComponentData
{
public float3 Position;
}
struct MyTransform : IComponentData
{
public float3 Position;
}
class MyComponentSystem : ComponentSystem
{
ref struct MyGroup
{
public ref BoidData Boid;
public ref MyTransform Transform;
}
ComponentGroup<MyGroup> m_Group = new ComponentGroup<MyGroup>();
void OnUpdate()
{
foreach(var entity in m_Group)
{
entity.Transform.Position = entity.Boid.Position;
}
}
}
The expected behaviour would be to transform this:
ref struct MyGroup
{
public ref BoidData Boid;
public ref MyTransform Transform;
}
to this:
ref struct MyGroup
{
private Boid* _boid;
public ref Boid Boid { { get ref *_boid; }
private MyTransform* _transform;
public ref MyTransform Transform { { get ref *_transform; }
}
How do you imagine such a struct being initialized? Sample code would be helpful here.
For our specific use case we do not require initializing ref values on the struct. We are writing to the pointers by offset using unsafe code inside the enumerator that GetEntities returns.
That is our use case. I can however imagine that being able to assign the ref in the constructor would cover other use cases for this.
Do you need this feature for only unmanaged types (transitively contains no class or interface >fields)?
Our use case is where the ref fields to Boid and MyTransform above are blittable. Thats our current requirements. We probably don't want to support interfaces / class types inside of Boid and MyTransform in the example above for other reasons. Not 100% on this, but if thats a hard requirement for some reason, we will find a way to live with it.
I would imagine that for other use cases allowing assignment to ref fields from the constructor would be the sensible thing to do. This matches how it works in C++.
ref struct MyGroup
{
MyGroup(ref MyTransform t) { Transform = t; }
private ref MyTransform Transform;
}
@benaadams since you emoticon loved it, I imagine you have some use cases for this as well?
Just wondering, if the feature comes with "only on the heap" restriction, would it be acceptable limitation?
Yes
I imagine you have some use cases for this as well?
Gather ops (returning multiple refs); side car data (ref + info fields)
For our specific use case we do not require initializing ref values on the struct. We are writing to the pointers by offset using unsafe code inside the enumerator that GetEntities returns.
This feature request started with an ask that the feature be usable in safe code. This design seems to require there is an unsafe creator behind the scenes newing up these values.
This was mainly what I was hoping for when I heard about 7.2 ref struct. I thought ref fields was the main reason to introduce ref structs in C# 7.2.
This was mainly what I was hoping for when I heard about 7.2 ref struct. I thought ref fields was the main reason to introduce ref structs in C# 7.2.
The same for me. I planned to use them as wrappers for Span
s.
The same for me. I planned to use them as wrappers for Spans.
You can do that as the ref field is hidden in the Span e.g.
Invalid
struct Thing
{
Span<byte> span;
}
Valid
ref struct Thing
{
Span<byte> span;
}
Is bit wasteful for a Span of count 1; also introduces variability to the field (as could now be count > 1)
@benaadams
Valid
Great! 👍
@jaredpar
The requirement to initialize such fields could make it safe: Foe example,
ref struct MessageWrapper
{
private ref Header header;
private Span<byte> body; a
public MessageWrapper(ref Header header, Span<byte> body)
{
this.header = ref header;
this.body = body;
}
}
So MessageWrapper
s must be initialized either via excplicit ctor call or if the ref fields are public
via initializer. Parameterless ctors are disallowed.
ref var wrapper = new MessageWrapper { Header = ref header, Body = body };
The requirement to initialize such fields could make it safe:
Not really:
MessageWrapper Create()
{
var header = CreateHeader();
return new MessageWrapper(ref header, CreateBody());
}
// call this method and watch bad things happen
void Crash() {
var w = Create();
// do something with w.header here
}
To make that safe the language/runtime would need to recognize that header
needs to be in a pinned location with a lifetime at least as long as the MessageWrapper
object. You can play with this sort of thing today via a Span<T>
pointer trick:
ref struct MessageWrapper
{
private Span<Header> header;
private Span<byte> body;
public MessageWrapper(ref Header header, Span<byte> body)
{
this.header = SpanEx.DangerousCreate(ref header);
this.body = body;
}
}
public static class SpanEx
{
/// <summary>
/// Creates a new span over the target. It is up to the caller to ensure
/// the target is pinned and that the lifetime of the span does not escape
/// the lifetime of the target.
/// </summary>
/// <param name="target">A reference to a blittable value type.</param>
public static unsafe Span<T> DangerousCreate<T>(ref T target) where T : struct
{
return new Span<T>(Unsafe.AsPointer(ref target), 1);
}
}
You can move the unsafe code to a second assembly and mark the one containing MessageWrapper
safe, as long as Header
is not itself a ref struct
(otherwise you cannot use it as a generic parameter and this safety illusion breaks down). And then the lifetime of MessageWrapper
must be carefully managed (it cannot for example be returned if Header
is a local). Most of the relevant restrictions are enforced by the compiler by virtue of it being a ref struct
.
Wrapping the "ref
field" in a Span<T>
is a tiny bit goofy (a ByReference
type or actual support would be more natural and efficient), but there are some neat tricks you can do once you have a span such as reinterpret internal structure as a Vector<T>
.
I entered https://github.com/dotnet/corefx/issues/26228 to make this a little nicer (don't expect much though, this sort of thing plays very loose with the safety boundaries of managed code). I sort of think requiring the unsafe flag might be reasonable for playing with Span<T>
objects of blittable types.
To make that safe the language/runtime would need to recognize that
header
needs to be in a pinned location with a lifetime at least as long as theMessageWrapper
object.
You mean like this: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md ?
I am not sure if that is enough because Header
here is not a ref struct
(and ref struct
types cannot be held inside a Span<T>
as the generic argument, you have to trick it to see the underlying bytes and reinterpret as a not-ref struct
). This program fails (every time for me with the above ) with the exception "Header not valid here":
public class Program {
static MessageWrapper Create()
{
var header = CreateHeader();
header.one = 1;
var w = new MessageWrapper(ref header, CreateBody());
if (w.header[0].one != 1) throw new InvalidOperationException("Header didn't make it");
return w;
}
static Span<byte> CreateBody() => default;
static Header CreateHeader() => default;
public static void Main()
{
var w = Create();
// anything that causes a GC event or sufficiently
// modifies the stack should cause breakage here?
Thread.Sleep(1);
// do something with w.header here
if (w.header[0].one != 1) throw new InvalidOperationException("Header not valid here");
}
}
public ref struct MessageWrapper
{
public Span<Header> header;
public Span<byte> body;
public MessageWrapper(ref Header header, Span<byte> body)
{
this.header = SpanEx.DangerousCreate(ref header);
this.body = body;
}
}
public static class SpanEx
{
/// <summary>
/// Creates a new span over the target. It is up to the caller to ensure
/// the target is pinned and that the lifetime of the span does not escape
/// the lifetime of the target.
/// </summary>
/// <param name="target">A reference to a blittable value type.</param>
public static unsafe Span<T> DangerousCreate<T>(ref T target) where T : struct
{
return new Span<T>(Unsafe.AsPointer(ref target), 1);
}
}
public struct Header {
public byte one;
}
How about creating a "RefWrapper" or a "SingleSpan" ref struct which is almost same as Span but doesn't have a length field?
The reason the Span<T>
type is useful is because it is public and already has the associated apis for Span<T>
. Any other name might as well be the ByReference
type. This type already exists and represents the functionality desired in this issue, but is internal because it is very easy to use it to do things that break assumptions we depend on as well as a question of how much support it should get I assume.
I think most of us looking for ref
fields would be satisfied if that type was made public and effectively ignored much like how TypedReference
and related features are treated. That would still likely mean significant additional work for future changes to the runtime.
@VSadov, what do you say on this? There is no way to add ref-fields directly to ref struct, as the problem of the default instances arises: the default instances of such structs would contain refs to nowhere. Thus not so much ref-fields itself need to be introduced in IL as much some sort of the monad Maybe for refs has to be implemented. if ref-fields are introduced in IL:
public readonly ref struct NullableRef<T>
{
public NullableRef(ref T r) => _ref = r;
private readonly ref T _ref;
public bool IsNull => Unsafe.AsPointer(ref _ref) == null;
public ref T Ref => IsNull ? throw new NullReferenceException() : _ref;
}
otherwise:
public readonly ref struct NullableRef<T>
{
public NullableRef(ref T r) => _ref = new ByReference<T>(ref r);
private readonly ByReference<T> _ref;
public bool IsNull => Unsafe.AsPointer(ref _ref.Value) == null;
public ref T Ref => IsNull ? throw new NullReferenceException() : _ref.Value;
}
@joeante wrote:
The referenced data is allocated from C++ via malloc.
If you're willing to switch over from malloc to allocating your structs in large C# arrays, then you could use the solution that I describe in #2417 in order to eliminate frame rate stalls/stutters caused by GC, without resorting to unsafe code.
An additional advantage: When you use malloc, then obviously you must later free the memory manually, but when you allocate your structs in C# arrays (call them "pages"), then each array ("page") is automatically garbage-collected when no more references to that "page" exist, as you know. Thus you can easily manage and balance the costs by merely changing the length of the arrays/pages that you create.
In the case of a game where frame rates are important, you'd create big arrays/pages to keep GC to the minimum, or potentially never release the arrays/pages while the user is still playing the game and not between levels etc. You would have the control AND the necessary performance, without resorting to unsafe code.
When allocating a struct, should you search for a free slot in an array/page? If you want to, you can, but note that a simpler solution is satisfactory in many apps: Don't reuse any slots. When the page is full, simply allocate a new page, and the previous page will be automatically garbage-collected when all of its slots become unused. This GC cost is low and completely satisfactory for most apps, but if your game is very sensitive to frame rate stalls, then you could simply increase the page size (array length), and if this GC cost is still not low enough, then you could keep track of which slots are free and reuse slots.
You could start using the solution already today with the current version of C#, but in order to make it a comfortable and convenient solution, C# needs the syntactic sugar that I describe in #2417, which is mostly the same syntax as @lucasmeijer described ("ref Color color;") except designed for pointing to elements of C# arrays instead of unsafe C++ malloc. Also note that lucasmeijer wrote "ref struct Group {...}" but in my proposal, both "Group" and "Color" would be normal structs, whereas the field "ref Color color;" is special.
As I understand it, you would be required to supply the ref
from elsewhere in order to initialize a ref
field, correct?
Would it be possible to have a struct containing fields itself that are used by ref
? For example, something like this, but expressible safely:
unsafe struct Example
{
Vector3 position;
Quaternion rotation;
public ref Vector3 Position => ref *(Vector3*)Unsafe.AsPointer(ref position);
public ref Quaternion Rotation => ref *(Quaternion*)Unsafe.AsPointer(ref rotation);
}
Which would allow for more convenient syntax, such as:
instance.Position.X += 1;
When using properties.
@DaZombieKiller Why go through those hoops when you can just do public ref Vector3 Position => ref position;
?
@Joe4evr
A struct member cannot return a reference to this
or any of its members. Hence the need for pointers and unsafe code there. Your example would ultimately be the preferred syntax if it were valid though, yes.
@DaZombieKiller:
public ref Vector3 Position => ref (Vector3)Unsafe.AsPointer(ref position);
@Joe4evr: Why [can't you] just do
public ref Vector3 Position => ref position;
@DaZombieKiller: A struct member cannot return a reference to
this
or any of its members.
I did my best to briefly write-up some of these issues in a StackOverflow answer. The relevant point for this exchange is that you can use the System.Runtime.CompilerServices.Unsafe
package to round-trip a struct's this
pointer through IntPtr
in order to work around the (overzealous) fatal compiler errors.
"Because
ref struct
code that should properly be allowed to return its ownthis
is still prevented by the compiler from doing so, you have to resort to some rather brutish techniques to get around the fatal error(s) when coding within the supposedly liberatedref struct
instance methods. To wit, value-type instance methods written in C# that legitimately need to override fatal errorsCS8170
/CS8157
can opaque the 'this' pointer by round-tripping it through anIntPtr
."
Using the As
methods--as opposed to IntPtr.ToPointer()
--from the aforementioned library should allow you to entirely avoid declaring the unsafe
keyword in C# for the Example
struct shown above.
The scenarios outlined by @lucasmeijer and @joeante - are in effect a different way of doing interop. This technique (which I also use) makes it easier (by which I mean more intuitive and simpler code) for managed code to access data in unmanaged memory.
Managed and unmanaged code/threads could access the same memory region in a very natural way by leveraging ref
fields.
Recent enhancements to ref
have had a big effect on simplifying (and thus reducing scope for error) some of my own low level code, so much so that what used to be done in C is now being moved straight to C#.
I should stress that this is only sought for unmanaged
structs in my case.
As someone suggested, having ref field is very similar to having Span of fixed size one. It seems to me that main problem is the safe initialization of Span with reference to a field, correct?
So far Span can be initialized only with:
Initialization of Span with ref field could be done like this:
Now, is it possible to determine whether ref field points to stack or heap?
As someone suggested, having ref field is very similar to having Span of fixed size one.
It's similar enough that conceptually you can think of them as equivalent. From a language perspective though there are some subtle differences that need to be dug into. Mostly around the "safe to escape" rules. Those currently treat ref struct
and ref
differently hence need to stare at those for a few hours to see if they're truly the same here.
Initialization of Span with ref field could be done like this
I think you're approach the problem from the wrong angle here. The key is not verifying the life time of the value the ref
field points to but instead ensuring that the ref
field is always assigned. Once we know that it's always assigned, given a set of input expressions, then we can calculate the lifetime of the field and it's container using the existing rules.
For the most part I think this is pretty straight forward. A struct
with a ref
filed has virtually all the same limitations as a ref struct
: can't be used as generic argument, can't be the element type of an array, etc ... Hence you're mostly left with the following cases:
struct
with a default ctor and ensuring all it's fields are assigned via object initializerref
fields with a value that is safe to escape from the constructor. This isn't a new rule but just application of existing rules to the new construct. Would it be possible to allow null ref fields in ref structs
?
I don't mean ref field pointing to null object, I mean ref field pointing nowhere (like IntPtr.Zero).
I don't like this at all, but I'm curious whether it would be technically possible.
CLR is able to represent null ref field, since default(Span<T>)
is valid construction.
I'm not sure whether it's possible to test for null ref field, but I don't see a reason why not.
Anytime ref field in ref struct
is accessed or passed into function, this test would be performed and NullReferenceException
would be thrown in case it's null.
As said above, this goes against the philosophy or ref
(always pointing to valid location) and I don't like it. I'm just trying to explore possible options.
Would it be possible to allow null ref fields in ref structs? I don't mean ref field pointing to null object, I mean ref field pointing nowhere (like IntPtr.Zero). I don't like this at all, but I'm curious whether it would be technically possible.
This is legal at a CLR level for ref
parameters today and would also be legal for ref
fields. There is no CLR language I'm aware of though which exposes this. Hence providing a null
for a ref
is akin to providing 2 for a bool
value. It's legal but no one expects it hence you won't get the behavior that you're expecting.
Concretely though C# does not allow ref
to have a null
value in any of the places ref
is allowed today. There isn't even a way to test for null
. This policy would be extended to ref
field if they were added.
I'm not sure whether it's possible to test for null ref field, but I don't see a reason why not.
It's not possible to test for null
in C# on a ref
.
CLR is able to represent null ref field, since default(Span
) is valid construction.
The Span<T>
type is different here because it's a CLR primitive. It doesn't necessarily need to follow all of the rules you'd expect if Span<T>
were defined literally as a (ref T Data, int length)
style struct
. Hence you can't always extrapolate behavior for it to ref
fields.
Still though not sure why you're going down this particular route. Forcing a ref
field to always point to a value is very straight forward. It doesn't change the usage of the type in any significant way compared to ref struct
in general.
@jaredpar wrote: Forcing a
ref
field to always point to a value is very straightforward.
And don't forget that such a fallback 'value' can be a static singleton object of an appropriate type, allocated/designated as a "sentinel" for that purpose. In general this approach goes beyond the idea of simply distinguishing the null
ref, because you can then conceivably have several different sentinels to indicate different logical conditions.
The approach is especially useful when the type is question is a primitive or value-type which is designed with a 100% domain-efficient memory image (no wasted bits). In this case, each/any static sentinel declaration still receives a distinct ref
identify (its address), despite there existentially being no in-domain "value" to usurp for out-of-domain or exceptional/administrative conditions or purposes.
Couple more points/questions on static value-type sentinels. Please correct or amplify if I'm off track:
Global/static value-type sentinel fields should never be marked readonly
. Seems like doing so could cause the address—which is the whole point here—to be lost to defensive copying? Although Roslyn gives some guidance here via the various in
/ref
warnings, errors, and prohibitions, I still vaguely sense that there are silent defensive copy scenarios that I haven't fully understood, and which are always mooted by making the field not readonly
.
Also as a rule, I always apply the [FixedAddressValueType]
attribute to fields that are intended for use as a static value-type sentinel. But I confess that, even less here than for my previous point, I haven't fully thought through the implications of whether or why this might matter in the context of guaranteeing the reliable distinguishing of well-known sentinel ref
addresses.
Comments/clarifications?
In the meantime, would this work?
readonly ref struct Foo<T>
{
private readonly Span<T> _bar;
public Foo(in T bar) => _bar = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(bar), 1);
public readonly ref T Bar => ref _bar[0];
}
It will compile but it’s definitely unsafe. The lifetime of the values will not be tracked properly. It’s easy to turn this into a runtime crash.
The lifetime of the values will not be tracked properly. It’s easy to turn this into a runtime crash.
@jaredpar could you expand on why that is? And how this differs from what you can do with Span right now?
MemoryMarshal is used here to defeat C# escape analysis and thus the construct is unsafe. Among other things It would allow returning ref to a local variable.
Something like this can lead to GC holes:
object local = “hello”;
return ref (new Foo<object>(in local).Bar);
Hi @VSadov, Let's have a look at the next two samples:
Span<char> Test(bool f, Span<char> outerSpan1, Span<char> outerSpan2)
{
var innerSpan = (Span<char>)stackalloc char[] { 'a' };
var res = f ? outerSpan1 : outerSpan2;
return res;
}
Span<char> Test(bool f, Span<char> outerSpan1, Span<char> outerSpan2)
{
var innerSpan = (Span<char>)stackalloc char[] { 'a' };
var res = f ? outerSpan1 : innerSpan;
return res;
}
The compiler recognizes the former sample as totally correct while it complains about the latter one.
This proves that despite the fact that both variables/parameters outerSpan2 and innerSpan have apparently the same type Span<char>
, the compiler implicitly assigns different types to them so the expression f ? outerSpan1 : outerSpan2
results in a type "outer Span<char>
" while the expression f ? outerSpan1 : innerSpan
results in a type "inner Span<char>
" and thus the latter result cannot be safely returned.
Maybe it's worth to consider explicitly exposing the difference between inner and outer Spans and use this approach for tagging inner and outer refs to any structs/vars so it could be obvious that you cannot return a ref to a local struct/var to the outer scope.
MemoryMarshal is used here to defeat C# escape analysis and thus the construct is unsafe.
@VSadov right, that's what I thought :) This is identical to:
public ref object RefGCHoleViaMemoryMarshal()
{
object local = "hello";
return ref (MemoryMarshal.CreateSpan(ref local, 1)[0]);
}
so is already possible today, but that is unsafe by definition so fine. To sum up, just for my own understanding, there are two main issues around adding this feature:
ref
fields are not directly expressible in ILref
members to ensure local ref
s are not leakedIs that correctly understood?
In the meantime there is a way to express this using the "slow" Span trick to "point" at managed and native memory but for just one element. I call this a View0D
or Span0D
. I am tinkering with n-dimensional memory views in https://github.com/DotNetCross/Memory.Views utilizing the "slow" Span or Joe Duffys https://github.com/joeduffy/slice.net trick. Anyway a single element reference can be expressed as:
public readonly struct View0D<T>
{
readonly object _objectOrNull;
readonly IntPtr _byteOffsetOrPointer;
public View0D(T[] array, int index)
{
// Checks omitted
_objectOrNull = array;
_byteOffsetOrPointer = Unsafe.ByteOffset(_objectOrNull, ref array[index]);
}
public ref T Element => ref GetPinnableReference();
public ref T GetPinnableReference() =>
ref Unsafe.RefAtByteOffset<T>(_objectOrNull, _byteOffsetOrPointer);
this of course isn't as fast having ref
members directly, but to my knowledge it is the best you can do currently if you want to be able to point to both managed and unmanaged/pinned memory or simply want to avoid pointers. Note this uses https://github.com/DotNetCross/Memory.Unsafe not the BCL CompilerServices.Unsafe
.
In the above the struct is not a ref struct
. This is intentional and is to support scenarios that I need this for. Just add ref
and perhaps call it Span0D
for your needs. cc: @safakgur
Of course, for @lucasmeijer's original problem, when you want to have multiple "pointers", this gets heavier, which I guess is actually the main issue here. If you only had one "pointer" you could just return the ref
directly. Hence, in other words this is a
How do I return multiple ref
s?
problem.
Among other things It would allow returning ref to a local variable.
Thank you - what are the other things, if I may ask? I'm trying to understand if the following scenario is safe:
public void Some<T>(in T bar)
{
var foo = new Foo<T>(bar);
Other1(foo);
Other2(foo);
}
private void Other1<T>(in Foo<T> foo) { /*...*/ } // Does not store foo or foo.Bar
private void Other2<T>(in Foo<T> foo) { /*...*/ } // Does not store foo or foo.Bar
the following scenario is safe
@safakgur if you can write it with regular ´ref`s it should be safe e.g.
public void Some<T>(in T bar)
{
ref readonly var foo = ref bar;
Other1(foo);
Other2(foo);
}
private void Other1<T>(in T bar) { /*...*/ }
private void Other2<T>(in T bar) { /*...*/ }
should work. Hence is safe. Again, I'm not sure what the exact use case here, but I guess it involves multiple ref
s. Perhaps, you could expand on that?
Forcing ref fields to always reference seems to be very difficult. But what if we could add a new nullable ref type?
ref? int vr;
To me it would be a lot more flexible if ref fields were nullable.
@AJosephsen Not sure if this is what you meant, but ref
variables are really just GC-tracked pointers, so being pointers they're already "nullable". For instance, you can do this:
ref int foo = ref Unsafe.AsRef<int>(null);
Actually they've also added a shorthand method for that right into the Unsafe
class now, here.
if you wanna be technical.... 😛 from ecma 335
@john-h-k I didn't mean that they are allowed to be null by the specs (in fact the whole Unsafe
class is also marked as not CLS compliant), just that being simply pointers (ie. they're just passed around as native integers at the end of the day) they can in fact be null as well. Even if the specs forbids this, there are quite a few places where CoreCLR does this, and the runtime itself is perfectly able to handle them properly (eg. just throwing a NullReferenceException
if you dereference one). Eg. here you can see one usage in Span<T>.GetPinnableReference
. That bit from the specs was interesting though, thanks!
Span<byte> span = default
might be a simpler example of a stored null ref
and expected api for accessing it MemoryMarshal.GetReference(span)
;
GetPinnableReference
is a bit more special as its used by the fixed
statement, so it additionally wants to return a null ref
for length zero.
@Sergio0694 very fair point, tho worth noting:
(in fact the whole Unsafe class is also marked as not CLS compliant)
CLS compliance is NOT tied, or even related, to runtime compliance. This doesn't say managed pointers being null is not CLS compliant, but rather it is not required to be allowed by a ECMA compliant runtime
However, yeah, Unsafe.NullRef<T>
/ Unsafe.AsRef<T>(null)
really defeats this (not that I am complaining - I love it and used it earlier today). While coreclr can make the assumptions about it, because it is tied to the coreclr impl, I guess if you want to nitpick, a different runtime (e.g an interpreter), may not accept null managed pointers. So while library code can, Roslyn generating them would be a bit more iffy
Span
span = default might be a simpler example of a stored null ref
Yep, but that span is an implementation detail of coreclr
I think it is closed already https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#ref-struct
No, this is different. This is ref fields, a thing not allowed in the language today.
Unity is a game engine that uses C# extensively.
We're in need of a way to have a struct that contains one or more pointers to other structs that we want to be able to read/write from/to. We do this with unsafe code today:
But we would love to do this without unsafe code, and would like to suggest a feature where it's allowed to store references to structs in fields of ref structs, that can guarantee that these pointers don't escape onto the heap:
EDIT link to spec for this feature https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md