Sdílet prostřednictvím


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 jako byref<'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á typ inref<_>.

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, že x je inref.

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:

  1. Parametr .NET nebo návratový typ, který má atribut IsReadOnly.
  2. Ukazatel this na typ struktury, který neobsahuje žádná proměnlivá pole.
  3. 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á byrefje 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.

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í byrefje 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 letnemůž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.