Slices in Rust
Mit einem Slice (Ausschnitt) leihst du dir einen Teil eines Arrays oder Vektors aus – eine „Ansicht“ auf einen Bereich. Dabei werden keine Daten kopiert, und du übernimmst auch keine Ownership. Wenn du Borrowing (Ausleihen) schon bei einzelnen Werten kennst: Ein Slice ist dieselbe Idee, nur für einen ganzen Bereich von Elementen.
Einen Ausschnitt aus einem Array nehmen
Abschnitt betitelt „Einen Ausschnitt aus einem Array nehmen“fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // Elemente an Index 1, 2, 3 println!("{:?}", middle);}Ausgabe:
[20, 30, 40]1..4 ist dieselbe Art von Bereich (Range), die du schon bei den for-Schleifen gesehen hast. Er beginnt bei Index 1 und endet vor Index 4 – du bekommst also die Indizes 1, 2 und 3.
Kurzschreibweisen für Bereiche
Abschnitt betitelt „Kurzschreibweisen für Bereiche“Ein paar Abkürzungen, die dir oft begegnen:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // vom Anfang bis vor Index 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // ab Index 2 bis zum Ende -> [30, 40, 50] println!("{:?}", &numbers[..]); // alles -> [10, 20, 30, 40, 50]}String-Slices
Abschnitt betitelt „String-Slices“Slices hast du eigentlich schon die ganze Zeit benutzt: &str ist ein Slice eines String (oder einer anderen Zeichenkette)! Deshalb passen die beiden Typen so gut zusammen:
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}Der Satz bleibt hier absichtlich auf Englisch: Bei String-Slices zählen die Indizes Bytes, nicht Buchstaben. Umlaute wie „ä“ oder „ü“ belegen in UTF-8 zwei Bytes. Ein Bereich wie 0..3 könnte so mitten in einem Zeichen enden – und dann löst Rust eine Panic aus.
Warum Slices statt Kopien?
Abschnitt betitelt „Warum Slices statt Kopien?“Du könntest das Problem natürlich auch lösen, indem du den gewünschten Teil in einen neuen Vec oder String kopierst. Slices sparen dir das komplett: Sie zeigen einfach auf einen Teil der vorhandenen Daten – genau wie eine Referenz (&) auf einen einzelnen Wert zeigt. Das heißt: keine zusätzliche Speicherreservierung, kein Kopieren, und die Regeln von der Seite Borrowing gelten weiterhin.
Eine Funktion, die ein Slice entgegennimmt
Abschnitt betitelt „Eine Funktion, die ein Slice entgegennimmt“Als Funktionsparameter sind Slices besonders praktisch, weil sie sowohl Arrays als auch Vektoren akzeptieren:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] nimmt ein Slice aus einem Array oder einem Vec an – eine einzige Funktion, und beide Aufrufe funktionieren.
Daten über ein veränderbares Slice ändern
Abschnitt betitelt „Daten über ein veränderbares Slice ändern“Bisher haben unsere Slices Daten nur gelesen. Mit einem veränderbaren Slice, &mut [T], kann eine Funktion die Elemente ändern, die sie bekommen hat – aber nur genau diese:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // nur Index 1 und 2 println!("{:?}", scores);
double_all(&mut scores); // der ganze Vektor println!("{:?}", scores);}Ausgabe:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]Der erste Aufruf übergibt &mut scores[1..3]. double_all sieht also nur die Indizes 1 und 2. Alles außerhalb dieses Bereichs ist vor der Funktion sicher. Innerhalb der Funktion beginnt das Slice wieder bei Index 0: Für double_all ist es einfach eine Liste aus zwei Zahlen.
Das ist sicher dank einer Regel, die du auf der Seite Borrowing genauer kennenlernst: Solange die Funktion ein &mut-Slice hält, darf nichts anderes diese Elemente benutzen. Zwei Teile deines Codes können sie also nie gleichzeitig ändern. Außerdem muss der Vektor mit mut deklariert sein, sonst lässt Rust dich ihn nicht veränderbar ausleihen.
Übungen
Abschnitt betitelt „Übungen“1. In zwei Hälften teilen
Abschnitt betitelt „1. In zwei Hälften teilen“Leg den Vektor vec![3, 6, 9, 12, 15, 18] an. Gib seine erste und seine zweite Hälfte als zwei getrennte Slices aus. Tipp: Berechne let mid = numbers.len() / 2; und nimm ..mid und mid...
Lösung anzeigen
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("Erste Hälfte: {:?}", first_half); println!("Zweite Hälfte: {:?}", second_half);}Ausgabe:
Erste Hälfte: [3, 6, 9]Zweite Hälfte: [12, 15, 18]Weil mid aus len() berechnet wird, funktioniert das auch noch, wenn du Zahlen hinzufügst oder entfernst. Bei einer ungeraden Länge landet das zusätzliche Element in der zweiten Hälfte.
2. Das größte Element in jeder Liste
Abschnitt betitelt „2. Das größte Element in jeder Liste“Schreib eine Funktion largest(numbers: &[i32]) -> i32, die die größte Zahl zurückgibt. Ruf sie mit einem Array auf, mit einem Vektor und mit einem Slice, das nur das erste Element des Vektors enthält. Tipp: Beginne mit numbers[0] und lauf über den Rest – so wie du es bei einem Array tun würdest.
Lösung anzeigen
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Ausgabe:
93510Wenn du über ein Slice läufst, bekommst du für jedes Element eine Referenz (&i32), nicht die Zahl selbst. *n bedeutet „der Wert, auf den n zeigt“. *n > biggest vergleicht also zwei ganz normale Zahlen. Bei einem leeren Slice löst diese Version eine Panic aus, weil es numbers[0] dann nicht gibt. Später lernst du, für diesen Fall eine Option zurückzugeben.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Ein Slice (
&[T]) leiht sich einen Teil eines Arrays oder Vektors aus, ohne ihn zu kopieren. 1..4wählt die Indizes 1 bis 3. Lässt du eine Seite von..weg, geht der Bereich vom Anfang bzw. bis zum Ende.&strist selbst ein Slice von Text – darum verhalten sich Strings und Slices so ähnlich.- Willst du Arrays und Vektoren gleichermaßen annehmen, nimm
&[T]als Parametertyp. - Mit einem veränderbaren Slice (
&mut [T]) kann eine Funktion genau die Elemente ändern, die sie bekommen hat.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Was kannst du mit einem Slice bei einem Teil eines Arrays oder Vektors machen?
2. Welche Indizes enthält &numbers[1..4]?
3. Warum ist &[i32] ein guter Typ für einen Funktionsparameter?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Der Slice-Typ – Das Rust-Buch: führt Schritt für Schritt zu Slices hin, mit dem Beispiel „erstes Wort eines Strings“.
- Arrays and Slices – Rust by Example (auf Englisch): ausführbare Beispiele dazu, wie du Arrays als Slices ausleihst.
slice– Doku der Standardbibliothek (auf Englisch): die vielen Methoden von Slices, etwafirst,split_at,containsundsort.- Slice types – The Rust Reference (auf Englisch): die genaue Definition, falls du wissen willst, was
[T]und&[T]exakt bedeuten.