-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Use of ref readonly
instead of in
in libraries is a breaking change for F# callers
#94317
Comments
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices Issue DetailsDescriptionWith the switch from Reproduction Stepstype MyRecord =
{ Value : int }
member this.SetValue(v: int) =
(Unsafe.AsRef<int> &this.Value) <- v Expected behaviorThe code compiles successfully, and calling Actual behaviorThe code fails to compile:
Regression?This is a regression from .NET 7. Known WorkaroundsNo response ConfigurationUsing .NET 8 RC2 and the associated F# compiler. Other informationNo response
|
If F# doesn't support In other words: isn't this an issue that should be in the F# repo? The API is working as intended. |
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. |
We don't explicitly support it, the fix would probably be an easy one, however I don't understand the change in existing API. Isn't it considered breaking in C#? |
No, it's explicitly not a breaking change in C# 12, as we introduced compatibility rules for The goal was explicitly to allow "fixing" the signatures of existing APIs such as this, without it being a breaking change. |
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 |
I was specifically answering to the question "isn't it considered a breaking change in C#?" 😅 |
My bad! There could be an angry mob of passionate F#'ers around the corner! (one of them myself) |
This looks like it needs to be reverted. Thanks for reporting this. |
We can fix it, however it will take much more time than revert. |
Two things:
|
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. |
How do you propose to do that?. NET 8 is done, and changing it in the reverse direction is a breaking change. |
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. |
Right, so we can keep the impact relatively narrow, sorry F# community, or we impact the larger C# community. I will defer to @jeffhandley and @artl93 if they have any opinion on this. |
I do not have an informed opinion at this time. I am going to play my "newbie card." I do not understand right now how the ABI is composed for combability purposes vs how the compiler understands it in reality, so I'm not going to draw conclusions, but ask questions.
|
The language of "breaking F#" is too strong, absent a valid example of code broken. The code shown in the original post describes a method for mutating an immutable field. The fact that this is possible is surely unintended by F# design and should be regarded as a bug. So the fact that this code is broken is a fortunate side-effect of the PR being discussed. |
Let me rephrase what I think I understand you're saying (I am not familiar with F# linguistics): are you saying this use pattern is essentially undefined behavior in F#? And that while it happened to work, now it does not? |
No eta now, I haven't even looked at the code yet, I would imagine we'll target 8.0.200 for the proper fix.
It's a source breaking change, not a binary one (i.e. it will fail to compile, but existing libraries which use that should continue working). I'm only aware of the change in Unsafe APIs. Generally anything which uses ref readonly parameters will be unusable from F#. |
This has been in the works for over a year (e.g. #67445 (comment)), with a language feature introduced in C# 12 (dotnet/csharplang#6010) to make it feasible for us to change some already-shipped method signatures in a way that wouldn't be breaking in C#. For example, the Volatile class had this method: public static long Read(ref long location); but that signature prohibits it from being used with something that's readonly, even though logically that's fine. The language change enabled this to become: public static long Read(ref readonly long location); without being a source-breaking change for C#. To my knowledge, the change shouldn't be binary breaking at all, regardless of language that had been used to compile the consumer. We discussed whether there would be impact on Visual Basic and F#, but apparently we never validated, which is a miss.
Yes. For example, changing from
It's primarily about usability. |
This could be considered undefined behaviour. F# would like to define the pattern as impossible. The F# specification (and all documentation) defines records as immutable (by default - i.e. unless the keyword However in practice F# is unable to guarantee the soundness of its constructs in all contexts, and there is an acknowledgement that these constructs can be broken, for example by reflection. So the F# spec does not guarantee that the pattern will not work. |
@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 |
It's not undefined, it's rather unverifiable. |
All bets are off with |
It’s worth noting that the use of There had been some testing with F# and C++/CLI at the time the change went in, but apparently the F# side of things either wasn’t done correctly or didn’t cover sufficient scenarios to hit this particular case. The general purpose of the change to C# 12 has introduced This issue has ultimately fallen out with F# because of how |
For additional context, that was the original proposed encoding in the spec, but it was later changed to just be |
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 #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.
|
I'm using it in a high performance protobuf serializer/deserializer in F#, and there is no current workaround to mitigate the problem. I'm stuck with waiting for the next release to be able tu use net 8.0 😥 |
Was inserted into 8.0.1xx in dotnet/sdk#36836 @baronfel might know approximate release window for it. |
It should be part of the 8.0.101 servicing release that'll drop next month. |
Description
With the switch from
in
toref readonly
inUnsafe.AsRef
(#89736), since as I understand it F# doesn't supportref readonly
, it becomes impossible to take an unsafe mutable reference to an immutable record field.Reproduction Steps
Expected behavior
The code compiles successfully, and calling
SetValue()
mutates the field.Actual behavior
The code fails to compile:
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
The text was updated successfully, but these errors were encountered: