F# has two major feature areas that deal in the space of low-level programming:
The byref/inref/outref types, which are managed pointers. They have restrictions on usage so that you cannot compile a program that is invalid at run time.
A byref-like struct, which is a struct that has similar semantics and the same compile-time restrictions as byref<'T>. One example is Span<T>.
Syntax
F#
// Byref types as parameterslet f (x: byref<'T>) = ()
let g (x: inref<'T>) = ()
let h (x: outref<'T>) = ()
// Calling a function with a byref parameterletmutable x = 3
f &x
// Declaring a byref-like structopen System.Runtime.CompilerServices
[<Struct; IsByRefLike>]typeS(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
Byref, inref, and outref
There are three forms of byref:
inref<'T>, a managed pointer for reading the underlying value.
outref<'T>, a managed pointer for writing to the underlying value.
byref<'T>, a managed pointer for reading and writing the underlying value.
A byref<'T> can be passed where an inref<'T> is expected. Similarly, a byref<'T> can be passed where an outref<'T> is expected.
Using byrefs
To use a inref<'T>, you need to get a pointer value with &:
F#
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
To write to the pointer by using an outref<'T> or byref<'T>, you must also make the value you grab a pointer to mutable.
F#
open System
let f (dt: byref<DateTime>) =
printfn $"Now: %O{dt}"
dt <- DateTime.Now
// Make 'dt' mutableletmutable dt = DateTime.Now
// Now you can pass the pointer to 'dt'
f &dt
If you are only writing the pointer instead of reading it, consider using outref<'T> instead of byref<'T>.
Inref semantics
Consider the following code:
F#
let f (x: inref<SomeStruct>) = x.SomeField
Semantically, this means the following:
The holder of the x pointer may only use it to read the value.
Any pointer acquired to struct fields nested within SomeStruct are given type inref<_>.
The following is also true:
There is no implication that other threads or aliases do not have write access to x.
There is no implication that SomeStruct is immutable by virtue of x being an inref.
However, for F# value types that are immutable, the this pointer is inferred to be an inref.
All of these rules together mean that the holder of an inref pointer may not modify the immediate contents of the memory being pointed to.
Outref semantics
The purpose of outref<'T> is to indicate that the pointer should only be written to. Unexpectedly, outref<'T> permits reading the underlying value despite its name. This is for compatibility purposes.
Semantically, outref<'T> is no different than byref<'T>, except for one difference: methods with outref<'T> parameters are implicitly constructed into a tuple return type, just like when calling a method with an [<Out>] parameter.
fs
typeC=
staticmember M1(x, y: _ outref) =
y <- x
truematch C.M1 1with
| true, 1 -> printfn "Expected"// Fine with outref, error with byref
| _ -> printfn "Never matched"
Interop with C#
C# supports the in ref and out ref keywords, in addition to ref returns. The following table shows how F# interprets what C# emits:
C# construct
F# infers
ref return value
outref<'T>
ref readonly return value
inref<'T>
in ref parameter
inref<'T>
out ref parameter
outref<'T>
The following table shows what F# emits:
F# construct
Emitted construct
inref<'T> argument
[In] attribute on argument
inref<'T> return
modreq attribute on value
inref<'T> in abstract slot or implementation
modreq on argument or return
outref<'T> argument
[Out] attribute on argument
Type inference and overloading rules
An inref<'T> type is inferred by the F# compiler in the following cases:
A .NET parameter or return type that has an IsReadOnly attribute.
The this pointer on a struct type that has no mutable fields.
The address of a memory location derived from another inref<_> pointer.
When an implicit address of an inref is being taken, an overload with an argument of type SomeType is preferred to an overload with an argument of type inref<SomeType>. For example:
F#
typeC() =
staticmember M(x: System.DateTime) = x.AddDays(1.0)
staticmember M(x: inref<System.DateTime>) = x.AddDays(2.0)
staticmember M2(x: System.DateTime, y: int) = x.AddDays(1.0)
staticmember M2(x: inref<System.DateTime>, y: int) = x.AddDays(2.0)
let res = System.DateTime.Now
let v = C.M(res)
let v2 = C.M2(res, 4)
In both cases, the overloads taking System.DateTime are resolved rather than the overloads taking inref<System.DateTime>.
Byref-like structs
In addition to the byref/inref/outref trio, you can define your own structs that can adhere to byref-like semantics. This is done with the IsByRefLikeAttribute attribute:
F#
open System
open System.Runtime.CompilerServices
[<IsByRefLike; Struct>]typeS(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.
Starting with F# 9, this restriction is relaxed if the generic parameter is defined in C# using the allows ref struct anti-constraint. F# can instantiate such generics in types and methods with byref-like types. As a few examples, this affects BCL delegate types (Action<>, Func<>), interfaces (IEnumerable<>, IComparable<>) and generic arguments with a user-provided accumulator function (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)).
It is impossible to author generic code supporting byref-like types in F#.
This last point is crucial for F# pipeline-style programming, as |> is a generic function that parameterizes its input types. This restriction may be relaxed for |> in the future, as it is inline and does not make any calls to non-inlined generic functions in its body.
Although these rules strongly restrict usage, they do so to fulfill the promise of high-performance computing in a safe manner.
Byref returns
Byref returns from F# functions or members can be produced and consumed. When consuming a byref-returning method, the value is implicitly dereferenced. For example:
F#
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
To return a value byref, the variable that contains the value must live longer than the current scope.
Also, to return byref, use &value (where value is a variable that lives longer than the current scope).
F#
letmutable sum = 0let safeSum (bytes: Span<byte>) =
for i in0 .. bytes.Length - 1do
sum <- sum + int bytes[i]
&sum // sum lives longer than the scope of this function.
To avoid the implicit dereference, such as passing a reference through multiple chained calls, use &x (where x is the value).
You can also directly assign to a return byref. Consider the following (highly imperative) program:
F#
typeC() =
letmutable nums = [| 1; 3; 7; 15; 31; 63; 127; 255; 511; 1023 |]
override _.ToString() = String.Join(' ', nums)
member _.FindLargestSmallerThan(target: int) =
letmutable ctr = nums.Length - 1while ctr > 0 && nums[ctr] >= target do ctr <- ctr - 1if ctr > 0then &nums[ctr] else &nums[0]
[<EntryPoint>]let main argv =
let c = C()
printfn $"Original sequence: %O{c}"let v = &c.FindLargestSmallerThan 16
v <- v*2// Directly assign to the byref return
printfn $"New sequence: %O{c}"0// return an integer exit code
A let-bound value cannot have its reference exceed the scope in which it was defined. For example, the following is disallowed:
F#
let test2 () =
let x = 12
&x // Error: 'x' exceeds its defined scope!let test () =
let x =
let y = 1
&y // Error: `y` exceeds its defined scope!
()
This prevents you from getting different results depending on if you compile with optimizations or not.
Surađujte s nama na GitHubu
Izvor za ovaj sadržaj možete pronaći na GitHubu, gdje možete stvarati i pregledavati probleme i zahtjeve za povlačenjem. Dodatne informacije potražite u našem vodiču za suradnike.
Povratne informacije o proizvodu .NET
.NET je projekt otvorenog koda. Odaberite vezu za slanje povratnih informacija:
Pridružite se seriji susreta kako biste s kolegama programerima i stručnjacima izgradili skalabilna rješenja umjetne inteligencije temeljena na stvarnim slučajevima upotrebe.