Ferris Talk #1: Iteratoren in Rust

Seite 4: Warum erst jetzt?

Inhaltsverzeichnis

Zum Abschluss wollen wir der Frage nachgehen, warum die Implementierung von IntoIterator für Felder so lange gedauert hat.

In früheren Rust-Versionen war IntoIterator nur für Referenzen auf Felder implementiert, daher konnte man nur by Reference über Felder iterieren. Der Grund dafür war, dass Arrays in Rust immer eine fixe Länge haben und die Länge Teil der Typdefinition ist, beispielsweise let numbers: [i32; 7] für ein Feld mit sieben Elementen vom Typ i32. Für eine generische Implementierung von IntoIterator für Felder ist es daher notwendig, die konstante Länge als Parameter für den generischen Typ übergeben zu können (wie impl<T, const N: usize> IntoIterator for [T; N] {...}). Diese Sprachfunktion wird als Const Generics bezeichnet und kam erst Anfang 2021 mit Rust 1.51.

Auf Basis der Const Generics-Funktion hätte man sofort IntoIterator für Felder implementieren können. Jedoch konnten Entwicklerinnen und Entwickler schon früher array.into_iter() aufrufen, was der Compiler aber implizit als (&array).into_iter() umgesetzt hat. Sie bekamen somit einen by Reference-Iterator. Das simple Hinzufügen von IntoIterator für Felder hätte also bestehenden Code gebrochen; und das ist nur bei Major Releases von Rust erlaubt.

Mit Rust 1.53 hat das Rust-Team zu einem Trick gegriffen: Wenn bestehender Code explizit array.into_iter() aufruft, wird daraus weiterhin (&array).into_iter(), allerdings mit einer Warnung, dass der Code in späteren Rust-Versionen nicht mehr funktionieren. Wenn array.into_iter() implizit aufgerufen wird (wie in einer for-Schleife), wird die neue by Value-Implementierung von IntoIterator für Felder verwendet. Dass sich der into_iter()-Aufruf für Felder anders verhält als der bei anderen Datenstrukturen (unter anderem Vektoren), ist nicht schön, bietet aber endlich die Möglichkeit, by Value über Felder zu iterieren und mit der for-Schleife Felder zu durchlaufen.

Das folgende Codebeispiel demonstriert das Vorgehen. Zum besseren Verständnis sind die Datentypen der Variablen explizit angegeben sind, auch wenn Rust diese Typen implizit ermitteln könnte.

use std::{vec::IntoIter};

fn main() {
    // Iterate over vector by value
    let numbers: Vec<i32> = vec![1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: IntoIter<i32> = numbers.into_iter();
    let item: i32 = num_iter.next().unwrap();
    println!("{}", item);

    // Iterate over vector by reference
    let numbers: &Vec<i32> = &vec![1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: std::slice::Iter<i32> = numbers.into_iter();
    let item: &i32 = num_iter.next().unwrap();
    println!("{}", item);

    // In contrast to vector, iterating over an array means
    // iterating BY REFERENCE, not by value. Note that this
    // code leads to a warning because this behavior will
    // change in future Rust versions.
    let numbers: [i32; 7] = [1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: std::slice::Iter<i32> = numbers.into_iter();
    let item: &i32 = num_iter.next().unwrap();
    println!("{}", item);

    // Since Rust 1.53, we can explicitly call IntoIterator::into_iter
    // as it is now implemented for arrays. It will lead to an iteration
    // by value, NO LONGER by reference.
    let numbers: [i32; 7] = [1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: std::array::IntoIter<i32, 7> = IntoIterator::into_iter(numbers);
    let item: i32 = num_iter.next().unwrap();
    println!("{}", item);

    // As IntoIterator has been implemented in Rust 1.53, the following
    // syntax is now possible. It iterates over the given array BY VALUE
    // using a for loop.
    for i in [1, 1, 2, 3, 5] {
        println!("{}", i)
    }
}