Byrefs
Jazyk F# má dvě hlavní oblasti funkcí, které se zabývají prostorem programování nízké úrovně:
- Typy spravovaných ukazatelů
byref
/inref
/outref
. Mají omezení použití, takže nelze zkompilovat program, který je v době běhu neplatný. - Struktura podobná
byref
, což je struktura , která má podobnou sémantiku a stejná omezení času kompilace jakobyref<'T>
. Jedním z příkladů je Span<T>.
Syntax
// Byref types as parameters
let f (x: byref<'T>) = ()
let g (x: inref<'T>) = ()
let h (x: outref<'T>) = ()
// Calling a function with a byref parameter
let mutable x = 3
f &x
// Declaring a byref-like struct
open System.Runtime.CompilerServices
[<Struct; IsByRefLike>]
type S(count1: int, count2: int) =
member x.Count1 = count1
member x.Count2 = count2
Byref, inref a outref
Existují tři formy byref
:
-
inref<'T>
– spravovaný ukazatel pro čtení podkladové hodnoty. -
outref<'T>
, spravovaný ukazatel pro zápis do podkladové hodnoty. -
byref<'T>
, spravovaný ukazatel pro čtení a zápis podkladové hodnoty.
byref<'T>
lze předat tam, kde se očekává inref<'T>
. Podobně lze předat byref<'T>
tam, kde se očekává outref<'T>
.
Použití byrefs
Pokud chcete použít inref<'T>
, musíte získat hodnotu ukazatele s &
:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Chcete-li na ukazatel psát pomocí outref<'T>
nebo byref<'T>
, musíte také nastavit hodnotu, kterou chytíte ukazatel na mutable
.
open System
let f (dt: byref<DateTime>) =
printfn $"Now: %O{dt}"
dt <- DateTime.Now
// Make 'dt' mutable
let mutable dt = DateTime.Now
// Now you can pass the pointer to 'dt'
f &dt
Pokud místo čtení píšete ukazatel, zvažte použití outref<'T>
místo byref<'T>
.
Sémantika inref
Vezměte v úvahu následující kód:
let f (x: inref<SomeStruct>) = x.SomeField
Séanticky to znamená následující:
- Držitel ukazatele
x
jej může použít pouze ke čtení hodnoty. - Jakýkoli ukazatel získaný k
struct
polím vnořeným uvnitřSomeStruct
má typinref<_>
.
Platí také toto:
- Neexistuje žádná implikace, že jiná vlákna nebo aliasy nemají přístup k zápisu na
x
. - Neexistuje žádná implikace, že
SomeStruct
je neměnný díky tomu, žex
jeinref
.
U typů hodnot jazyka F#, které jsou neměnné, je ukazatel this
odvozen jako inref
.
Všechna tato pravidla společně znamenají, že držitel ukazatele inref
nesmí upravovat okamžitý obsah paměti, na kterou odkazuje.
Sémantika outref
Účelem outref<'T>
je indikovat, že ukazatel by měl být zapsán pouze do. Neočekávaně outref<'T>
umožňuje čtení podkladové hodnoty bez ohledu na jeho název. To je pro účely kompatibility.
outref<'T>
se sémanticky neliší od byref<'T>
, až na jeden rozdíl: metody s parametry outref<'T>
jsou implicitně sestavovány do návratového typu n-tice, stejně jako při volání s parametrem [<Out>]
.
type C =
static member M1(x, y: _ outref) =
y <- x
true
match C.M1 1 with
| true, 1 -> printfn "Expected" // Fine with outref, error with byref
| _ -> printfn "Never matched"
Spolupráce s jazykem C#
Jazyk C# podporuje klíčová slova in ref
a out ref
a kromě toho i ref
. Následující tabulka ukazuje, jak jazyk F# interpretuje, co jazyk C# generuje:
Konstruktor jazyka C# | F# odvozuje |
---|---|
ref návratová hodnota |
outref<'T> |
ref readonly návratová hodnota |
inref<'T> |
parametr in ref |
inref<'T> |
parametr out ref |
outref<'T> |
Následující tabulka ukazuje, co jazyk F# generuje:
Konstrukce jazyka F# | Emitovaná konstrukce |
---|---|
inref<'T> argument |
atribut [In] na argumentu |
vrácení inref<'T> |
atribut modreq pro hodnotu |
inref<'T> v abstraktním slotu nebo implementaci |
modreq na argument nebo návratovou hodnotu |
outref<'T> argument |
atribut [Out] na argumentu |
Pravidla odvozování typů a přetížení
inref<'T>
typ je odvozen kompilátorem jazyka F# v následujících případech:
- Parametr .NET nebo návratový typ, který má atribut
IsReadOnly
. - Ukazatel
this
na typ struktury, který neobsahuje žádná proměnlivá pole. - Adresa paměťového místa odvozená z jiného ukazatele
inref<_>
.
Pokud je přijímána implicitní adresa inref
, přetížení s argumentem typu SomeType
je upřednostňované pro přetížení s argumentem typu inref<SomeType>
. Například:
type C() =
static member M(x: System.DateTime) = x.AddDays(1.0)
static member M(x: inref<System.DateTime>) = x.AddDays(2.0)
static member M2(x: System.DateTime, y: int) = x.AddDays(1.0)
static member 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)
V obou případech jsou vyřešena přetížení využívající System.DateTime
spíše než přetížení využívající inref<System.DateTime>
.
Struktury podobné typu Byref
Kromě tria byref
/inref
/outref
můžete definovat vlastní struktury, které mohou dodržovat sémantiku podobnou byref
. To se provádí pomocí atributu IsByRefLikeAttribute:
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
neznamená Struct
. Oba musí být přítomné na typu.
Struktura „byref
-like“ v jazyce F# je typ hodnoty vázaný na zásobník. Nikdy není alokováno na spravovaném haldovém rámci. Struktura podobná byref
je užitečná pro programování s vysokým výkonem, protože se vynucuje sadou přísných kontrol ohledně životnosti a nezachytávání. Pravidla jsou:
- Mohou být použity jako parametry funkcí, parametry metod, místní proměnné, návratové hodnoty metod.
- Nemohou být statickými ani instančními členy třídy nebo normální struktury.
- Nelze je zachytit žádným konstruktorem uzavření (
async
metody nebo výrazy lambda). - Nelze je použít jako obecný parametr.
- Počínaje F# 9 je toto omezení uvolněno, pokud je obecný parametr v jazyce C# definován pomocí anti-omezení, které povoluje strukturu ref. Jazyk F# může instancovat takové generické typy v typech a metodách s typy podobnými byref. Jako několik příkladů to ovlivňuje typy delegátů BCL (
Action<>
,Func<>
), rozhraní (IEnumerable<>
,IComparable<>
) a obecné argumenty s funkcí akumulátoru poskytnutou uživatelem (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)
). - V jazyce F# není možné vytvořit obecný kód podporující typy typu byref.
- Počínaje F# 9 je toto omezení uvolněno, pokud je obecný parametr v jazyce C# definován pomocí anti-omezení, které povoluje strukturu ref. Jazyk F# může instancovat takové generické typy v typech a metodách s typy podobnými byref. Jako několik příkladů to ovlivňuje typy delegátů BCL (
Tento poslední bod je zásadní pro programování ve stylu kanálu F#, protože |>
je obecná funkce, která parametrizuje své vstupní typy. V budoucnu může být toto omezení uvolněno pro |>
, protože je inline a neprovádí žádná volání ne-vložených obecných funkcí ve svém těle.
I když tato pravidla důrazně omezují využití, dělají to tak, aby splňovaly příslib vysoce výkonného výpočetního prostředí bezpečným způsobem.
Byref vrátí
Byref návraty z funkcí nebo členů jazyka F# lze vytvářet a využívat. Při využívání metody vracející byref
je hodnota implicitně dereferencována. Například:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Pokud chcete vrátit hodnotu „byref“, proměnná, která tuto hodnotu obsahuje, musí existovat déle než aktuální obor.
Pokud chcete vrátit hodnotu jako referenci, použijte &value
(kde hodnota je proměnná, která přetrvává déle než aktuální obor).
let mutable sum = 0
let safeSum (bytes: Span<byte>) =
for i in 0 .. bytes.Length - 1 do
sum <- sum + int bytes[i]
&sum // sum lives longer than the scope of this function.
Pokud se chcete vyhnout implicitní dereference, například předání odkazu prostřednictvím více zřetězených volání, použijte &x
(kde x
je hodnota).
Můžete také přímo přiřadit k návratové byref
. Zvažte následující (vysoce imperativní) program:
type C() =
let mutable nums = [| 1; 3; 7; 15; 31; 63; 127; 255; 511; 1023 |]
override _.ToString() = String.Join(' ', nums)
member _.FindLargestSmallerThan(target: int) =
let mutable ctr = nums.Length - 1
while ctr > 0 && nums[ctr] >= target do ctr <- ctr - 1
if ctr > 0 then &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
Toto je výstup:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Vymezení rozsahu pro byrefs
Hodnota omezená na let
nemůže mít svůj odkaz přesahující obor, ve kterém byla definována. Například následující příkaz je zakázán:
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!
()
To vám zabrání v získání různých výsledků v závislosti na tom, jestli je zkompilujete s optimalizacemi nebo ne.