Open saul opened 6 years ago
@saul Yes agreed, the behaviour shouldn't change under optimization
However the correct code to mutate the struct is this:
let mutable s = SomeStruct()
My first question is why we are not getting a "struct is being mutated" warning/error.
@saul OK, I've looked into this more.
First, if your struct types are mutable then the right way to code with them is like this:
let mutable x = SomeStruct()
x.PropertyAccessOnAStruct
x.MethodOnAStruct()
etc. That's just the right thing to do.
Now, let's consider
let x = SomeStruct()
x.PropertyAccessOnAStruct
As a value-based language where let x = ...
declares an immutable value, F# has special rules time with mutable structs. F# does the following:
If SomeStruct is declared in F# then it can be analyzed to determine if it is mutable or immutable (it's nearly always the latter in F# code, being quite hard to declare a mutable struct). In that case, we know that the property access doesn't mutate the struct, and we don't need to take a defensive copy before taking its address.
However, if SomeStruct is declared in .NET, we currently always assume it is immutable, or at least that the property access doesn't mutate the struct. (As an aside, this wasn't the case in early versions of F# (1.x) but was the case from F# 2.0 onwards. I can't recall the exact reasoning but I'm assuming the change was made for performance reasons - otherwise too many defensive copies would be taken)
A couple of operations such as property setters are inferred to be as "definitely mutates" (DefinitelyMutates) and an outright error will be given if they are used on let x = SomeStruct()
There is a level 5 optional warning you can turn on to show when defensive copies are made
Now, that leaves us in an awkward situation where we are falsely assuming your .NET struct type to be immutable. As a result, I propose to make the following adjustments:
void
/unit
return type then we will label it a "likely mutates" and give a warning (on by default) when used on an .NET let-declared struct value. In your code, this would now give the following warning at your call to x.Set(...)
:This is only a warning since it possible the user has I/O operations such as x.Dump()
on a struct type. They would need to turn the warning off. But the case should be rare enough that we are doing the right thing here. For stability reasons we won't change the execution behaviour of "likely mutates" operations
This is implemented in https://github.com/Microsoft/visualfsharp/pull/3968
Structs referenced from other projects have a significant optimisation failure whereby an instance of the struct cannot be passed to functions - it is always re-instantiated to empty just before the function call.
Repro steps
Imagine you have the following C# project with a single file:
The above project is referenced from F#:
Expected behavior
Output of the program is:
Actual behavior
With optimisations enabled (e.g. Release mode), the output is:
Known workarounds
No known workaround.
Related information
Severe bug, stopping me from using a C# library that uses structs.
The IL for the main function looks like:
IL_0017 and IL_0019 have been added by the optimiser - in Debug mode these instructions are not here, and the bad behaviour is not present.
Thanks