Skip to content

Error constructing struct record in member using 'with' #7536

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

Open
antrv opened this issue Sep 11, 2019 · 6 comments
Open

Error constructing struct record in member using 'with' #7536

antrv opened this issue Sep 11, 2019 · 6 comments
Assignees
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code.
Milestone

Comments

@antrv
Copy link

antrv commented Sep 11, 2019

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, 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
@antrv antrv changed the title Compilation error FS3232 when constructing record using 'with' Compilation error FS3232 when constructing struct record in member using 'with' Sep 11, 2019
@cartermp
Copy link
Contributor

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 }

@abelbraaksma
Copy link
Contributor

@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?

@cartermp
Copy link
Contributor

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?

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Dec 19, 2019

@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.

@dsyme
Copy link
Contributor

dsyme commented Jan 7, 2020

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.

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Jan 7, 2020

@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?

@dsyme dsyme reopened this Jan 8, 2020
@cartermp cartermp added this to the Backlog milestone Mar 6, 2020
@dsyme dsyme added the Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code. label Aug 31, 2020
@dsyme dsyme added Area-Compiler-Checking Type checking, attributes and all aspects of logic checking and removed Area-Compiler labels Apr 4, 2022
@dsyme dsyme changed the title Compilation error FS3232 when constructing struct record in member using 'with' Error constructing struct record in member using 'with' Apr 4, 2022
@vzarytovskii vzarytovskii moved this to Not Planned in F# Compiler and Tooling Jun 17, 2022
@edgarfgp edgarfgp self-assigned this May 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code.
Projects
Status: New
Development

No branches or pull requests

5 participants