Não sei por que isso acontece, mas rovar e XMPPwocky no IRC acreditavam que fosse um bug do compilador.
struct A {
a: i32
}
impl A {
fn one(&mut self) -> &i32{
self.a = 10;
&self.a
}
fn two(&mut self) -> &i32 {
loop {
let k = self.one();
if *k > 10i32 {
return k;
}
}
}
}
... dá o seguinte erro ...
<anon>:12:21: 12:25 error: cannot borrow `*self` as mutable more than once at a time
<anon>:12 let k = self.one();
^~~~
<anon>:12:21: 12:25 note: previous borrow of `*self` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self` until the borrow ends
<anon>:12 let k = self.one();
^~~~
<anon>:17:6: 17:6 note: previous borrow ends here
<anon>:10 fn two(&mut self) -> &i32 {
...
<anon>:17 }
^
error: aborting due to previous error
playpen: application terminated with error code 101
Curiosamente, se o segundo método for alterado para ...
fn two(&mut self) -> &i32 {
loop {
let k = self.one();
return k;
}
}
Isso vai compilar bem.
cercadinho: http://is.gd/mTkfw5
Eu acredito que este é um comportamento correto. Para ver o motivo, considere o código com anotações explícitas de vida:
struct A {
a: i32
}
impl A {
fn one<'a>(&'a mut self) -> &'a i32{
self.a = 10;
&self.a
}
fn two<'a>(&'a mut self) -> &'a i32 {
loop {
let k = self.one();
if *k > 10i32 {
return k;
}
}
}
}
two
precisa retornar uma referência com o mesmo tempo de vida do empréstimo mutável fornecido a ele. No entanto, uma vez que obtém o seu. Isso significa que k
deve ter 'a
vitalício. No entanto, para que o valor retornado de one
tenha tempo de vida 'a
, a entrada para one
também deve ter tempo de vida 'a
. Isso significa que Rust é forçado a não "pegar de novo" self
e movê-lo para one
. Como as referências mutáveis são lineares, elas só podem ser movidas uma vez, mas loop
significa que one
pode precisar ser chamado com a mesma referência mutável repetidamente.
O segundo exemplo só funciona porque Rust pode ver que o loop será executado apenas uma vez, então self
será movido apenas uma vez.
Isso parece um problema de empréstimos não lexicais. Você pode obter um comportamento semelhante sem nenhum loop:
struct Foo { data: Option<i32> }
fn main() {
let mut x = Foo{data: Some(1)};
foo(&mut x);
}
fn foo(x: &mut Foo) -> Option<&mut i32> {
if let Some(y) = x.data.as_mut() {
return Some(y);
}
println!("{:?}", x.data);
None
}
<anon>:14:22: 14:28 error: cannot borrow `x.data` as immutable because it is also borrowed as mutable
<anon>:14 println!("{:?}", x.data);
^~~~~~
note: in expansion of format_args!
<std macros>:2:43: 2:76 note: expansion site
<std macros>:1:1: 2:78 note: in expansion of println!
<anon>:14:5: 14:30 note: expansion site
<anon>:10:22: 10:28 note: previous borrow of `x.data` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x.data` until the borrow ends
<anon>:10 if let Some(y) = x.data.as_mut() {
^~~~~~
<anon>:16:2: 16:2 note: previous borrow ends here
<anon>:9 fn foo(x: &mut Foo) -> Option<&mut i32> {
...
<anon>:16 }
^
error: aborting due to previous error
Rustc não pode "lidar" com retornos de empréstimos condicionais.
CC @pcwalton
Acho que o problema é que não é usada a mutabilidade do valor de retorno, mas a mutabilidade do argumento da função.
O código a seguir chama uma função mutante que retorna uma referência imutável. Depois disso, não posso aceitar referências imutáveis.
struct Foo (u8);
impl Foo {
fn bar(&mut self) -> &u8 {
self.0 += 1;
&self.0
}
}
fn main() {
let mut x = Foo(42);
let a = x.bar(); // note: borrow of `x` occurs here
let b = x.0; // error: cannot use `x.0` because it was mutably borrowed
}
Ainda estou tendo esse problema na última noite. Parece que ele trata os retornos como tendo uma vida útil até o final da função, mesmo se a função sem o retorno passar no verificador de empréstimo. Isso parece meio estranho, considerando como o ponto de retorno é encerrar a função mais cedo.
Se você der uma olhada no exemplo de @Gankro , ele passa no verificador de empréstimo se você remover a palavra-chave return, mesmo que a função resultante não funcione corretamente.
Isso é bloqueado em vidas não lexicais, que por sua vez é bloqueado no MIR. O ideal é que isso seja consertado no final de 2016, mas não chegará em breve.
Algumas discussões sobre internals: https://internals.rust-lang.org/t/relaxing-the-borrow-checker-for-fn-mut-self-t/3256
Discussão sobre o recurso de empréstimo não léxico: https://github.com/rust-lang/rfcs/issues/811
Portanto, ler o [tópico interno] me faz sentir que isso não pode ser corrigido (mesmo com NLL). Alguém pode confirmar? (@eddyb?)
cc @ rest-long / long
Isso é de fato NLL e, se não me engano, seria corrigido pelas várias propostas que fiz. Isso corresponde aproximadamente ao "caso de problema nº 3" da minha primeira postagem no blog . A ideia básica (em termos da formulação da última postagem do blog ) de por que funcionaria é que a subtipagem do empréstimo só seria necessária para se estender até o final de 'a
no ramo de if
que causa um return
.
@nikomatsakis Você poderia explicar mais sobre como o NLL interage com esse problema? Em meu modelo mental de referências de &mut
, elas podem ser passadas por movimentação ou de novo , e o problema nesta edição é que o retorno requer o modo de movimentação e a reutilização de self
depois requer um novo amanhã . No meu entendimento, o tempo de vida da nova referência &mut
novo empréstimo é limitado pelo tempo de vida da variável que contém a &mut
-reference - neste caso, a variável self
, portanto, é limitada por o corpo da função, portanto, não pode se estender para fora da chamada de função. O NLL vai mudar essa limitação de reborrows (ou talvez não haja tal limitação)?
Também estou me perguntando se a correção desse problema é algo inerentemente vinculado ao NLL ou talvez seja ortogonal? Se for o último, talvez valha a pena consertar antes do NLL.
Além disso, se o NLL resolverá esse problema, isso significa que, no NLL, você nunca terá que escolher manualmente entre uma mudança e um novo amanhã?
Isso vai ser consertado em breve?
@krdln
Você poderia explicar mais sobre como a NLL interage com esse problema?
O resumo é que, hoje, se um valor é retornado da função em qualquer caminho, o empréstimo deve ser válido para o resto da função em todos os caminhos. De acordo com a proposta da NLL que redigi, essa restrição foi suspensa.
Mais detalhadamente, se você pegar a assinatura elaborada aqui, verá que devemos retornar uma referência com duração 'a
:
fn two<'a>(&'a mut self) -> &'a i32 { .. }
agora observe que chamamos let k = self.one()
e então retornamos este k
. Isso significa que o tipo de k
deve ser &'a i32
. Isso, por sua vez, significa que quando chamamos self.one()
, devemos fornecer a ele um empréstimo de self
com o tipo &'a mut Self
. Portanto, em let k = self.one()
emitimos um empréstimo de self
com 'a
vitalício. Esse tempo de vida é maior do que o loop (e, na verdade, toda a função), portanto, ele persiste conforme percorremos o loop, levando ao relatório de erro. De acordo com as regras de NLL que propus, a vida útil de k
só deve se estender para 'a
se if
for assumido. Portanto, se os if
não forem tomados, o empréstimo pode terminar mais cedo.
Isso ajuda?
@ osa1
Isso vai ser consertado em breve?
Ainda faltam alguns passos antes de fazermos isso, mas ainda há trabalho ativo para fazer as refatorações necessárias.
@nikomatsakis
Obrigado, ajudou um pouco. Mas há uma pequena coisa que eu não entendo - o que exatamente acontece quando você escreve self
em seu código - como funciona o novo amanhã de self
. Eu li seu exaplanation do "Problem case 3" ( get_default
), onde você inline o código dentro do chamador, mas lá, você mudou cada uso de self
para emprestar o map
variável diretamente, de modo que o desugaring não esclareceu tudo para mim.
É aqui que estou preso: quando chamamos let k = self.one()
, o self
não pode ser movido (porque é necessário mais tarde), então é considerado emprestado. Posteriormente, retornamos condicionalmente aquele k
, de forma que emprestar deve ter uma vida útil 'a
, que sobrevive à chamada de função. Mas! Pegamos emprestado de self
, que vive apenas até o final da função. Essa limitação parece encurtar 'a
, portanto, em meu modelo mental, mesmo sob NLL, devemos obter o erro "não vive o suficiente".
@krdln , na verdade pegamos emprestado de *self
- isto é, emprestamos novamente o que self se refere. Nós permitimos que você tome emprestado *self
por uma vida que sobreviva a self
, porque o tipo de self
implica um 'bloqueio' para toda a vida 'a
. Portanto, desde que possamos garantir que durante 'a
, self não seja mais usado, o resultado deve ser uma referência exclusiva - neste caso, após o retorno de fn, self foi exibido e, portanto, poderia não ser usado, portanto, apenas garantir que self não seja usado até o final do fn é suficiente. (Pelo menos espero que seja verdade. =)
Isso é bloqueado em vidas não lexicais, que por sua vez é bloqueado no MIR. O ideal é que isso seja consertado no final de 2016, mas não chegará em breve.
Não é muito surpreendente, mas eu confirmei que o exemplo inicial realmente compila todas as noites se você ativar o recurso de tempos de vida não lexicais.
Acho que tive um problema semelhante na produção, em que um empréstimo imutável parece durar muito tempo ( solução alternativa ).
Devo também mencionar que o código do starwed não compila mais na última noite. @oberien sugeriu uma regression-from-nightly-to-nightly
" e também foi sugerido que eu deveria marcar @nikomatsakis no caso de ser um problema urgente :)
Encontrei este problema tentando fazer algo muito semelhante (o que não é permitido pelo nll atual):
fn f(vec: &mut Vec<u8>) -> &u8 {
if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
*n = 10;
n
} else {
vec.push(10);
vec.last().unwrap()
}
}
fn main() {
let mut vec = vec![1, 2, 3];
f(&mut vec);
}
error[E0499]: cannot borrow `*vec` as mutable more than once at a time
--> src/main.rs:6:9
|
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
| - let's call the lifetime of this reference `'1`
2 | if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
| --- first mutable borrow occurs here
3 | *n = 10;
4 | n
| - returning this value requires that `*vec` is borrowed for `'1`
5 | } else {
6 | vec.push(10);
| ^^^ second mutable borrow occurs here
error[E0502]: cannot borrow `*vec` as immutable because it is also borrowed as mutable
--> src/main.rs:7:9
|
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
| - let's call the lifetime of this reference `'1`
2 | if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
| --- mutable borrow occurs here
3 | *n = 10;
4 | n
| - returning this value requires that `*vec` is borrowed for `'1`
...
7 | vec.last().unwrap()
| ^^^ immutable borrow occurs here
(deveríamos ter removido E-needstest quando removemos NLL-fixed-by-NLL)
Comentários muito úteis
Acho que tive um problema semelhante na produção, em que um empréstimo imutável parece durar muito tempo ( solução alternativa ).
Devo também mencionar que o código do starwed não compila mais na última noite. @oberien sugeriu uma
regression-from-nightly-to-nightly
" e também foi sugerido que eu deveria marcar @nikomatsakis no caso de ser um problema urgente :)