No puede salir del contenido prestado / no puede salir de una referencia compartida

127

No entiendo el error cannot move out of borrowed content. Lo he recibido muchas veces y siempre lo he resuelto, pero nunca he entendido por qué.

Por ejemplo:

for line in self.xslg_file.iter() {
    self.buffer.clear();

    for current_char in line.into_bytes().iter() {
        self.buffer.push(*current_char as char);
    }

    println!("{}", line);
}

produce el error:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:31:33
   |
31 |             for current_char in line.into_bytes().iter() {
   |                                 ^^^^ cannot move out of borrowed content

En versiones más recientes de Rust, el error es

error[E0507]: cannot move out of `*line` which is behind a shared reference
  --> src/main.rs:31:33
   |
31 |             for current_char in line.into_bytes().iter() {
   |                                 ^^^^ move occurs because `*line` has type `std::string::String`, which does not implement the `Copy` trait

Lo resolví clonando line:

for current_char in line.clone().into_bytes().iter() {

No entiendo el error incluso después de leer otras publicaciones como:

¿Cuál es el origen de este tipo de error?

Peekmo
fuente
1
¿Has mirado preguntas como esta ? (Por cierto, las cadenas ofrecen el .bytes()método.)
Huon
Sí, lo busqué, pero no entendí :( Y mi cadena es std :: string :: String, de acuerdo con la documentación, no hay ningún método .bytes ()
Peekmo
44
Se llama.as_bytes()
bluss
De hecho, gracias, funciona as_bytes()sin clonación. Pero todavía no entiendo por qué?
Peekmo
Stringobtiene el bytesmétodo de str.
Huon

Respuestas:

108

Veamos la firma de into_bytes:

fn into_bytes(self) -> Vec<u8>

Esto toma self, no una referencia a uno mismo ( &self). Eso significa que selfse consumirá y no estará disponible después de la llamada. En su lugar, obtienes un Vec<u8>. El prefijo into_es una forma común de denotar métodos como este.

No sé exactamente qué iter()devuelve su método, pero supongo que es un iterador terminado &String, es decir, devuelve referencias a un Stringpero no le da la propiedad de ellos. Eso significa que no puede llamar a un método que consume el valor .

Como has encontrado, una solución es usar clone. Esto crea un objeto duplicado que le pertenece y que puede llamar into_bytes. Como mencionan otros comentaristas, también puede usar as_byteslas tomas &self, por lo que funcionará con un valor prestado. Cuál debe usar depende de su objetivo final para lo que hace con el puntero.

En la imagen más grande, todo esto tiene que ver con la noción de propiedad . Ciertas operaciones dependen de poseer el artículo, y otras operaciones pueden salirse con el préstamo del objeto (quizás mutablemente). Una referencia ( &foo) no otorga propiedad, es solo un préstamo.

¿Por qué es interesante usar en selflugar de &selfen los argumentos de una función?

La transferencia de propiedad es un concepto útil en general: cuando haya terminado con algo, alguien más puede tenerlo. En Rust, es una forma de ser más eficiente. Puedo evitar asignar una copia, darle una copia y luego tirar mi copia. La propiedad es también el estado más permisivo; si poseo un objeto, puedo hacer con él lo que desee.


Aquí está el código que creé para probar:

struct IteratorOfStringReference<'a>(&'a String);

impl<'a> Iterator for IteratorOfStringReference<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

struct FileLikeThing {
    string: String,
}

impl FileLikeThing {
    fn iter(&self) -> IteratorOfStringReference {
        IteratorOfStringReference(&self.string)
    }
}

struct Dummy {
    xslg_file: FileLikeThing,
    buffer: String,
}

impl Dummy {
    fn dummy(&mut self) {
        for line in self.xslg_file.iter() {
            self.buffer.clear();

            for current_char in line.into_bytes().iter() {
                self.buffer.push(*current_char as char);
            }

            println!("{}", line);
        }
    }
}

fn main() {}
Shepmaster
fuente