Borrows devolvidos
Uma função que devolve um de seus parâmetros, ou uma parte de um deles, retorna um borrow do argumento do chamador. Nada na assinatura diz isso, e nada precisa dizer: o compilador lê o corpo, descobre para quais argumentos o resultado pode apontar e cobra isso do chamador.
Um parâmetro devolvido
Um corpo que só lê um parâmetro e o devolve empresta esse parâmetro. O chamador continua com o valor, e o resultado é um olhar para ele:
fn id(xs: Array<i64>): Array<i64> { xs }
fn main(): i32 {
let xs: Array<i64> = [1, 2];
let r = id(xs);
(r.len() + xs.len()) as i32 // 4: xs is still the caller's
}Uma parte de um parâmetro passa por borrow do mesmo jeito. first devolve o elemento onde ele está, e nada é copiado:
fn first(xs: Array<Array<i64>>): Array<i64> { xs[0] }
fn main(): i32 {
let xs: Array<Array<i64>> = [[1, 2], [3]];
let r = first(xs);
r.len() as i32 // 2
}Um resultado que pode vir de vários argumentos
Quando o corpo pode devolver um entre vários parâmetros, o resultado fica preso a todo argumento de onde ele pode vir:
fn longer(x: Array<i64>, y: Array<i64>): Array<i64> {
if x.len() > y.len() { x } else { y }
}
fn main(): i32 {
let a = [1, 2, 3];
let b = [4];
let r = longer(a, b); // a look at a or at b
(r.len() + a.len() + b.len()) as i32
}O chamador não sabe qual dos dois recebeu, então enquanto r estiver vivo nem a nem b podem ser substituídos, passar por move ou ser escritos:
fn longer(x: Array<i64>, y: Array<i64>): Array<i64> {
if x.len() > y.len() { x } else { y }
}
fn main(): i32 {
let a = [1, 2, 3];
let b = [4];
let r = longer(a, b);
b = []; // refused: r may be a look at b
(r.len() + b.len()) as i32
}error: 'b' is viewed on line 8 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()'Um borrow dura até o seu último uso, e não até o fim do bloco. Depois que r é lido pela última vez, b fica livre de novo.
O contrato é derivado, e fica visível
A assinatura de longer é a que foi escrita. O que o compilador derivou do corpo é registrado por talor surface, que escreve o contrato do module e faz o build falhar quando ele muda:
fn longer(x: Array<i64>, y: Array<i64>): Array<i64> from param 0 from param 1from param 0 from param 1 é o contrato cobrado de quem chama. Uma mudança no corpo que muda esse contrato - um resultado que agora pode vir de um terceiro argumento, ou que deixa de ser um borrow - muda essa linha, então a mudança aparece onde a função é definida, e não em uma chamada distante.
Argumentos que terminam na chamada passam por move
Quando todo argumento de onde o resultado pode vir é um temporário, ou um local que não é usado de novo, a chamada faz o move deles. O chamador fica dono do resultado, o argumento que não foi devolvido é liberado na chamada, e nada é copiado:
fn longer(x: Array<i64>, y: Array<i64>): Array<i64> {
if x.len() > y.len() { x } else { y }
}
fn build(n: i64): Array<i64> {
let a: Array<i64> = [1, 2, 3, 4];
let b: Array<i64> = [n];
longer(a, b) // a and b end here: the result is build's own
}
fn main(): i32 {
let kept = build(7);
kept.len() as i32 // 4
}Onde um borrow viveria mais que o argumento, o compilador copia
Um borrow não pode viver mais que aquilo para o qual aponta. Quando um resultado que é um borrow é guardado em um lugar que é dono do valor - um binding com o tipo escrito, uma atribuição, um retorno, um campo, um elemento, um argumento que o callee guarda - o compilador o copia nesse ponto, e em nenhum outro:
fn longer(x: Array<i64>, y: Array<i64>): Array<i64> {
if x.len() > y.len() { x } else { y }
}
fn main(): i32 {
let a: Array<i64> = [1, 2, 3];
let result: Array<i64> = [];
{
let b: Array<i64> = [4];
result = longer(a, b); // copied here: result outlives b
}
(result.len() * 10 + a.len()) as i32 // 33
}A cópia é a mesma que o programa teria de escrever à mão como .clone(), no mesmo lugar. Uma chamada cujo resultado fica dentro da região dos seus argumentos, que é o caso comum, não copia nada.
A mesma regra vale para um resultado que é um borrow guardado em um container:
fn first(xs: Array<Array<i64>>): Array<i64> { xs[0] }
fn main(): i32 {
let xs: Array<Array<i64>> = [[1, 2], [3]];
let ys: Array<Array<i64>> = [];
ys.push(first(xs)); // copied: ys keeps what it is given
(ys.len() + xs.len()) as i32
}--borrow strict: nenhuma cópia que o programa não escreveu
Compilado com --borrow strict, o compilador não insere nenhuma cópia por conta própria. Um resultado que é um borrow guardado além dos seus argumentos é recusado, e a mensagem diz de qual argumento ele é emprestado:
talor build --borrow stricterror: this value is borrowed from 'a', and a borrowed value cannot be kept here; write '.clone()' to keep a copyA mesma flag desliga a cópia que o compilador faz quando um argumento de uma chamada é escrito e outro é lido do mesmo lugar, como em g(h.k, h): essa chamada é recusada, e a mensagem diz onde escrever .clone().
O que continua sendo um move
Dois formatos não passam por borrow, e o chamador entrega o valor:
Um parâmetro que o corpo escreve e depois devolve. Um borrow é somente leitura, então um corpo que altera o parâmetro entrega o valor alterado:
talorfn add(x: Array<i64>, v: i64): Array<i64> { x.push(v); x } fn main(): i32 { let a: Array<i64> = [1]; let r = add(a, 2); // a is given to add r.len() as i32 }Um parâmetro devolvido em um caminho, e um valor novo em outro. O chamador não saberia se é dono do resultado, então o parâmetro é entregue em todos os caminhos:
talorfn or_new(x: Array<i64>, k: i64): Array<i64> { if k > 0 { x } else { [9, 9] } } fn main(): i32 { let a: Array<i64> = [1, 2, 3]; let r = or_new(a, 1); // a is given to or_new r.len() as i32 }
Onde um valor com dono e um borrow se encontram
Um if ou um match cujos ramos discordam - um produz o borrow que uma chamada devolve, outro um valor próprio - se junta em um único valor que pertence ao programa, então o compilador copia o lado emprestado no join:
fn first(xs: Array<Array<i64>>): Array<i64> { xs[0] }
fn main(): i32 {
let xs = [[1, 2], [3]];
let k = 1;
let r = if k > 0 { first(xs) } else { [7] }; // the borrowed side is copied here
xs[0].push(9); // so xs may change while r is live
println(`${r.len()} ${xs[0].len()}`);
0
}Com --borrow strict o join é recusado em vez disso, e .clone() no ramo do borrow é a cópia escrita à mão:
error: this value crosses the join and is kept there: it is borrowed from 'xs', and '.clone()' is what keeps a copyAs garantias
- Um borrow devolvido nunca vive mais que aquilo para o qual aponta: ou os argumentos vivem mais que ele, ou o compilador o copia no ponto em que isso não aconteceria, ou, com
--borrow strict, o programa é recusado ali. - Enquanto um resultado está vivo, todo argumento de onde ele pode vir fica protegido: nenhum deles pode ser substituído, passar por move ou ser escrito.
- Um borrow custa o que um pointer custa. Uma cópia só acontece onde o programa guarda um resultado que é um borrow além dos seus argumentos, e ela é a cópia que o programa teria de escrever.