New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Replace TFrom : struct
constraint on Unsafe.BitCast
with a dynamic check
#99205
Comments
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices Issue DetailsBackground and motivationIn .NET 8 we've added the One justification was that this would let us avoid codegen pessimization for values marked as address-taken in some cases. Example from the original API proposal: #81334 (comment) Due to the With the constraint removed, I was able to confirm that we're able to flow the constness of values through for this case: MihaZupan@65c8054. Quoting the conclusion from the initial API review:
API Proposalnamespace System.Runtime.CompilerServices;
public static class Unsafe
{
public static TTo BitCast<TFrom, TTo>(TFrom value)
- where TFrom : struct
where TTo : struct;
} API Usagepublic static unsafe int IndexOfAny<T>(this Span<T> span, T value)
where T : IEquatable<T>?
{
if (RuntimeHelpers.IsBitwiseEquatable<T>() && sizeof(T) == sizeof(short))
{
return IndexOfValueType(
ref Unsafe.As<T, short>(ref MemoryMarshal.GetReference(span)),
Unsafe.BitCast<T, short>(value0), // <--- This is now legal
span.Length);
}
// ...
}
private static unsafe int IndexOfValueType<T>(ref T searchSpace, T value, int length)
where T : struct, INumber<T> => 42; Alternative DesignsWe could drop the constraint on RisksSome erroneous usages move from being compile-time errors to run-time exceptions.
|
I've also bumped up against this multiple times. I agree we should remove the constraint. |
This assumes that we won't get generic bridging anytime soon I guess? |
We should really be using these scenarios as evidence to better prioritize a feature that properly resolves the issue and only removing constraints where there is sufficient justification to handle it immediately and where a reasonable fallback can be given. That is to say, lack of bridging generic constraints is a major pain point and we can't simply remove all constraint checks and make them dynamic or analyzers because we've hit some places where it'd be beneficial to go from A to B. Removing constraints to work around the lack of bridging works today, but is also irreversible once its done and removes a level of safety. For ever case where we think removing the constraint would be beneficial, we really need to look deeper and see if there is a reasonable workaround and the implications of removing the constraint. For this particular scenario, we explicitly have 3 related APIs:
The first is for reinterpreting an object reference, the second for reinterpreting identically sized value types, and the third for reinterpreting arbitrary references. Given that, I think its better to have devs use the trivial workaround that is |
It's annoying to use in that capacity when dealing with the return value of a call, forcing you to store into a local. And a key reason BitCast was added was to handle the scenarios where Unsafe.As was already available and being used. Further, the constraint already isn't complete, in that you can still meet the constraint but be using args that fail the actual requirements of the method and still get an exception. Even if/when bridging support is available, it's simpler to consume without the constraint, and is already defined in a type that yells "there be dragons". If you get an exception from using it incorrectly, as you already can, so be it. |
I think this is going to come down to a difference of opinion. I agree some of the annoyances exist and that it would be great to get rid of them, I don't think those annoyances are sufficient justification to make the API even more unsafe, particularly where a valid workaround exists and we know we need a longer term language/runtime feature in this space. |
It doesn't make it any more unsafe, though. Invalid types would still produce an exception. It's purely a matter of whether misuse compiles or not, trading off whether valid use compiles or not, too. |
👍 to the proposal, the only two cases I ever needed this API I bumped into the same annoying limitation. |
Would |
This, which would evaporate for valid use via it being a JIT intrinsic. |
Invalid. It would throw at runtime, same as That is, there are already cases where you can compile a call to this API and have it throw at runtime, this just adds one more. |
That's not actually the case, it lets you reinterpret |
Ok, that sounds reasonable to me. |
TFrom : struct
constraint from Unsafe.BitCast
TFrom : struct
constraint on Unsafe.BitCast
with a dynamic check
Today our logic is basically this:
Do we plan to keep this logic when expanding to general
(obviously, the safety of each of the above is the same as the safety of This would result in the following APIs:
I think this is the set of APIs that makes the most sense conceptually and would be the most useful practically. |
My proposal is to keep the supported scenarios the same as part of this change. Passing in a The intention of the API as I understood it was to offer "safer" casting that would reject likely erroneous uses. |
This is already allowed...
I personally never understood this to be the intent - the only safety I'm aware of is only allowing types with the same size, since that's required for the operation to even make sense. It's on the The intent of the API as I understood it was to allow value reinterpretation;
So would you want to make |
namespace System.Runtime.CompilerServices;
public static class Unsafe
{
public static TTo BitCast<TFrom, TTo>(TFrom value)
- where TFrom : struct
- where TTo : struct;
} |
Background and motivation
In .NET 8 we've added the
Unsafe.BitCast
API as a less-unsafe alternative toUnsafe.As
when casting between compatible structs.One justification was that this would let us avoid codegen pessimization for values marked as address-taken in some cases. Example from the original API proposal: #81334 (comment)
Due to the
TFrom : struct
constraint, we can't use this API in places like thespan.IndexOf*
helpers.I propose we drop the generic constraint while keeping the runtime behavior the same.
With the constraint removed, I was able to confirm that we're able to flow the constness of values through for this case: MihaZupan@65c8054.
Quoting the conclusion from the initial API review:
API Proposal
namespace System.Runtime.CompilerServices; public static class Unsafe { public static TTo BitCast<TFrom, TTo>(TFrom value) - where TFrom : struct where TTo : struct; }
API Usage
Alternative Designs
We could drop the constraint on
TTo
as well while we're at it?Risks
Some erroneous usages move from being compile-time errors to run-time exceptions.
But this is an
Unsafe
API for a reason :)The text was updated successfully, but these errors were encountered: