Byref
F# имеет две основные области функций, которые занимаются в пространстве низкоуровневого программирования:
byref
//inref
outref
Типы, которые являются управляемыми указателями. Они имеют ограничения на использование, чтобы вы не скомпилировали программу, которая является недопустимой во время выполнения.- Например
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
запись. - Не существует никаких последствий, которые неизменяемы в силу того,
SomeStruct
x
что они являются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# в следующих случаях:
- Параметр .NET или тип возвращаемого
IsReadOnly
значения, имеющий атрибут. - Указатель
this
на тип структуры, не имеющий изменяемых полей. - Адрес расположения памяти, производный от другого
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
//inref
outref
Помимо трио, вы можете определить собственные структуры, которые могут придерживаться 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!
()
Это предотвращает получение различных результатов в зависимости от того, компилируется ли оптимизация или нет.