With C# 7.3 there's now the unmanaged generic constraint and it looks like it should be applied to MemoryMarshal.Cast before that becomes a breaking change
cc: @ahsonkhan, @jkotas
What do older compilers do when they see this constraint on that API?
@jnm2
The unmanaged constraint is implemented as modreq, with version 2.3 of roslyn you get
error CS0315: The type 'Consumer.Test' cannot be used as type parameter 'T' in the generic type or method 'MemoryMarshal.Cast<T>(T)'. There is no boxing conversion from 'Consumer.Test' to '?'.
error CS0648: '' is a type not supported by the language
Would also apply to anything that checked IsReferenceOrContainsReferences and then threw an exception?
public static partial class MemoryMarshal
{
public static T Read<T>(System.ReadOnlySpan<byte> source) where T : unmanaged { throw null; }
public static bool TryRead<T>(ReadOnlySpan<byte> source, out T value) where T : unmanaged { throw null; }
public static void Write<T>(Span<byte> destination, ref T value) where T : unmanaged { throw null; }
public static bool TryWrite<T>(Span<byte> destination, ref T value) where T : unmanaged { throw null; }
public static Span<byte> AsBytes<T>(Span<T> span) where T : unmanaged { throw null; }
public static ReadOnlySpan<byte> AsBytes<T>(ReadOnlySpan<T> span) where T : unmanaged { throw null; }
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span)
where TFrom : unmanaged where TTo : unmanaged { throw null; }
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span)
where TFrom : unmanaged where TTo : unmanaged { throw null; }
}
The unmanaged constraint is implemented as modreq, with version 2.3 of roslyn you get
error CS0315: The type 'Consumer.Test' cannot be used as type parameter 'T' in the generic type or method 'MemoryMarshal.Cast<T>(T)'. There is no boxing conversion from 'Consumer.Test' to '?'. error CS0648: '' is a type not supported by the language
Would that mean you could only call these apis with C#7.3+ ?
@benaadams Yes
In my opinion, lifting the required C# version to 7.3 isn't too much of a price to pay as all the Span stuff requires C# 7.2+ anyway and we gain additional compile time safety through this.
The unmanaged constrains are using modreq in a position where it was not ever used before. There are know issues in the existing tooling and runtimes that are not handling them correctly. It makes it risky to start using these at this point for 2.1. It has unpredictable bug-tail.
Other related discussions that touch of problems with unmanaged constrains:
My recomendation is won't fix.
The check and throw
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
could be left in the method body to act as a runtime guard where its not valid?
i.e. the runtime doesn't need to rely on modreq working correctly; but it would act as a compile time discovery if/when the tooling is handling it correctly?
Though, I don't really have enough of an understanding of modreq to form an opinion.
@GrabYourPitchforks pointed out it would be problematic to call these functions from a method that you switch paths on an if statement with RuntimeHelpers.IsReferenceOrContainsReferences<T>() as the non-reference path then couldn't call the AsBytes method for example as it wouldn't be correctly constrained (even though the test validated it was a correct type)
Right, the unmanaged constrain is both too limited and too dangerous at the same time. I do not think it is very useful. I wish that C# just removed the restriction on what can pointers point to instead (e.g. turned pointers pointing to potentially non-blittable types into warnings). It would get us on path towards removing dependency on S.R.CS.Unsafe and similar hacks.
@jkotas Is T* for arbitrary T actually valid IL? Or is it one of those things that just fails at JIT time if T happens to contain references, so as long as we never JIT such a method we're good?
T* is valid IL, for any T. It is just one of those things that C# does not let you create.
You can actually get arbitrary T* locals in C# because of C# allows consuming T* for any T:
T* AsPointerOfT<T>(void *p) and just returns p. BTW: I have looked into adding it to System.Runtime.CompilerServices.Unsafe but gave up on it because of the plumbing for the reference assembly would be very non-trivial.static void foo<T>(void * p, int length)
{
// pointerOfT is T*, for any T !!!
var pointerOfT = AsPointerOfT<T>(p);
for (int i = 0; i < length; i++)
pointerOfT[i].GetHashCode();
}
Most helpful comment
Right, the
unmanagedconstrain is both too limited and too dangerous at the same time. I do not think it is very useful. I wish that C# just removed the restriction on what can pointers point to instead (e.g. turned pointers pointing to potentially non-blittable types into warnings). It would get us on path towards removing dependency on S.R.CS.Unsafe and similar hacks.