Références et emprunts

Emprunt d'une possession via une référence

Les mécanismes de transfert de possession présentés précédemment sont fastidieux à utiliser. Rust utilise un mécanisme d'emprunt (borrowing) simplifiant l'utilisation de données en mémoire, par l'intermédiaire d'un mécanisme de référence à une zone mémoire.

Examinons par exemple ce code qui retourne la longueur d'une chaîne stockée dans un type String:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

La notation &s1 indique que l'on emprunte la zone mémoire par l'intermédiaire d'une référence. Le type de s dans la fonction calculate_length est &String qui indique une référence vers une zone mémoire de type String. L'organisation de cette zone mémoire est la suivante:

Three tables: the table for s contains only a pointer to the table
for s1. The table for s1 contains the stack data for s1 and points to the
string data on the heap.

Lorsque l'exécution de la fonction se termine, s est détruite, ainsi que sa mémoire associée, mais pas s1 qui avait été empruntée.

Mutabilité des références

Par défaut, une référence est immutable, même si la zone mémoire est mutable. Ainsi, le code en commentaire ci-dessous est incorrect:

fn main() {
    let mut s = String::from("hello");

    // change(&s);  // invalide

    s.push_str("!");
    println!("{}", s);
}

// Cette fonction est invalide
// fn change(some_string: &String) {
//    some_string.push_str(", world");
//}

Pour permettre à s d'être modifiée lors de l'emprunt, il faut explicitement définir la référence comme étant mutable avec le mot-clef mut, dans la définition du type du paramètre et lors du passage du paramètre:

fn main() {
    let mut s = String::from("hello");

    change(&mut s);

    s.push_str("!");
    println!("{}", s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Il n'est pas possible de créer une référence mutable à partir d'une variable qui ne l'est pas: si s n'était pas mutable, il ne serait pas possible de créer une référence &mut s.

Références multiples

Il est possible de créer plusieurs références en même temps vers une zone mémoire, donc de l'emprunter simultanément, avec néanmoins une restriction importante: il ne doit pas y avoir de référence mutable en même temps qu'une autre référence mutable ou immutable. Une référence mutable verrouille donc l'emprunt. Ce mécanisme de verrou est vérifié au moment de la compilation.

Ainsi, le code ci-dessous est correct, mais la partie en commentaire ne l'est pas:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem

    println!("{}, {}", r1, r2);

    //let r3 = &mut s; // BIG PROBLEM
    //println!("{}, {}, and {}", r1, r2, r3);
}

Cet exemple nous permet d'introduire la notion de durée de vie (liveness) d'une variable, légèrement différente de la notion de portée. Considérons le code suivant:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem

    println!("{}, {}", r1, r2);

    let r3 = &mut s; // no problem
    println!("{}", r3);

    //let r3 = &mut s; // BIG PROBLEM
    //println!("{}", r3);
    //println!("{}, {}", r1, r2);
}

Ainsi, bien que la portée de r1 et r2 soit celle du bloc de la fonction main(), la durée de vie des variables r1 et r2 s'arrête après le println!: comme ces variable ne sont plus utilisées par la suite, Rust considère qu'elle ne sont plus vivantes.

Dangling Reference

Rust ne permet pas de créer des références vers de la mémoire qui peut potentiellement être détruite avant que la référence ne le soit. Ainsi, le code ci-dessous est faux:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

alors que celui-ci est correct:

fn main() {
    let s1 = no_dangle();
}


fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

Synthèse

  • À tout instant, nous pouvons avoir soit une référence mutable, soit un nombre arbitraire de références immutables
  • les références doivent toujours être valides