Rust: & mut auto prestado en conflicto consigo mismo.

Creado en 3 feb. 2015  ·  20Comentarios  ·  Fuente: rust-lang/rust

No sé por qué sucede esto, pero rovar y XMPPwocky en IRC creían que era un error del 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;
            }
        }
    }
}

... da el siguiente error ...

<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, si el segundo método se cambia a ...

    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            return k;
        }
    }

Esto se compilará bien.

parque infantil :

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

Comentario más útil

Creo que me encontré con un problema similar en la producción en el que un préstamo inmutable parece vivir demasiado tiempo ( solución alternativa ).

También debo mencionar que el código de Starwed ya no se compila en la última noche. @oberien sugirió una regression-from-nightly-to-nightly ", y también se sugirió que debería etiquetar a @nikomatsakis en caso de que fuera un problema urgente :)

Todos 20 comentarios

Creo que este es el comportamiento correcto. Para ver por qué, considere el código con anotaciones explícitas de por 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 debe devolver una referencia con la misma vida útil que el préstamo mutable que se le dio. Sin embargo, dado que obtiene su. Esto significa que k debe tener una duración de 'a . Sin embargo, para que el valor devuelto de one tenga una duración de 'a , la entrada de one también debe tener una duración de 'a . Esto significa que Rust se ve obligado a no "rehacer" self y moverlo a one . Dado que las referencias mutables son lineales, solo se pueden mover una vez, pero loop significa que es posible que se deba llamar a one con la misma referencia mutable repetidamente.

El segundo ejemplo solo funciona porque Rust puede ver que el ciclo solo se ejecutará una vez, por lo que self solo se moverá una vez.

Esto parece un problema de préstamos no léxicos. Puede obtener un comportamiento similar sin ningún bucle:

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 no puede "lidiar" con las devoluciones de préstamos condicionales.

CC @pcwalton

Creo que el problema es en realidad que no se usa la mutabilidad del valor de retorno, sino la mutabilidad del argumento de la función.

El siguiente código llama a una función mutante que devuelve una referencia inmutable. Después de eso, no puedo aceptar referencias inmutables.

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
}

Corralito

Todavía me encuentro con este problema a última hora de la noche. Parece que trata los rendimientos como si tuvieran una vida útil hasta el final de la función, incluso si la función sin el rendimiento pasa el comprobador de préstamos. Esto parece un poco extraño, considerando que el punto de retorno es finalizar la función antes de tiempo.

Si echas un vistazo al ejemplo de

Esto está bloqueado en vidas no léxicas, que a su vez está bloqueado en MIR. Idealmente, esto se solucionará a finales de 2016, pero no llegará pronto.

Alguna discusión sobre los aspectos internos:

Discusión de la función de préstamos no léxicos: https://github.com/rust-lang/rfcs/issues/811

Entonces, leer el [hilo interno] me hace sentir que esto no se puede arreglar (incluso con NLL). ¿Alguien puede confirmar? (@eddyb?)

cc @ descanso largo / largo

De hecho, esto es NLL y, si no me equivoco, lo arreglarían las diversas propuestas que he hecho. Esto corresponde aproximadamente al "caso problemático n. ° 3" de mi primera publicación de blog . La idea básica (en términos de la formulación de la última publicación del blog ) de por qué funcionaría es que solo se requeriría que el subtipo del préstamo se extendiera hasta el final de 'a en la rama del if que causa un return .

@nikomatsakis ¿Podría dar más detalles sobre cómo NLL interactúa con este problema? En mi modelo mental de referencias de &mut , se pueden pasar por mover o volver a mañana , y el problema en este número es que la devolución requiere el modo de movimiento y la reutilización de self después requiere renacer . Según tengo entendido, la vida útil de la nueva &mut renovada está limitada por la vida útil de la variable que contiene la referencia &mut ; en este caso, la variable self , por lo que está limitada por el cuerpo de la función, por lo que no puede extenderse fuera de la llamada a la función. ¿Va a cambiar la NLL esa limitación de los nuevos mañana (o tal vez no existe tal limitación)?

También me pregunto si solucionar este problema es algo inherentemente ligado a NLL o es quizás ortogonal. Si es lo último, ¿quizás vale la pena conseguir una solución antes de la NLL?

Además, si NLL va a solucionar este problema, ¿significa eso que bajo NLL nunca tendrá que elegir manualmente entre un movimiento y un nuevo préstamo?

¿Se solucionará esto pronto?

@krdln

¿Podría dar más detalles sobre cómo NLL interactúa con este problema?

El resumen es que, hoy, si se devuelve un valor de la función en cualquier ruta, el préstamo debe ser válido para el resto de la función en todas las rutas. Bajo la propuesta de NLL que escribí, esa restricción se levanta.

Con más detalle, si toma la firma elaborada aquí, puede ver que debemos devolver una referencia con vida útil 'a :

fn two<'a>(&'a mut self) -> &'a i32 { .. }

ahora observe que llamamos let k = self.one() y luego devolvemos este k . Esto significa que el tipo de k debe ser &'a i32 . Esto a su vez significa que cuando llamamos self.one() , debemos proporcionarle un préstamo de self con el tipo &'a mut Self . Por lo tanto, al let k = self.one() emitimos un préstamo de self por vida 'a . Esta vida útil es mayor que el ciclo (y, de hecho, toda la función), por lo que persiste a medida que recorremos el ciclo, lo que lleva al informe de error. Según las reglas de la NLL que propuse, la vida útil de k solo debe extenderse a 'a si se toma el if . Por lo tanto, si no se toman los if , el préstamo puede finalizar antes.

¿Eso ayuda?


@ osa1

¿Se solucionará esto pronto?

Todavía quedan algunos pasos antes de que podamos hacer esto, pero hay un trabajo activo para hacer las refactorizaciones necesarias.

@nikomatsakis
Gracias, ayudó un poco. Pero hay una pequeña cosa que no entiendo - qué sucede exactamente cuando escribes self en tu código - cómo funciona el rehacer de self . He leído su explicación del "Caso de problema 3" ( get_default ), donde inserta el código dentro de la persona que llama, pero ahí, ha cambiado cada uso de self para tomar prestado el map variable directamente, por lo que el desugaring no me lo aclaró.

Aquí es donde estoy atascado: cuando llamamos let k = self.one() , el self no se puede mover (porque se necesita más adelante), por lo que se considera prestado. Más tarde, devolvemos condicionalmente ese k , de modo que el préstamo debe tener una duración de 'a , que sobrevive a la llamada a la función. ¡Pero! Hemos tomado prestado de self , que vive solo hasta el final de la función. Esa limitación parece acortar 'a , por lo que en mi modelo mental, incluso bajo NLL, deberíamos obtener el error "no vive lo suficiente".

@krdln en realidad hemos tomado prestado de *self , es decir, hemos vuelto a pedir prestado a qué se refiere yo. Le permitimos pedir prestado *self por vida y sobrevivir a self porque el tipo de self implica un 'bloqueo' durante toda la vida 'a . Por lo tanto, siempre que podamos garantizar que durante 'a , self ya no se use, el resultado debe ser una referencia exclusiva; en este caso, después de que fn regrese, self se ha revelado y, por lo tanto, podría no se utilizará, por lo que basta con garantizar que self no se utilice hasta el final de la fn. (Al menos espero que sea cierto. =)

Esto está bloqueado en vidas no léxicas, que a su vez está bloqueado en MIR. Idealmente, esto se solucionará a finales de 2016, pero no llegará pronto.

No es muy sorprendente, pero confirmé que el ejemplo inicial de hecho se compila todas las noches si activa la función de vida útil no léxica.

Creo que me encontré con un problema similar en la producción en el que un préstamo inmutable parece vivir demasiado tiempo ( solución alternativa ).

También debo mencionar que el código de Starwed ya no se compila en la última noche. @oberien sugirió una regression-from-nightly-to-nightly ", y también se sugirió que debería etiquetar a @nikomatsakis en caso de que fuera un problema urgente :)

Me encontré con este problema al intentar hacer algo muy similar (que no está permitido por nll actual):

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

(deberíamos haber eliminado E-needstest cuando eliminamos NLL-fixed-by-NLL)

¿Fue útil esta página
0 / 5 - 0 calificaciones