runtime: Use of `ref readonly` instead of `in` in libraries is a breaking change for F# callers
Description
With the switch from in to ref readonly in Unsafe.AsRef (#89736), since as I understand it F# doesn’t support ref readonly, it becomes impossible to take an unsafe mutable reference to an immutable record field.
Reproduction Steps
type MyRecord =
{ Value : int }
member this.SetValue(v: int) =
(Unsafe.AsRef<int> &this.Value) <- v
Expected behavior
The code compiles successfully, and calling SetValue() mutates the field.
Actual behavior
The code fails to compile:
Error FS0041 : No overloads match for method 'AsRef'.
Known type of argument: inref<'T>
Available overloads:
- Unsafe.AsRef<'T>(source: byref<'T>) : byref<'T> // Argument 'source' doesn't match
- Unsafe.AsRef<'T>(source: voidptr) : byref<'T> // Argument 'source' doesn't match
Regression?
This is a regression from .NET 7.
Known Workarounds
No response
Configuration
Using .NET 8 RC2 and the associated F# compiler.
Other information
No response
About this issue
- Original URL
- State: closed
- Created 8 months ago
- Reactions: 7
- Comments: 30 (22 by maintainers)
Yes, I considered posting to F# too, and I probably will. I do find it odd though that only C# was taken into consideration to determine whether this would be a breaking change.
Thanks @vzarytovskii. Having F# fix it seems like general goodness, but this is likely blocking people from adopting .NET 8, which is a red flag in general. If someone wants to try this API change again in .NET 9, that is fine but for now this shouldn’t have been changed. The PR that did this has had other unfortunage side effects that caused other reverts. Unless there is push back from @jkotas or @stephentoub I am going to revert the change.
F# very much is considered with regards to breaking changes, and we do our best to avoid breaking any language.
But, avoiding all breaks is effectively impossible as simply exposing a new API can break user code. It can change overload resolution in C#, it can change type inference in F#, it can cause an extension API to no longer be invoked, it can cause an ambiguity with certain types of edge cases, and so on.
We ultimately try to move the core .NET Libraries forward in a direction that is a positive for the ecosystem as a whole, and that includes changes like https://github.com/dotnet/runtime/pull/89736 which are designed to improve safety, remove reliance on unsafe code, and help users avoid pits of failure.
We will need to do better about testing such changes in the future to ensure that the two edge cases caught don’t happen again.
intoref readonlythat would also be impacted. The first isnew ReadOnlySpan(ref readonly T)and the otherNullable.GetValueRefOrDefaultRef(ref readonly T?). The change fromreftoref readonlywould be invisible to F# users (it currently sees it asbyrefstill, and will see it asinrefafter the language support is added), as will the change fromreftoin(since F# allows implicitbyref->inref)If F# doesn’t support
ref readonly, wouldn’t just every single API using it be broken there, unless the language is updated?In other words: isn’t this an issue that should be in the F# repo? The API is working as intended.
No, it’s explicitly not a breaking change in C# 12, as we introduced compatibility rules for
in/ref/ref readonlyfor this.The goal was explicitly to allow “fixing” the signatures of existing APIs such as this, without it being a breaking change. Which is the case, at least for C# only. You can read the spec here.
Right, I think it may be too late to revert this change. It is a discussion for shiproom.
#89736 was rushed in with a weak justification at the very last minute that I was not happy with (we had internal teams discussion about that). The change caused number of issues, and it is not surprising that we are discovering more issues with it. Something to learn from for next time.
cc @jeffhandley @artl93
@charlesroddie I think you’re focusing too much on the fact that I used a record field in my example. The same issue happens no matter what is behind the
inref.All bets are off with
Unsafe,System.ReflectionorUncheckedHow do you propose to do that?. NET 8 is done, and changing it in the reverse direction is a breaking change.
It should be part of the 8.0.101 servicing release that’ll drop next month.
This looks like it needs to be reverted. Thanks for reporting this.
BCL is not “for C#” though, but “for .NET” (written in C# yes)
Therefore if changes in BCL are breaking for .NET they are the breaking change by definition