Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O F# tem duas grandes áreas de recursos que lidam com o espaço da programação de baixo nível:
- Os
byref//inrefoutreftipos, que são ponteiros gerenciados. Eles têm restrições de uso para que você não possa compilar um programa que é inválido em tempo de execução. - Um
byrefstruct -like, que é um struct que tem semântica semelhante e as mesmas restrições de tempo de compilação quebyref<'T>. Um exemplo é Span<T>.
Sintaxe
// 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 e outref
Existem três formas de byref:
-
inref<'T>, um ponteiro gerenciado para ler o valor subjacente. -
outref<'T>, um ponteiro gerenciado para gravar no valor subjacente. -
byref<'T>, um ponteiro gerenciado para ler e gravar o valor subjacente.
A byref<'T> pode ser passado onde um inref<'T> é esperado. Da mesma forma, um byref<'T> pode ser passado onde um outref<'T> é esperado.
Usando byrefs
Para usar um inref<'T>, você precisa obter um valor de ponteiro com &:
open System
let f (dt: inref<DateTime>) =
printfn $"Now: %O{dt}"
let usage =
let dt = DateTime.Now
f &dt // Pass a pointer to 'dt'
Para gravar no ponteiro usando um outref<'T> ou byref<'T>, você também deve fazer o valor que você pega um ponteiro para 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
Se você estiver apenas escrevendo o ponteiro em vez de lê-lo, considere usar outref<'T> em vez de byref<'T>.
Semântica Inref
Considere o seguinte código:
let f (x: inref<SomeStruct>) = x.SomeField
Semanticamente, isso significa o seguinte:
- O detentor do
xponteiro só pode usá-lo para ler o valor. - Qualquer ponteiro adquirido para
structcampos aninhados dentroSomeStructé dado tipoinref<_>.
O seguinte também é verdadeiro:
- Não há nenhuma implicação de que outros threads ou aliases não tenham acesso de gravação ao
x. - Não há nenhuma implicação que
SomeStructseja imutável em virtude dexser uminref.
No entanto, para tipos de valor F# que são imutáveis, o this ponteiro é inferido como um inref.
Todas essas regras juntas significam que o detentor de um inref ponteiro não pode modificar o conteúdo imediato da memória que está sendo apontada.
Semântica Outref
O objetivo é outref<'T> indicar que o ponteiro só deve ser escrito. Inesperadamente, permite a leitura do valor subjacente, outref<'T> apesar do seu nome. Isto é para fins de compatibilidade.
Semanticamente, outref<'T> não é diferente de byref<'T>, exceto por uma diferença: métodos com outref<'T> parâmetros são implicitamente construídos em um tipo de retorno de tupla, assim como ao chamar um método com um [<Out>] parâmetro.
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"
Interoperabilidade com C#
C# suporta as in ref palavras-chave e out ref , além de ref retornos. A tabela a seguir mostra como o F# interpreta o que o C# emite:
| Construção C# | F# infere |
|---|---|
ref Valor de retorno |
outref<'T> |
ref readonly Valor de retorno |
inref<'T> |
parâmetro in ref |
inref<'T> |
parâmetro out ref |
outref<'T> |
A tabela a seguir mostra o que o F# emite:
| Construção F# | Construção emitida |
|---|---|
Argumento inref<'T> |
[In] atributo no argumento |
inref<'T> regresso |
modreq atributo no valor |
inref<'T> em slot abstrato ou implementação |
modreq na argumentação ou no retorno |
Argumento outref<'T> |
[Out] atributo no argumento |
Regras de inferência de tipo e sobrecarga
Um inref<'T> tipo é inferido pelo compilador F# nos seguintes casos:
- Um parâmetro .NET ou tipo de retorno que tem um
IsReadOnlyatributo. - O
thisponteiro em um tipo struct que não tem campos mutáveis. - O endereço de um local de memória derivado de outro
inref<_>ponteiro.
Quando um endereço implícito de um inref está sendo tomado, uma sobrecarga com um argumento de tipo SomeType é preferível a uma sobrecarga com um argumento de tipo inref<SomeType>. Por exemplo:
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)
Em ambos os casos, as sobrecargas que tomam são resolvidas em vez das sobrecargas que tomam System.DateTimeinref<System.DateTime>.
Estruturas semelhantes a Byref
Além do byref//inrefoutref trio, você pode definir suas próprias estruturas que podem aderir à byrefsemântica -like. Isso é feito com o IsByRefLikeAttribute atributo:
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 não implica Struct. Ambos devem estar presentes no tipo.
Uma estrutura "byref-like" em F# é um tipo de valor vinculado a pilha. Ele nunca é alocado na pilha gerenciada. Um byrefstruct -like é útil para programação de alto desempenho, pois é aplicado com um conjunto de verificações fortes sobre o tempo de vida e a não captura. As regras são:
- Eles podem ser usados como parâmetros de função, parâmetros de método, variáveis locais, retornos de método.
- Eles não podem ser estáticos ou membros de instância de uma classe ou struct normal.
- Eles não podem ser capturados por qualquer construção de fechamento (
asyncmétodos ou expressões lambda). - Eles não podem ser usados como um parâmetro genérico.
- A partir de F# 9, essa restrição será relaxada se o parâmetro genérico for definido em C# usando o anti-restrição allows ref struct. F# pode instanciar tais genéricos em tipos e métodos com tipos byref-like. Como alguns exemplos, isso afeta os tipos de delegados BCL (
Action<>,Func<>), interfaces (IEnumerable<>,IComparable<>) e argumentos genéricos com uma função de acumulador fornecida pelo usuário (String.string Create<TState>(int length, TState state, SpanAction<char, TState> action)). - É impossível criar código genérico que suporte tipos byref-like em F#.
- A partir de F# 9, essa restrição será relaxada se o parâmetro genérico for definido em C# usando o anti-restrição allows ref struct. F# pode instanciar tais genéricos em tipos e métodos com tipos byref-like. Como alguns exemplos, isso afeta os tipos de delegados BCL (
Este último ponto é crucial para a programação no estilo pipeline F#, assim como |> uma função genérica que parametriza seus tipos de entrada. Esta restrição pode ser relaxada no |> futuro, uma vez que está em linha e não faz quaisquer chamadas para funções genéricas não alinhadas no seu corpo.
Embora essas regras restrinjam fortemente o uso, elas o fazem para cumprir a promessa de computação de alto desempenho de maneira segura.
Devoluções Byref
Os retornos Byref de funções ou membros do F# podem ser produzidos e consumidos. Ao consumir um byrefmétodo -returning, o valor é implicitamente desreferenciado. Por exemplo:
let squareAndPrint (data : byref<int>) =
let squared = data*data // data is implicitly dereferenced
printfn $"%d{squared}"
Para retornar um valor byref, a variável que contém o valor deve viver mais do que o escopo atual.
Além disso, para retornar byref, use &value (onde value é uma variável que vive mais do que o escopo atual).
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.
Para evitar a desreferência implícita, como passar uma referência através de várias chamadas encadeadas, use &x (onde x está o valor).
Você também pode atribuir diretamente a um retorno byref. Considere o seguinte programa (altamente imperativo):
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
Esta é a saída:
Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence: 1 3 7 30 31 63 127 255 511 1023
Escopo para byrefs
Um letvalor vinculado não pode ter sua referência excedendo o escopo no qual foi definido. Por exemplo, o seguinte não é permitido:
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!
()
Isso impede que você obtenha resultados diferentes, dependendo se você compila com otimizações ou não.