web-dev-qa-db-fra.com

Durée de vie en Rust

Parfois, je me suis retrouvé à vouloir écrire des fonctions qui peuvent être appelées de deux manières:

// With a string literal:
let lines = read_file_lines("data.txt");

// With a string pointer:
let file_name = ~"data.txt";
let lines = read_file_lines(file_name);

Ma première supposition a été d'utiliser un pointeur emprunté (&str) pour le type de paramètre, mais quand cela n'a pas fonctionné (cela m'a seulement permis d'utiliser @str et ~str), J'ai essayé ce qui suit (en copiant les bibliothèques Rust), qui fonctionnaient.

fn read_file_lines<'a>(path: &'a str) -> ~[~str] {
    let read_result = file_reader(~Path(path));
    match read_result {
        Ok(file) => file.read_lines(),
        Err(e) => fail!(fmt!("Error reading file: %?", e))
    }
}

Le problème est que je ne comprends pas ce que je fais. D'après ce que je peux rassembler (principalement à partir d'erreurs de compilation), je déclare une durée de vie sur laquelle il n'y a pas de restriction et je l'utilise pour décrire le paramètre de chemin (ce qui signifie que toute durée de vie peut être passée en tant que paramètre).

Alors:

  • Ma compréhension est-elle vaguement exacte?
  • Qu'est-ce qu'une vie? Où puis-je en savoir plus à leur sujet?
  • Quelle est la différence entre un paramètre de type &str et un paramètre de type &'a str dans l'exemple ci-dessus?
  • Et pendant que j'y suis, qu'est-ce que 'self?

(J'utilise Rust 0.7, si cela fait une différence dans la réponse)

53
Daniel

Mise à jour 2015-05-16 : le code de la question d'origine s'appliquait à une ancienne version de Rust, mais les concepts restent les mêmes. Cette réponse a été mise à jour pour utiliser la syntaxe/bibliothèques modernes Rust. (Changer essentiellement ~[] En Vec et ~str En String et en ajustant l'exemple de code à la fin.)

Ma compréhension est-elle vaguement exacte?
[...]
Quelle est la différence entre un paramètre de type & str et un paramètre de type & 'a str dans l'exemple ci-dessus?

Oui, une vie comme ça dit essentiellement "pas de restrictions", en quelque sorte. Les durées de vie sont un moyen de connecter les valeurs de sortie aux entrées, c'est-à-dire que fn foo<'a, T>(t: &'a T) -> &'a T dit que foo renvoie un pointeur qui a la même durée de vie que t, c'est-à-dire les données qu'il pointe to est valide pour la même durée que t (enfin, strictement, au moins aussi longtemps que). Cela implique essentiellement que la valeur de retour pointe vers une sous-section de la mémoire vers laquelle t pointe.

Ainsi, une fonction comme fn<'a>(path: &'a str) -> Vec<String> est très similaire à l'écriture de { let x = 1; return 2; } ... c'est une variable inutilisée.

Rust attribue des durées de vie par défaut lors de l'écriture de &str, Ce qui équivaut exactement à l'écriture de la durée de vie variable inutilisée. c'est-à-dire que fn(path: &str) -> Vec<String> n'est pas différent de la version avec 'a s. La seule fois où une vie est interrompue est différente de l'inclusion si vous devez appliquer un pointeur global (c'est-à-dire la durée de vie spéciale 'static) Ou si vous souhaitez renvoyer une référence (par exemple -> &str ) qui n'est possible que si la valeur de retour a une durée de vie (et cela doit être soit la durée de vie d'une ou plusieurs entrées, soit 'static).

Qu'est-ce qu'une vie? Où puis-je en savoir plus à leur sujet?

Une durée de vie est la durée d'existence garantie des données pointeur, par ex. une variable globale est garantie pour durer "pour toujours" (donc elle a la durée de vie spéciale 'static). Une bonne façon de les regarder est: les durées de vie connectent les données au cadre de pile sur lequel leur propriétaire est placé; une fois que ce cadre de pile se termine, le propriétaire sort de la portée et tous les pointeurs vers/dans cette valeur/structure de données ne sont plus valides, et la durée de vie est un moyen pour le compilateur de raisonner à ce sujet. (Avec la vue du cadre de pile, c'est comme si @ Avait un cadre de pile spécial associé à la tâche en cours, et statics avait un cadre de pile "global").

Il y a aussi un chapitre des vies du livre , et ce Gist (NB. Le code est maintenant obsolète mais les concepts sont toujours vrais) est une petite démonstration soignée de la façon dont on peut utiliser des durées de vie pour éviter d'avoir à copier/allouer (avec une forte garantie de sécurité: pas de possibilité de balancer les pointeurs).

Et pendant que j'y suis, qu'est-ce que 'self?

Littéralement rien de spécial, seuls certains endroits nécessitent que les types aient des durées de vie (par exemple dans les définitions de struct/enum et dans impls), et actuellement 'self Et 'static Sont les seuls noms acceptés. 'static Pour les pointeurs globaux toujours valides, 'self Pour quelque chose qui peut avoir n'importe quelle durée de vie. C'est un bogue qui appelle cette vie (non -static) toute autre chose que self est une erreur.


Dans l'ensemble, j'écrirais cette fonction comme:

use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;

fn read_file_lines(path: &Path) -> Vec<String> {
    match File::open(path) {
        Ok(file) => {
            let read = BufReader::new(file);
            read.lines().map(|x| x.unwrap()).collect()
        }
        Err(e) => panic!("Error reading file: {}", e)
    }
}

fn main() {
   let lines = read_file_lines(Path::new("foo/bar.txt"));
   // do things with lines
}
56
huon