Rust: & mut self emprestar conflitante consigo mesmo.

Criado em 3 fev. 2015  ·  20Comentários  ·  Fonte: rust-lang/rust

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

A-NLL A-borrow-checker A-typesystem C-bug NLL-polonius T-compiler

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 :)

Todos 20 comentários

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
}

Cercadinho

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.

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)

Esta página foi útil?
0 / 5 - 0 avaliações