dotnet / csharplang

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

ReadOnlySpan should allow ref return of readonly structs #2040

Closed dzmitry-lahoda closed 5 years ago

dzmitry-lahoda commented 5 years ago

Issue

Returning readonly struct from ReadOnlySpan as ref should be possible.

Why I want it

Considerations

Example

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace design
{
    [StructLayout(LayoutKind.Sequential)]
    readonly struct ReadPosition
    {
        public readonly float x;
        public readonly float y;
        public readonly float z;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct Position
    {
        public float x;
        public float y;
        public float z;
    }

    public static class Test
    {
        public static unsafe void Run()
        {
            var positions = new  Position[ushort.MaxValue];
            positions[0] = new Position {x = 13, y = 42, z = 123};

            // compiles fine - OK
            Span<Position> writeWrite = new Span<Position>(positions);
            ref Position a1 = ref writeWrite[0];
            a1.x = a1.x + 1;

            // fails to compile - OK
            ReadOnlySpan<Position> readWrite = new Span<Position>(positions);
            ref Position a2 = ref readWrite[0];//Cannot use property 'ReadOnlySpan<Position>.this[int]' as a ref or out value because it is a readonly variable 
            a2.x = a2.x + 1;

            fixed (void* positionsPointer = positions)
            {
                // fails to compile - OK
                Span<ReadPosition> writeRead = new Span<ReadPosition>(positionsPointer, positions.Length);
                ref ReadPosition a3 = ref writeRead[0];
                a3.x = a3.x + 1; // A readonly field cannot be assigned to (except in a constructor or a variable initializer)

                // fails to compile - BAD, I want it because readonly struct
                ReadOnlySpan<ReadPosition> readRead = new Span<ReadPosition>(positionsPointer, positions.Length);
                ref ReadPosition a4 = ref readRead[0]; // Cannot use property 'ReadOnlySpan<ReadPosition>.this[int]' as a ref or out value because it is a readonly variable
                a4.x = a4.x + 1;// fails and it is fine

                // I WANT READONLY SPAN TO REF READONLY STRUCT TO AVOID STRUCT COPY IF STRUCT IS VERY LARGE (10+ fields)
            }
        }
    }
}
NinoFloris commented 5 years ago

There are a few problems with this, 1. the CLR generics restriction of not allowing ref Type arguments, and 2. the C# language restriction of also not allowing ref-like (Span etc) type arguments either, mainly because the CLR does not even support the weaker versions.

https://github.com/dotnet/csharplang/blob/54fb167419f8fb901df1c4c838964d3f771510bc/proposals/csharp-7.2/span-safety.md

dzmitry-lahoda commented 5 years ago

@NinoFloris , I do not understand what you are saying. May be I can clarify.

Now:

  1. span + ref return possible.
  2. readonly span + ref return is not possible.

I want:

  1. span + ref return possible 2.1 readonly span + ref return is not possible if struct is writetable. 2.2 NEW readonly span + ref return is possible if struct is readonly.

Question: Is 2.2 unsound/leaky?

As I see you say that 2.2 not possible because of both CLR and C#. But I doubt that CLR has restrictions. Probably C# only restrictions do apply. I just cannot grasp what exactly CLR prevents from your answer.

dzmitry-lahoda commented 5 years ago
        class Readonly
        {
            private ReadPosition one;
            public ref ReadPosition this[int index] => ref one;
        }

        class Readonly2
        {
            private ReadPosition one;
            public ref readonly ReadPosition this[int index] => ref one;
        }  
            // compiles - OK
            var myReadonly = new Readonly();
            ref ReadPosition my = ref myReadonly[0];
            my = new ReadPosition();

            // does not compiles - BAD
            var myReadonly2 = new Readonly2();
            ref ReadPosition my2 = ref myReadonly2[0]; //does not compiles, but could
            my2 = new ReadPosition();// should be disallowed

So to restate:

  1. If indexer is readonly and struct is readonly than ref should be allowed in above case.
  2. ref returned from readonly index of readonly struct should prevent write to that ref. As in this case, readonly marker on struct is useless as I can create new struct and replace it by ref, essentially modify readonly struct.

So I guess 2 can be CLR limitation.

dzmitry-lahoda commented 5 years ago

ref readonly. Sorry for that. I have not found answer on SO...

dzmitry-lahoda commented 5 years ago

С# could tune error message which suggest to mark ref return as readonly as part of fix so :)