Error constructing struct record in member using 'with'
The following code is not compiled:
[<Struct>]
type Person =
{ Name: string; Age: int }
member x.WithAge age =
{ x with Age = age }
with the error FS3232: Struct members cannot return the address of fields of the struct by reference, pointing to the x inside WithAge member.
The problem is that x has type inref<Person>, but nothing prevents from constructing another record instance from it.
The known workaround is to declare another variable from x:
[<Struct>]
type Person =
{ Name: string; Age: int }
member x.WithAge age =
let copy = x
{ copy with Age = age }
Environment:
- .NET Core 2.2
- VS 2019 16.2.5
This error is to be expected - no longer making x an inref would violate guarantees around not copying, so an explicit copy is needed if that is your aim. An alternative approach would be:
[<Struct>]
type Person =
{ Name: string; Age: int }
module Person =
let withAge age p = { p with Age = age }
@cartermp, you wrote:
no longer making x an inref would violate guarantees around not copying
However, I have come to understand that the with syntax always creates a copy, this is also what the docs say:
This form of the record expression is called the copy and update record expression.
Records are immutable by default; however, you can easily create modified records by using a copy and update expression
I don't understand why your module code creates a copy, but the OP's code does not. In his code, I don't see any reference, or is the 'this' pointer an implicit reference here and needs it (implicitly) to be dereferenced first by copying it locally?
For non struct records, that syntax would just copy and update the record and return a new copy.
If there are differences in behavior, perhaps we can update the docs? And maybe also the error?
The 'this' pointer is an inref<Peson> from the F# type system standpoint. This was introduced in F# 4.5 to ensure certain guarantees around copying and structs, which is why this arises. I would imagine that this could be special-cased - do not emit this error when we are only doing a copy-and-update expression - but that kind of special casing also tends to be pretty fragile in nature. @dsyme?
@cartermp, thanks for the explanation. I reread the byref docs, and it's included that a readonly struct is treated as inref<'T>, must've missed that detail.
However, since the with copy-and-update syntax only reads, then copies, then returns that copy, it doesn't seem to be any violation of the inref<'T> contract to allow this. But perhaps that should go into a language suggestion?
FS3232: Struct members cannot return the address of fields of the struct by reference
The error mentioned by the OP doesn't appear to be very specific to the situation, esp since the code doesn't show any attempt to return an address.
This error surprises me, I can't immediately imagine why it's being triggered. We should have de-sugared to a TAST like this:
member x.WithAge age =
TOp.Recd ( args = [ x.Name; age ] )
and I would have thought this would allow the x.Name in the checks in PostInferenceChecks.fs.
But clearly there's something awry.
@dsyme, I suggest we reopen this? Your comment makes me believe this is a bug after all.
BTW, I just recollect that I have code like the one of the OP that just compiles as expected, perhaps the structness of this one causes the unexpected behavior? Something with boxing of the this pointer perhaps, making it inref?