Structures
A structure is a compact object type that can be more efficient than a class for types that have a small amount of data and simple behavior.
Syntax
[ attributes ]
type [accessibility-modifier] type-name =
struct
type-definition-elements-and-members
end
// or
[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
type-definition-elements-and-members
Remarks
Structures are value types, which means that they are stored directly on the stack or, when they are used as fields or array elements, inline in the parent type. Unlike classes and records, structures have pass-by-value semantics. This means that they are useful primarily for small aggregates of data that are accessed and copied frequently.
In the previous syntax, two forms are shown. The first is not the lightweight syntax, but it is nevertheless frequently used because, when you use the struct
and end
keywords, you can omit the StructAttribute
attribute, which appears in the second form. You can abbreviate StructAttribute
to just Struct
.
The type-definition-elements-and-members in the previous syntax represents member declarations and definitions. Structures can have constructors and mutable and immutable fields, and they can declare members and interface implementations. For more information, see Members.
Structures cannot participate in inheritance, cannot contain let
or do
bindings, and cannot recursively contain fields of their own type (although they can contain reference cells that reference their own type).
Because structures do not allow let
bindings, you must declare fields in structures by using the val
keyword. The val
keyword defines a field and its type but does not allow initialization. Instead, val
declarations are initialized to zero or null. For this reason, structures that have an implicit constructor (that is, parameters that are given immediately after the structure name in the declaration) require that val
declarations be annotated with the DefaultValue
attribute. Structures that have a defined constructor still support zero-initialization. Therefore, the DefaultValue
attribute is a declaration that such a zero value is valid for the field. Implicit constructors for structures do not perform any actions because let
and do
bindings aren’t allowed on the type, but the implicit constructor parameter values passed in are available as private fields.
Explicit constructors might involve initialization of field values. When you have a structure that has an explicit constructor, it still supports zero-initialization; however, you do not use the DefaultValue
attribute on the val
declarations because it conflicts with the explicit constructor. For more information about val
declarations, see Explicit Fields: The val
Keyword.
Attributes and accessibility modifiers are allowed on structures, and follow the same rules as those for other types. For more information, see Attributes and Access Control.
The following code examples illustrate structure definitions.
// In Point3D, three immutable values are defined.
// x, y, and z will be initialized to 0.0.
type Point3D =
struct
val x: float
val y: float
val z: float
end
// In Point2D, two immutable values are defined.
// It also has a member which computes a distance between itself and another Point2D.
// Point2D has an explicit constructor.
// You can create zero-initialized instances of Point2D, or you can
// pass in arguments to initialize the values.
type Point2D =
struct
val X: float
val Y: float
new(x: float, y: float) = { X = x; Y = y }
member this.GetDistanceFrom(p: Point2D) =
let dX = (p.X - this.X) ** 2.0
let dY = (p.Y - this.Y) ** 2.0
dX + dY |> sqrt
end
ByRefLike structs
You can define your own structs that can adhere to byref
-like semantics: see Byrefs for more information. This is done with the IsByRefLikeAttribute attribute:
open System
open System.Runtime.CompilerServices
[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
member x.Count1 = count1
member x.Count2 = count2
IsByRefLike
does not imply Struct
. Both must be present on the type.
A "byref
-like" struct in F# is a stack-bound value type. It is never allocated on the managed heap. A byref
-like struct is useful for high-performance programming, as it is enforced with set of strong checks about lifetime and non-capture. The rules are:
- They can be used as function parameters, method parameters, local variables, method returns.
- They cannot be static or instance members of a class or normal struct.
- They cannot be captured by any closure construct (
async
methods or lambda expressions). - They cannot be used as a generic parameter.
Although these rules very strongly restrict usage, they do so to fulfill the promise of high-performance computing in a safe manner.
ReadOnly structs
You can annotate structs with the IsReadOnlyAttribute attribute. For example:
[<IsReadOnly; Struct>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
IsReadOnly
does not imply Struct
. You must add both to have an IsReadOnly
struct.
Use of this attribute emits metadata letting F# and C# know to treat it as inref<'T>
and in ref
, respectively.
Defining a mutable value inside of a readonly struct produces an error.
Struct Records and Discriminated Unions
You can represent Records and Discriminated Unions as structs with the [<Struct>]
attribute. See each article to learn more.