Byref

F# имеет две основные области функций, которые занимаются в пространстве низкоуровневого программирования:

  • byref//inrefoutref Типы, которые являются управляемыми указателями. Они имеют ограничения на использование, чтобы вы не скомпилировали программу, которая является недопустимой во время выполнения.
  • Напримерbyref, структуру, которая имеет аналогичную семантику и те же ограничения времени компиляции, что byref<'T>и структуры. Один из примеров: Span<T>.

Синтаксис

// 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 и outref

Существует три формы byref:

  • inref<'T>— управляемый указатель для чтения базового значения.
  • outref<'T>— управляемый указатель для записи в базовое значение.
  • byref<'T>— управляемый указатель для чтения и записи базового значения.

Можно передать, byref<'T>inref<'T> где ожидается. Аналогичным образом можно передать объект byref<'T> , outref<'T> в котором ожидается.

Использование byrefs

Для использования inref<'T>необходимо получить значение указателя со следующими значениями &:

open System

let f (dt: inref<DateTime>) =
    printfn $"Now: %O{dt}"

let usage =
    let dt = DateTime.Now
    f &dt // Pass a pointer to 'dt'

Чтобы записать указатель на указатель с помощью outref<'T> или byref<'T>, необходимо также сделать значение, на 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

Если вы пишете указатель, а не читаете его, рекомендуется использовать outref<'T> вместо byref<'T>него.

Семантика inref

Рассмотрим следующий код:

let f (x: inref<SomeStruct>) = x.SomeField

Семантически это означает следующее:

  • Владелец указателя x может использовать его только для чтения значения.
  • Любой указатель, полученный в struct полях, вложенных в пределах SomeStruct , задан тип inref<_>.

Ниже приведено следующее значение:

  • Нет никаких последствий, что другие потоки или псевдонимы не имеют доступа на xзапись.
  • Не существует никаких последствий, которые неизменяемы в силу того, SomeStructx что они являются inref.

Однако для типов значений F#, которые неизменяемы , this указатель выводится как неизменяемый inref.

Все эти правила вместе означают, что владелец inref указателя не может изменять немедленное содержимое памяти, на которую указывает.

Семантика outref

Цель outref<'T> состоит в том, чтобы указать, что указатель должен быть записан только в. Неожиданно позволяет outref<'T> считывать базовое значение, несмотря на его имя. Это предназначено для обеспечения совместимости.

Семантически, outref<'T> не отличается byref<'T>от одного различия: методы с outref<'T> параметрами неявно создаются в тип возвращаемого кортежа, как и при вызове метода с параметром [<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"

Взаимодействие с C#

C# поддерживает in ref и out ref ключевое слово, а также ref возвращается. В следующей таблице показано, как F# интерпретирует то, что C# выдает:

Конструкция C# F# infers
ref возвращаемое значение outref<'T>
ref readonly возвращаемое значение inref<'T>
in ref параметр inref<'T>
out ref параметр outref<'T>

В следующей таблице показано, что F# выдает:

Конструкция F# Генерируемая конструкция
Аргумент inref<'T> [In] атрибут для аргумента
inref<'T> Вернуться modreq атрибут по значению
inref<'T> в абстрактном слоте или реализации modreq в аргументе или возврате
Аргумент outref<'T> [Out] атрибут для аргумента

Правила вывода и перегрузки типов

Тип inref<'T> выводится компилятором F# в следующих случаях:

  1. Параметр .NET или тип возвращаемого IsReadOnly значения, имеющий атрибут.
  2. Указатель this на тип структуры, не имеющий изменяемых полей.
  3. Адрес расположения памяти, производный от другого inref<_> указателя.

Если принимается неявный адрес, inref перегрузка с аргументом типа SomeType предпочтительна перегрузке с аргументом типа inref<SomeType>. Например:

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)

В обоих случаях перегрузки System.DateTime разрешаются, а не перегрузки inref<System.DateTime>.

Структуры byref-like

byref//inrefoutref Помимо трио, вы можете определить собственные структуры, которые могут придерживаться byrefтакой семантики. Это делается с помощью атрибута 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 не подразумевает Struct. Оба должны присутствовать в типе.

Структуру типа "byrefкак" в F# — это тип значения, привязанного к стеку. Он никогда не выделяется в управляемой куче. Напримерbyref, структуру полезно для высокопроизводительного программирования, так как она применяется с набором сильных проверка о времени существования и без отслеживания. Ниже приведены правила.

  • Их можно использовать в качестве параметров функции, параметров метода, локальных переменных, возвращаемого методом.
  • Они не могут быть статическими или экземплярами элементов класса или обычной структуры.
  • Они не могут быть захвачены какой-либо конструкцией закрытия (async методами или лямбда-выражениями).
  • Их нельзя использовать в качестве универсального параметра.

Эта последняя точка имеет решающее значение для программирования в стиле конвейера F#, так как |> это универсальная функция, которая параметризирует типы входных данных. Это ограничение может быть расслаблено |> в будущем, так как оно является встроенным и не делает никаких вызовов нелинейных универсальных функций в своем теле.

Хотя эти правила строго ограничивают использование, они делают это для выполнения обещания высокопроизводительных вычислений в безопасном режиме.

Возвращает byref

Байтовые возвраты из функций или членов F# могут быть созданы и использованы. При использовании byrefметода -returning значение неявно различается. Например:

let squareAndPrint (data : byref<int>) =
    let squared = data*data    // data is implicitly dereferenced
    printfn $"%d{squared}"

Чтобы вернуть значение путем ссылки, переменная, содержащая значение, должна жить дольше текущей область. Кроме того, чтобы вернуть byref, используйте &value (где значение является переменной, которая живет дольше текущей область).

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.

Чтобы избежать неявного разыменования, например передачи ссылки через несколько цепочек вызовов, используйте ( &x где x это значение).

Вы также можете напрямую назначить возвращаемый byrefобъект. Рассмотрим следующую (очень императивную) программу:

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

Результат.

Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence:      1 3 7 30 31 63 127 255 511 1023

Определение области для byrefs

Значение letс привязкой не может превышать область, в которой она была определена. Например, следующее запрещено:

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!
    ()

Это предотвращает получение различных результатов в зависимости от того, компилируется ли оптимизация или нет.