¿Hay alguna forma de devolver una referencia a una variable creada en una función?

Resuelto Nex asked hace 8 años • 0 respuestas

Quiero escribir un programa que escriba un archivo en 2 pasos. Es probable que el archivo no exista antes de ejecutar el programa. El nombre del archivo es fijo.

El problema es que OpenOptions.new().write()puede fallar. En ese caso, quiero llamar a una función personalizada trycreate(). La idea es crear el archivo en lugar de abrirlo y devolver un identificador. Dado que el nombre del archivo es fijo, trycreate()no tiene argumentos y no puedo establecer una duración del valor devuelto.

¿Como puedo resolver este problema?

use std::io::Write;
use std::fs::OpenOptions;
use std::path::Path;

fn trycreate() -> &OpenOptions {
    let f = OpenOptions::new().write(true).open("foo.txt");
    let mut f = match f {
        Ok(file)  => file,
        Err(_)  => panic!("ERR"),
    };
    f
}

fn main() {
    {
        let f = OpenOptions::new().write(true).open(b"foo.txt");
        let mut f = match f {
            Ok(file)  => file,
            Err(_)  => trycreate("foo.txt"),
        };
        let buf = b"test1\n";
        let _ret = f.write(buf).unwrap();
    }
    println!("50%");
    {
        let f = OpenOptions::new().append(true).open("foo.txt");
        let mut f = match f {
            Ok(file)  => file,
            Err(_)  => panic!("append"),
        };
        let buf = b"test2\n";
        let _ret = f.write(buf).unwrap();
    }
    println!("Ok");
}
Nex avatar Sep 21 '15 01:09 Nex
Aceptado

La pregunta que hiciste

TL;DR: No, no se puede devolver una referencia a una variable que pertenece a una función. Esto se aplica si creó la variable o si tomó posesión de la variable como argumento de función.

Soluciones

En lugar de intentar devolver una referencia, devuelva un objeto de propiedad. Stringen lugar de &str, Vec<T>en lugar de &[T], Ten lugar de &T, etc.

Si tomó posesión de la variable mediante un argumento, intente tomar una referencia (mutable) y luego devolver una referencia de la misma duración.

En casos excepcionales, puede utilizar código no seguro para devolver el valor de propiedad y una referencia al mismo. Esto tiene una serie de requisitos delicados que debe cumplir para asegurarse de no provocar un comportamiento indefinido o inseguridad en la memoria.

Ver también:

  • Forma correcta de devolver una nueva cadena en Rust
  • Devuelve una cadena local como un segmento (& str)
  • ¿Por qué no puedo almacenar un valor y una referencia a ese valor en la misma estructura?

respuesta más profunda

fjh es absolutamente correcto , pero quiero comentar un poco más profundamente y abordar algunos de los otros errores de su código.

Comencemos con un ejemplo más pequeño de cómo devolver una referencia y observemos los errores:

fn try_create<'a>() -> &'a String {
    &String::new()
}

Óxido 2015

error[E0597]: borrowed value does not live long enough
 --> src/lib.rs:2:6
  |
2 |     &String::new()
  |      ^^^^^^^^^^^^^ temporary value does not live long enough
3 | }
  | - temporary value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 1:15...
 --> src/lib.rs:1:15
  |
1 | fn try_create<'a>() -> &'a String {
  |               ^^

Óxido 2018

error[E0515]: cannot return reference to temporary value
 --> src/lib.rs:2:5
  |
2 |     &String::new()
  |     ^-------------
  |     ||
  |     |temporary value created here
  |     returns a reference to data owned by the current function

¿Hay alguna forma de devolver una referencia de una función sin argumentos?

Técnicamente "sí", pero para lo que quieras, "no".

Una referencia apunta a un fragmento de memoria existente. En una función sin argumentos, las únicas cosas a las que se puede hacer referencia son las constantes globales (que tienen la vida útil &'static) y las variables locales. Ignoraré los globales por ahora.

En un lenguaje como C o C++, podrías tomar una referencia a una variable local y devolverla. Sin embargo, tan pronto como la función regrese, no hay garantía de que la memoria a la que hace referencia siga siendo lo que pensaba que era. Puede que siga siendo lo que esperas durante un tiempo, pero eventualmente la memoria se reutilizará para otra cosa. Tan pronto como su código mire la memoria e intente interpretar un nombre de usuario como la cantidad de dinero que queda en la cuenta bancaria del usuario, ¡surgirán problemas!

Esto es lo que previene la vida útil de Rust: no se le permite usar una referencia más allá del tiempo de validez del valor al que se hace referencia en su ubicación de memoria actual.

Ver también:

  • ¿Es posible devolver un tipo prestado o propio en Rust?
  • ¿Por qué puedo devolver una referencia a un literal local pero no a una variable?

Tu problema real

Mire la documentación para OpenOptions::open:

fn open<P: AsRef<Path>>(&self, path: P) -> Result<File>

Devuelve un Result<File>, por lo que no sé cómo esperarías devolver un OpenOptionso una referencia a uno. Tu función funcionaría si la reescribieras como:

fn trycreate() -> File {
    OpenOptions::new()
        .write(true)
        .open("foo.txt")
        .expect("Couldn't open")
}

Esto suele Result::expectprovocar pánico con un útil mensaje de error. Por supuesto, entrar en pánico en las entrañas de su programa no es muy útil, por lo que se recomienda propagar los errores nuevamente:

fn trycreate() -> io::Result<File> {
    OpenOptions::new().write(true).open("foo.txt")
}

Optiony Resulttengo muchos métodos interesantes para lidiar con la lógica de error encadenada. Aquí puedes usar or_else:

let f = OpenOptions::new().write(true).open("foo.txt");
let mut f = f.or_else(|_| trycreate()).expect("failed at creating");

También devolvería el Resultde main. Todos juntos, incluidas las sugerencias de fjh:

use std::{
    fs::OpenOptions,
    io::{self, Write},
};

fn main() -> io::Result<()> {
    let mut f = OpenOptions::new()
        .create(true)
        .write(true)
        .append(true)
        .open("foo.txt")?;

    f.write_all(b"test1\n")?;
    f.write_all(b"test2\n")?;

    Ok(())
}
Shepmaster avatar Sep 20 '2015 19:09 Shepmaster

¿Hay alguna forma de devolver una referencia de una función sin argumentos?

No (excepto las referencias a valores estáticos, pero no son útiles aquí).

Sin embargo, es posible que desees consultar OpenOptions::create. Si cambia su primera línea maina

let  f = OpenOptions::new().write(true).create(true).open(b"foo.txt");

el archivo se creará si aún no existe, lo que debería resolver su problema original.

fjh avatar Sep 20 '2015 19:09 fjh

No se puede devolver una referencia que apunte a una variable local. Tiene dos alternativas: devolver el valor o utilizar una variable estática.

He aquí por qué:

Las referencias son indicadores de ubicaciones de memoria. Una vez que se ejecutan las funciones, las variables locales se eliminan de la pila de ejecución y los recursos se desasignan. Después de ese punto, cualquier referencia a una variable local apuntará a algunos datos inútiles. Dado que se desasignó, ya no está en posesión de nuestro programa y es posible que el sistema operativo ya lo haya entregado a otro proceso y que nuestros datos se hayan sobrescrito.

En el siguiente ejemplo, xse crea cuando se ejecuta la función y se elimina cuando la función completa su ejecución. Es local de la función y vive en la pila de esta función en particular. La pila de funciones contiene variables locales.

Cuando runsale de la pila de ejecución, cualquier referencia a x, &xapuntará a algunos datos basura. Esto es lo que la gente llama un puntero colgante. El compilador Rust no permite el uso de punteros colgantes ya que no es seguro.

fn run() -> &u32 {
    let x: u32 = 42;

    return &x;
} // x is dropped here

fn main() {
    let x = run();
}

Por eso no podemos devolver una referencia a una variable local. Tenemos dos opciones: devolver el valor o utilizar una variable estática.

Devolver el valor es la mejor opción aquí. Al devolver el valor, pasará el resultado de su cálculo a la persona que llama; en términos de Rust, xserá propiedad de la persona que llama. En nuestro caso lo es main. Entonces, no hay problema.

Dado que una variable estática vive mientras se ejecuta el proceso, sus referencias apuntarán a la misma ubicación de memoria tanto dentro como fuera de la función. Ahí tampoco hay problema.

Nota: @navigaid recomienda usar un cuadro, pero no tiene sentido porque está moviendo datos fácilmente disponibles al montón encajándolos y luego devolviéndolos. No resuelve el problema, todavía estás devolviendo la variable local a la persona que llama pero usando un puntero al acceder a ella. Agrega una dirección indirecta innecesaria debido a la desreferenciación, por lo que incurre en costos adicionales. Básicamente lo usarás &por usarlo, nada más.

snnsnn avatar Sep 11 '2019 18:09 snnsnn

Esta es una explicación de la respuesta de snnsnn , que explicó brevemente el problema sin ser demasiado específica.

Rust no permite devolver una referencia a una variable creada en una función. ¿Existe alguna solución? Sí, simplemente coloque esa variable en un cuadro y luego devuélvala. Ejemplo:

fn run() -> Box<u32> {
    let x: u32 = 42;
    return Box::new(x);
} 

fn main() {
    println!("{}", run());
}

código en el patio de recreo oxidado

Como regla general, para evitar problemas similares en Rust, devuelva un objeto propio (Box, Vec, String, ...) en lugar de una referencia a una variable:

  • Box<T>en lugar de&T
  • Vec<T>en lugar de&[T]
  • Stringen lugar de&str

Para otros tipos, consulte La tabla periódica de tipos de óxido para determinar qué objeto de propiedad utilizar.

Por supuesto, en este ejemplo puedes simplemente devolver el valor ( Ten lugar de &To Box<T>)

fn run() -> u32 {
    let x: u32 = 42;
    return x;
} 
btwiuse avatar Oct 04 '2020 14:10 btwiuse