Ein Slice in Rust ist eine dynamische Darstellung eines Teils einer Sammlung, deren Elemente sequenziell im Speicher angeordnet sind. Ein typisches Beispiel für ein Slice ist &[T] oder &mut [T]. Slices besitzen keine Daten, sondern verweisen auf einen externen Puffer. Alle Slices haben eine Länge, die zusammen mit der Referenz gespeichert wird.
Haupttypen:
&[T] — unveränderliches Slice&mut [T] — veränderliches Slice&str (faktisch — Byte-Slice, immer korrektes UTF-8)Die Sicherheit von Slices wird auf der Ebene des Compilers und zur Laufzeit gewährleistet: Jegliche Versuche, auf ein Element außerhalb des Slices zuzugreifen, führen zur Panik (panic) zur Laufzeit, es tritt kein undefiniertes Verhalten im Speicher wie in C/C++ auf.
Beispiel:
let v = vec![1, 2, 3, 4, 5]; let s: &[i32] = &v[1..4]; // Slice, verweist auf die Elemente 2, 3, 4 println!("{:?}", s); // [2, 3, 4] // s[3]; // panic! (außerhalb der Grenzen)
Können Slices leer sein? Was unterscheidet ein leeres Slice von None?
Antwort: Ja, Slices können leer sein (&[]), das bedeutet einen Verweis auf einen Datenteil mit null Länge, ist jedoch nicht das Gleiche wie None. Ein leeres Slice ist sicher zu verwenden, während Option<&[T]> dazu dient," gibt es überhaupt ein Slice" zu unterscheiden.
Beispiel:
let s: &[i32] = &[]; assert!(s.is_empty()); // Option<&[i32]> wird verwendet, wenn es das Slice möglicherweise nicht gibt.
Geschichte
In einem großen Log-Service nahm ein Programmierer ein Slice anhand eines dynamisch berechneten Bereichs von Benutzerdaten. Bei fehlerhafter Berechnung des Slices (z.B. bei start > end oder wenn end > len) lief der Code in Tests, verursachte jedoch in der Produktion einen "panic" und stoppte den Prozess während der Lastspitzen.
Geschichte
In einer internen Bibliothek für konkurrierende Hashing-Operationen verwendeten sie &mut [T], und mehrere Threads nahmen gleichzeitig verschiedene Slices eines Arrays. Ein Thread änderte das Slice, während ein anderer ein anderes Slice auf denselben Speicher zugriff. Das Programm wurde kompiliert, aber aufgrund falscher Teilung konnte es zu UB durch unsicheren Code (Verwendung von unsafe) kommen, wenn die Slices sich überschneiden.
Geschichte
In einem systematischen Parser für Netzwerkpakete wurde ein Slice unsicher erstellt (raw pointer und from_raw_parts). Der Entwickler vergaß, die Korrektheit der Länge des eingehenden Pakets zu überprüfen. Infolgedessen führte der Versuch, außerhalb der Grenzen zu lesen, zum Absturz der Anwendung und zu einer Schwachstelle (potential OOB access), die durch die richtige Anwendung von sicheren Slices hätte behoben werden können.