Wie man in Rust mit einer Schrittweite von 3, 5 oder 100 über Zahlen iteriert? Vollständige Analyse
Stellen Sie sich vor: Sie programmieren in Rust und müssen alle geraden Zahlen von 0 bis 20 ausgeben. Oder, sagen wir, eine Sequenz 0, 5, 10, 15, 20 erstellen. In Python würden Sie einfach range(0, 21, 5) schreiben – und fertig. Wie macht man das in Rust? Hier gibt es Nuancen, und heute werden wir sie uns ansehen.
Diese Frage ist eine der beliebtesten auf Stack Overflow (tausende Aufrufe). Und das ist kein Wunder: Die Standard-for i in 0..10-Schleife funktioniert nur mit Schrittweite 1. Was aber, wenn eine andere Schrittweite benötigt wird? Es gibt tatsächlich drei Methoden, jede mit ihren eigenen Vor- und Nachteilen.
Wir zeigen nicht nur den Code – wir vergleichen die Ansätze hinsichtlich Leistung, Lesbarkeit und Flexibilität. Los geht's!
Warum gibt es in Rust keinen integrierten range mit Schrittweite?
Die kurze Antwort: Weil Range in Rust ein einfacher Iterator ist, der eine Folge von ganzen Zahlen erzeugt. Er unterstützt standardmäßig keine Schrittweite, um möglichst leicht und vorhersagbar zu bleiben. Aber die Entwickler haben Methoden bereitgestellt, die diese Aufgabe lösen.
Die lange Antwort: In der Rust-Ökosystem gilt die Philosophie „explizit ist besser als implizit". Anstatt die Schrittweite in die Syntax des Bereichs zu integrieren, haben die Entwickler flexible Werkzeuge zur Anpassung von Iteratoren vorgeschlagen. Und das eröffnet mehr Möglichkeiten, als man denkt.
Methode 1: step_by() – der direkteste Weg
In der Standardbibliothek von Rust gibt es die Methode step_by(n), die einen Iterator erzeugt, der jedes n-1-te Element überspringt. So funktioniert sie:
fn main() { // Gibt Zahlen von 0 bis 20 mit Schrittweite 5 aus for i in (0..=20).step_by(5) { println!("{}", i); } // Ausgabe: 0, 5, 10, 15, 20}Beachten Sie: Ich habe 0..=20 (inklusiv) verwendet, nicht 0..20 (exklusiv). Wenn Sie eine Schrittweite von 3 bis 10 benötigen, schreiben Sie (0..10).step_by(3) – Sie erhalten 0, 3, 6, 9.
Vorteile von step_by():
- Einfachheit – eine Zeile, keine Closures
- Lesbarkeit – man sieht sofort, was gemacht wird
- Leistung – minimaler Overhead
Nachteile:
- Funktioniert nur mit ganzen Zahlen (i32, u64 usw.)
- Man kann keine dynamische Schrittweite pro Iteration festlegen
- Erst seit Rust 1.28 verfügbar (aber das ist schon lange her)
Diese Methode ist die ideale Wahl für 90% der Aufgaben. Wenn Sie einfach mit einer konstanten Schrittweite über Zahlen iterieren müssen – verwenden Sie step_by().
Methode 2: filter() – wenn die Schrittweite eine Bedingung ist
Was aber, wenn die Schrittweite nicht konstant ist? Zum Beispiel müssen Sie Zahlen ausgeben, die ohne Rest durch 3 teilbar sind. Oder Zahlen, deren Quersumme größer als 5 ist. Hier kommt filter() zur Hilfe:
fn main() { // Alle Zahlen von 0 bis 30, die durch 4 teilbar sind for i in (0..=30).filter(|x| x % 4 == 0) { println!("{}", i); } // Ausgabe: 0, 4, 8, 12, 16, 20, 24, 28
// Und jetzt – Zahlen, die nicht durch 3 teilbar sind for i in (0..20).filter(|x| x % 3 != 0) { println!("{}", i); } // Ausgabe: 1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19}Hier übergeben wir ein Closure, das jede Zahl überprüft. filter() behält nur die Elemente, für die die Bedingung wahr ist.
Wann ist filter() besser als step_by():
- Die Schrittweite hängt vom Wert ab (z. B. „alle Primzahlen")
- Es muss mit anderen Bedingungen kombiniert werden
- Die Schrittweite ist nicht konstant (z. B. Fibonacci-Zahlen)
Nachteile:
- Etwas langsamer als step_by() – jede Zahl muss überprüft werden
- Der Code kann für Anfänger weniger offensichtlich sein
Ich persönlich verwende filter(), wenn es nicht einfach um „jede n-te Zahl" geht, sondern um „jede, die einer Regel entspricht". Das ist mächtiger, erfordert aber mehr Aufmerksamkeit.