Skip to content

Allow IsByRefLike and IsReadOnly anonymous records #712

@cartermp

Description

@cartermp

I propose we allow ByRefLike and ReadOnly struct anonymous records.

You can create struct anonymous records as such today:

let f (r: struct {| IntVal: int |}) = r.IntVal
f {| IntVal=12 |}
// Or if you don't like structness inference: f struct {| IntVal=12 |}

However, there is no ability to make them ByRefLike, which means you cannot have them contain other ByRefLike structs such as Span<'T>.

A possible declaration syntax might look like this:

// Attribute
let f ([<IsByRefLike>] r: {| S: Span<int> |}) = S.Length

// Keyword
let a (r: byref struct {| S: Span<int> |}) = S.Length
let b (r: struct byref {| S: Span<int> |}) = S.Length
let c (r: byreflike {| S: Span<int> |}) = S.Length // 'byrefLike' implies struct

It would be sort of nonsense to have you require an attribute to instantiate one, so a keyword would likely be needed:

a byref struct {| S = Span<int>.Empty |}
b struct byref {| S = Span<int>.Empty |}
c byreflike {| S = Span<int>.Empty |} // 'byrefLike' implies struct

Or perhaps structness inference would take care of the instantiation site:

// Inferred to by a ByRefLike struct anonymous record
a {| S = Span<int>.Empty |}

Similarly, there is no way to define a IsReadOnly struct anonymous record. Possible declaration syntax:

// Attribute
let f ([<IsReadOnly>] r: {| F: FooReadOnly<int> |}) = S.Length

// Keyword
let a (r: readonly struct {| F: FooReadOnly<int> |}) = S.Length
let b (r: struct readonly {| F: FooReadOnly<int> |}) = S.Length
let c (r: readonly {| F: FooReadOnly<int> |}) = S.Length // 'readonly' implies struct

And an instantiation syntax:

a readonly struct {| F = FooReadOnly<int>.Empty |}
b struct readonly {| F = FooReadOnly<int>.Empty |}
c readonly {| F = FooReadOnly<int>.Empty |} // 'byrefLike' implies struct

Declaration syntax options for having both:

// Attribute declaration
let f ([<IsReadOnly; IsByRefLike>] r: {| S: ReadOnlySpan<int> |}) = S.Length

// Syntax declarations
let a (r: byreflike readonly struct {| S: ReadOnlySpan<int> |}) = S.Length
let b (r: struct byreflike readonly {| S: ReadOnlySpan<int> |}) = S.Length
let c (r: byreflike readonly {| S: ReadOnlySpan<int> |}) = S.Length // 'byrefLike' and 'readonly' imply struct

Instantiation syntax for having both:

a byref readonly struct {| R = ReadOnlySpan<int>.Empty |}
b struct byref readonly {| R = ReadOnlySpan<int>.Empty |}
c byreflike readonly struct {| R = ReadOnlySpan<int>.Empty |}

Structness inference could capture both IsByRefLike and IsReadOnly:

f {| R = ReadOnlySpan<int>.Empty |} // Only works if the input type is byref-like and readonly

Pros and Cons

Advantages:

  • Parity with records
  • Allows you to use these more ephemeral constructs in the perf-sensitive environments ByRefLike structs are intended for

Disadvantages:

  • I can't think of an explicit instantiation syntax that doesn't kind of suck
  • This can make an input parameter heavy syntax-wise
  • These kinds of structs are done with attributes in F# today, but to enable them for anonymous records would require a keyword due to the lack of support for attributes on function parameters
  • Allowing a keyword instead of an attribute is inconsistent with how you define ByRefLike structs today in F#

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions