Tranches

Les tranches (slices) sont des références spéciales permettant d'emprunter une séquence d'éléments d'une collection plutôt que la collection complète. Une collection est une suite d'éléments: une chaîne de caractères, un vecteur, un tableau, etc.

Tranche de chaîne de caractère

Prenons l'exemple d'une fonction devant retourner le premier mot d'une chaîne de caractères constituée de mots séparés par des espaces:

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

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

    let word = first_word(&s); // word will get the value 5

    s.clear(); // this empties the String, making it equal to ""   
    // word still has the value 5 here, but there's no more string that
    // we could meaningfully use the value 5 with. word is now totally invalid!

    println!("End index of first word: {}", word);
}

Bien que correcte, cette implémentation n'est pas pertinente. En effet, même si word contient toujours 5 après l'appel à s.clear(), cet index n'a plus de sens.

Afin d'implémenter cette fonction de manière plus pertinente, il est plus judicieux d'utiliser un mécanisme de tranche:

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

    let hello = &s[0..5];
    let world = &s[6..11];

    println!("{}:{}", hello, world);
}

La représentation en mémoire est la suivante:

Three tables: a table representing the stack data of s, which points
to the byte at index 0 in a table of the string data "hello world" on
the heap. The third table rep-resents the stack data of the slice world, which
has a length value of 5 and points to byte 6 of the heap data table.

Une tranche de chaîne est dénotée en Rust par &src, ce qui nous permet de réécrire la fonction précédente en:

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

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

    let word = first_word(&s); // word will get the value 5

    // s.clear(); // Incorrect !

    println!("first word: {}", word);
}

L'appel s.clear() a été mis en commentaire car il est incorrect. En effet, nous avons créé une référence immutable à s lors de l'appel à first_word, transmise sous la forme d'une tranche à word. Nous ne pouvons donc pas changer s par l'intermédiaire d'une référence mutable.

Les littéraux de type chaîne de caractère sont des tranches de type &str:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let hs = "hello world";
    let my_string = String::from(hs);

    // `first_word` works on slices of `String`s, whether partial or whole
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    // `first_word` also works on references to `String`s, which are equivalent
    // to whole slices of `String`s
    let word = first_word(&my_string);

    let my_string_literal = "hello world";

    // `first_word` works on slices of string literals, whether partial or whole
    let word = first_word(&my_string_literal[0..6]);
    let word = first_word(&my_string_literal[..]);

    // Because string literals *are* string slices already,
    // this works too, without the slice syntax!
    let word = first_word(my_string_literal);
}

Autres tranches

fn main() {
    let a = [1, 2, 3, 4, 5];

    let slice = &a[1..3];

    assert_eq!(slice, &[2, 3]);
}