web-dev-qa-db-fra.com

Existe-t-il un moyen de renvoyer une référence à une variable créée dans une fonction?

Je veux écrire un programme qui va écrire un fichier en 2 étapes. Il est probable que le fichier n'existe pas avant l'exécution du programme. Le nom de fichier est fixe.

Le problème est que OpenOptions.new().write() peut échouer. Dans ce cas, je veux appeler une fonction personnalisée trycreate(). L'idée est de créer le fichier au lieu de l'ouvrir et de retourner un handle. Puisque le nom de fichier est fixe, trycreate() n'a pas d'arguments et je ne peux pas définir une durée de vie de la valeur retournée.

Comment puis-je résoudre ce problème?

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");
}
28
Nex

fjh est absolument correct , mais je veux commenter un peu plus profondément et aborder certaines des autres erreurs avec votre code.


Commençons par un exemple plus petit de renvoi d'une référence et examinons les erreurs:

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

Rust 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 {
  |               ^^

Rust 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

Existe-t-il un moyen de renvoyer une référence d'une fonction sans arguments?

Techniquement "oui", mais pour ce que vous voulez, "non".

Une référence pointe vers un morceau de mémoire existant. Dans une fonction sans argument, les seules choses qui pourraient être référencées sont des constantes globales (qui ont la durée de vie &'static) et les variables locales. Je vais ignorer les globaux pour l'instant.

Dans un langage comme C ou C++, vous pouvez réellement prendre une référence à une variable locale et la renvoyer. Cependant, dès que la fonction revient, il n'y a aucune garantie que la mémoire à laquelle vous faites référence continue d'être ce que vous pensiez qu'elle était. Cela peut rester ce que vous attendez pendant un certain temps, mais la mémoire sera finalement réutilisée pour autre chose. Dès que votre code examine la mémoire et essaie d'interpréter un nom d'utilisateur comme le montant d'argent restant dans le compte bancaire de l'utilisateur, des problèmes surviennent!

C'est ce que les durées de vie de Rust empêchent - vous n'êtes pas autorisé à utiliser une référence au-delà de la durée de validité de la valeur référencée à son emplacement de mémoire actuel.

Au lieu d'essayer de renvoyer une référence, renvoyez un objet possédé. String au lieu de &str, Vec<T> au lieu de &[T], T au lieu de &T, etc.

Voir également:

Votre problème réel

Regardez la documentation de OpenOptions::open :

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

Il renvoie un Result<File>, donc je ne sais pas comment vous vous attendez à retourner un OpenOptions ou une référence à un. Votre fonction fonctionnerait si vous la réécriviez comme suit:

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

Cela utilise Result::expect pour paniquer avec un message d'erreur utile. Bien sûr, paniquer dans les tripes de votre programme n'est pas super utile, il est donc recommandé de propager vos erreurs:

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

Option et Result ont beaucoup de méthodes Nice pour gérer la logique d'erreur chaînée. Ici, vous pouvez utiliser or_else :

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

Je retournerais également le Result de main. Tous ensemble, y compris les suggestions 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(())
}
40
Shepmaster

Existe-t-il un moyen de renvoyer une référence d'une fonction sans arguments?

Non (sauf les références aux valeurs statiques, mais celles-ci ne sont pas utiles ici).

Cependant, vous voudrez peut-être regarder OpenOptions::create . Si vous changez votre première ligne dans main en

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

le fichier sera créé s'il n'existe pas encore, ce qui devrait résoudre votre problème d'origine.

16
fjh

Les références sont des pointeurs. Une fois les fonctions exécutées, elles sont extraites de la pile d'exécution et les ressources sont désallouées.

Pour l'exemple suivant, x est supprimé à la fin du bloc. Après ce point, la référence &x pointera vers certaines données inutiles. Fondamentalement, c'est un pointeur suspendu. Le compilateur Rust ne permet pas une telle chose car il n'est pas sûr.

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

    return &x;
} // x is dropped here

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