Lifetimes
Um borrow é um olhar para um valor que vive em outro lugar. O Talor não escreve a região nem um nome para ela: não existe 'a, nem &, nem nada no call site. O compilador infere do fluxo de controle quanto tempo um borrow vive, e o borrow termina no último uso dele, não no fim do scope que o contém.
Uma linguagem que faz dos lifetimes parte do tipo chama de elision o ato de deixá-los de fora: a anotação é omitida onde uma regra fixa sobre a assinatura a determina. O Talor só tem a forma elidida - nada é escrito em posição nenhuma - e a regra não é sobre a assinatura: a origem de uma view retornada é lida do corpo, então duas funções com a mesma assinatura podem devolver views de lugares diferentes.
fn main(): i32 {
let xs: Array<Array<i64>> = [[1, 2], [3]];
let r = xs[0]; // `r` reads the element where it lies
println(`${r.len()}`);
xs = []; // accepted: `r` was used on the line above
println(`${xs.len()}`);
0
}Views
Uma view é um olhar compartilhado e somente leitura para um lugar. Ela é uma palavra de máquina, é Copy, não possui nada e não libera nada.
Nenhuma palavra escreve uma. Um lugar entregue a um parâmetro que a função só lê já é um borrow desse lugar, uma leitura de um lugar que esta função não possui mantém o borrow, e um valor que um corpo devolve de uma leitura deriva a origem dele do corpo. O compilador faz uma view sempre que uma leitura pode ficar onde está, e o programa não diz nada para isso. view é o nome do conceito, não uma palavra da linguagem: um programa pode usá-la como nome.
struct Point { x: i64, y: i64 }
fn norm2(p: Point): i64 { p.x * p.x + p.y * p.y }
let p = Point { x: 3, y: 4 };
println(`${norm2(p)}`); // the parameter is only read: nothing is written at the call siteUma view de um T responde o que um lugar de tipo T responde: um campo é lido através dela (r.x), uma chamada de método a toma como receiver (r.len()), e um índice é lido através dela (r[i]).
A leitura de um escalar Copy é uma cópia, porque a cópia de um escalar é mais barata que um pointer para ele. Para um valor que possui algo, leia um campo ou chame um método através da view: *r em uma view de um agregado é recusado, porque levaria o valor inteiro de um lugar que a função não possui.
As regras
- Uma view não pode viver mais que o lugar para o qual aponta.
- Uma view não pode viver em um campo.
- Uma view não pode viver em um container nem em uma tupla.
- Enquanto uma view de
xestá viva,xnão pode ser atribuído, passar por move, nem ser passado aonde seria alterado. Viva termina no último uso da view. - Uma view precisa de um lugar: uma chamada ou um literal não tem onde viver.
- Uma view é somente leitura; a alteração passa pelo dono.
- Uma função retorna uma view quando toda view retornada vem de seus parâmetros, e o call site prende o resultado a todo argumento de onde ele pode vir.
Cada regra tem uma mensagem, e a mensagem diz o que escrever em vez disso:
error: 'xs' is viewed on line 3 and the view is still live here, so this assignment would leave it pointing at something else; end the view first, or take a copy with '.clone()'
error: a view is read-only: write through 'x', which owns the place, or take an owned copy with '.clone()'A única forma da mesma ideia com escrita permitida é Locked<T>, que só um lock faz e só pelo tempo de uma chamada: uma view com a escrita liberada, e uma regra de região mais estrita que estas sete.
Ler um lugar que esta função não possui é um olhar
Um elemento de um array, um campo de um parâmetro que é um borrow, a variável de um for e um binding de padrão sobre um valor que está sob borrow são lugares de onde nada pode passar por move, então ler um deles mantém o borrow em vez de copiar o valor:
struct Row { cells: Array<i64> }
fn main(): i32 {
let xs: Array<Row> = [Row { cells: [1, 2] }, Row { cells: [3] }];
let r = xs[0]; // `r` reads the element
let n = r.cells[1]; // and a view is indexed like the place it views
println(`${n}`);
for x in xs { println(`${x.cells.len()}`); } // the loop variable is a borrow too
0
}Todo o resto continua igual: um valor Copy é copiado, porque a cópia de um escalar é mais barata que um pointer para ele; um lugar que esta função possui também é lido onde está, e passa por move quando o novo nome é entregue adiante ou escrito; um temporário não tem de onde ser lido. Guardar um desses olhares, ou passá-lo a um parâmetro com que a função fica, é um move para fora de um armazenamento que a função não possui, e é recusado. Retornar um deles faz a função retornar um borrow, como mostrado abaixo.
Um valor retornado é um borrow quando o corpo lê no lugar
Um tipo de retorno é escrito como o tipo que ele é - Row - e o corpo decide se o que volta é um olhar ou um valor:
struct Row { cells: Array<i64> }
struct Table { rows: Array<Row> }
fn row(t: Table, i: i64): Row { t.rows[i] } // a look at `t`, not a copy
let t = Table { rows: [Row { cells: [1, 2] }] };
let r = row(t, 0);
println(`${r.cells.len()}`);Um olhar retornado pode vir de mais de um parâmetro, e então o resultado fica preso a todo argumento de onde ele pode vir: nenhum deles pode ser substituído enquanto o resultado estiver vivo. Borrows devolvidos mostra cada caso.
O lugar de onde um olhar é tirado precisa viver mais que a instrução que o usa, então uma chamada cuja origem é um temporário é recusada:
error: a view returned by this call points into a temporary, and a temporary dies at the end of the statement; bind it to a name firstUm olhar guardado onde o valor tem dono - colocado em um container, ou atribuído a um binding que vive mais que o argumento - viveria mais que aquilo para o qual aponta, então o compilador o copia nesse ponto. Compilado com --borrow strict, ele é recusado, e .clone() é como um programa pede um valor próprio.
Um corpo que constrói um valor retorna um valor com dono, e o mesmo vale para um corpo que escreve em um parâmetro e depois o devolve. Um corpo que retorna um olhar em um caminho e um valor que ele construiu em outro é recusado, porque o chamador não saberia se cabe a ele liberar o resultado, e o mesmo vale para um olhar envolvido em um valor que o corpo constrói, como Some(t.rows[i]). Escrever .clone() na parte faz dela um valor próprio da função:
error: 'pick' reads a place it does not own on line 4 and returns a value of its own on line 4, so the caller cannot tell whether the result is its to release; return a value of its own on both, with '.clone()' on line 4Um borrow em uma closure
Uma closure pode capturar uma view, porque uma view é Copy e somente leitura: o lugar para o qual ela aponta fica sob borrow enquanto a closure viver, então esse lugar não pode passar por move nem ser alterado enquanto a closure existir.
struct Row { cells: Array<i64> }
struct Table { rows: Array<Row> }
fn row(t: Table, i: i64): Row { t.rows[i] }
let t = Table { rows: [Row { cells: [1, 2] }] };
let r = row(t, 0);
let f = () => r.cells.len();
println(`${f()}`);Essa closure não pode sair da função em que foi criada: um valor que carrega uma view não é retornado nem passado aonde a função chamada fica com ele, porque qualquer um dos dois viveria mais que a região de onde a view foi tirada, e fn(A): R não tem onde dizer qual região é essa.