dotnet / csharplang

The official repo for the design of the C# programming language
11.41k stars 1.02k forks source link

allow references to structs to be stored in fields of ref structs #1147

Open lucasmeijer opened 6 years ago

lucasmeijer commented 6 years ago

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:

    struct Color { float r,g,b; }

    unsafe struct Group
    {
      Color* _color;
      ref Color color => ref *_color;
    }

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:

    struct Color { float r,g,b; }

    ref struct Group
    {
        ref Color color;
    }

EDIT link to spec for this feature https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md

Joe4evr commented 6 years ago

I feel like you're trying too hard to use ref struct everywhere. Always keep in mind:

jnm2 commented 6 years ago

@Joe4evr I don't think that comment is necessarily apropos.

VSadov commented 6 years ago

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?

joeante commented 6 years ago

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

gafter commented 6 years ago

Stack-only structs were introduced in C# 7.2. See https://github.com/dotnet/csharplang/issues/666

gafter commented 6 years ago

This (a ref stored in a struct) would need to be supported by the CLR before/when it could be supported by C#.

jaredpar commented 6 years ago

Can you expand the sample to include a couple of other use cases? In particular:

joeante commented 6 years ago

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.

joeante commented 6 years ago

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?

benaadams commented 6 years ago

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)

jaredpar commented 6 years ago

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.

AJosephsen commented 6 years ago

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.

omariom commented 6 years ago

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 Spans.

benaadams commented 6 years ago

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)

omariom commented 6 years ago

@benaadams

Valid

Great! 👍

omariom commented 6 years ago

@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 MessageWrappers 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 };
bbarry commented 6 years ago

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.

gafter commented 6 years ago

To make that safe the language/runtime would need to recognize that headerneeds to be in a pinned location with a lifetime at least as long as the MessageWrapperobject.

You mean like this: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md ?

bbarry commented 6 years ago

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;
}
aobatact commented 6 years ago

How about creating a "RefWrapper" or a "SingleSpan" ref struct which is almost same as Span but doesn't have a length field?

bbarry commented 6 years ago

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.

AleckAgakhan commented 6 years ago

@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;
}
verelpode commented 6 years ago

@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.

DaZombieKiller commented 5 years ago

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.

Joe4evr commented 5 years ago

@DaZombieKiller Why go through those hoops when you can just do public ref Vector3 Position => ref position;?

DaZombieKiller commented 5 years ago

@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.

glenn-slayden commented 5 years ago

@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 own this 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 liberated ref struct instance methods. To wit, value-type instance methods written in C# that legitimately need to override fatal errors CS8170​/​CS8157 can opaque the 'this' pointer by round-tripping it through an IntPtr."

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.

Korporal commented 5 years ago

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.

OndrejPetrzilka commented 5 years ago

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:

  1. interior pointer of managed array (+length)
  2. stackalloc (implicit length)
  3. unsafe pointer (+length)

Initialization of Span with ref field could be done like this:

  1. when field is on heap - use interior pointer of managed object (same as managed array init)
  2. when field is on stack - use same initialization as stackalloc I think same rules applies as for stackalloc. Since Span is on stack, we can guarantee Span gets "released" first before the data it references are "released".

Now, is it possible to determine whether ref field points to stack or heap?

jaredpar commented 5 years ago

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:

OndrejPetrzilka commented 4 years ago

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.

jaredpar commented 4 years ago

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.

glenn-slayden commented 4 years ago

@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:

  1. 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.

  2. 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?

safakgur commented 4 years ago

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];
}
jaredpar commented 4 years ago

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.

nietras commented 4 years ago

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?

VSadov commented 4 years ago

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);
AleckAgakhan commented 4 years ago

Hi @VSadov, Let's have a look at the next two samples:

  1.     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;
        }
  2.     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.

nietras commented 4 years ago

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:

Is 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 refs?

problem.

safakgur commented 4 years ago

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
nietras commented 4 years ago

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 refs. Perhaps, you could expand on that?

AJosephsen commented 4 years ago

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.

Sergio0694 commented 4 years ago

@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.

john-h-k commented 4 years ago

image if you wanna be technical.... 😛 from ecma 335

Sergio0694 commented 4 years ago

@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!

benaadams commented 4 years ago

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.

john-h-k commented 4 years ago

@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

john-h-k commented 4 years ago

Span span = default might be a simpler example of a stored null ref

Yep, but that span is an implementation detail of coreclr

AlexRadch commented 3 years ago

I think it is closed already https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#ref-struct

333fred commented 3 years ago

No, this is different. This is ref fields, a thing not allowed in the language today.