&[T] ist der allgemeine Element-Slice — eine Referenz auf einen zusammenhängenden Bereich von T-Werten beliebiger Länge. Wo &str das Spezial-Format für UTF-8-Strings ist, ist &[T] die generische Form für jede andere Sequenz: &[i32], &[u8], &[Person], &[(K, V)]. Eine Funktion mit &[T]-Parameter akzeptiert Arrays, Vecs, Sub-Bereiche von beidem — über Auto-Deref-Coercion alles transparent. Dieser Artikel zeigt die Range-Syntax in voller Breite, das interne Speicher-Layout, die wichtigsten Methoden und macht klar, warum &[T] der Standard-Funktions-Parameter für Sequenzen ist.
Was &[T] ist
Genau wie &str ist &[T] ein Fat Pointer:
use std::mem::size_of;
fn main() {
let v: Vec<i32> = vec![10, 20, 30];
let s: &[i32] = &v;
println!("{}", size_of::<&[i32]>()); // 16 — Pointer + Länge
println!("{}", size_of::<&i32>()); // 8 — nur Pointer
println!("{}", s.len()); // 3
}Zwei Felder, je 8 Bytes auf 64-bit:
- Pointer auf das erste Element.
- Länge in Anzahl der Elemente (nicht in Bytes — der Compiler weiß die Element-Größe).
Der Slice besitzt die Elemente nicht — er borrowt sie. Die Original-Sammlung (Array, Vec) bleibt der Besitzer.
Wie ein Slice entsteht
Auto-Deref-Coercion von Vec
let v: Vec<i32> = vec![1, 2, 3, 4, 5];
let s: &[i32] = &v; // &Vec<i32> → &[i32] via Deref
// Vec<T> implementiert Deref<Target = [T]>Auto-Deref-Coercion von Array
let a: [i32; 5] = [1, 2, 3, 4, 5];
let s: &[i32] = &a; // &[i32; 5] → &[i32]Range-Indexierung
let v = vec![10, 20, 30, 40, 50];
let s1: &[i32] = &v[..]; // ganzer Vec als Slice
let s2: &[i32] = &v[..3]; // [10, 20, 30]
let s3: &[i32] = &v[2..]; // [30, 40, 50]
let s4: &[i32] = &v[1..4]; // [20, 30, 40]
let s5: &[i32] = &v[1..=3]; // [20, 30, 40] (inklusiv)Die Range-Syntax kennt fünf Varianten:
| Syntax | Bedeutung |
|---|---|
.. | Alles |
..n | Vom Anfang bis Index n (exklusiv) |
n.. | Ab Index n bis Ende |
n..m | Von n (inklusiv) bis m (exklusiv) |
n..=m | Von n bis m (beide inklusiv) |
Out-of-bounds führt zu Runtime-Panic.
Die wichtigsten Slice-Methoden
Längen und Prüfungen
let v = [10, 20, 30];
let s: &[i32] = &v;
s.len(); // 3
s.is_empty(); // false
s.first(); // Some(&10)
s.last(); // Some(&30)Sicherer Zugriff
let s = [10, 20, 30];
let a = s[1]; // 20 — Panic bei Out-of-Bounds
let b = s.get(1); // Some(&20)
let c = s.get(99); // None — kein Panic
let d = s.get(1..3); // Some(&[20, 30])get ist die fallible Variante — gibt Option<&T> zurück. Bei User-Input oder unsicheren Quellen besser als direkter Index-Zugriff.
Iteration
let s = [1, 2, 3, 4, 5];
for x in s.iter() { // &i32
print!("{x} ");
}
for (i, x) in s.iter().enumerate() { // (usize, &i32)
print!("{i}={x} ");
}
let summe: i32 = s.iter().sum(); // 15
let max = s.iter().max(); // Some(&5)Suche
let s = [10, 20, 30, 40, 50];
s.contains(&30); // true
s.iter().position(|&x| x > 25); // Some(2)
s.iter().find(|&&x| x > 25); // Some(&30)
s.iter().any(|&x| x > 100); // false
s.iter().all(|&x| x > 5); // true
// binary_search auf sortiertem Slice
let pos = s.binary_search(&30); // Ok(2)
let missing = s.binary_search(&25); // Err(2) — würde an Index 2 eingefügtbinary_search ist O(log n) — funktioniert nur auf sortierten Slices. Err(i) gibt die Position, an der der gesuchte Wert eingefügt werden müsste.
Splitting
let s = [1, 2, 0, 3, 4, 0, 5];
// Bei jedem Vorkommen von 0 splitten
let teile: Vec<&[i32]> = s.split(|&x| x == 0).collect();
// [[1, 2], [3, 4], [5]]
// Erste Hälfte / zweite Hälfte
let (links, rechts) = s.split_at(3);
// links = [1, 2, 0], rechts = [3, 4, 0, 5]
// Erstes Element + Rest
if let Some((kopf, schwanz)) = s.split_first() {
println!("Kopf: {kopf}, Schwanz: {schwanz:?}");
}Konvertierungen
let s = [1, 2, 3];
let als_vec: Vec<i32> = s.to_vec(); // alloziert neu
let als_array: [i32; 3] = s.try_into().unwrap(); // wenn Länge passtAPI-Pattern: &[T] als Parameter
Wie bei &str gilt: &[T] ist die idiomatische Form für Sequenz-Parameter. Sie akzeptiert alles, was sich zu einem Slice coercen lässt.
pub fn summe(daten: &[i32]) -> i32 {
daten.iter().sum()
}
fn main() {
let array = [1, 2, 3, 4];
let vec = vec![10, 20, 30];
assert_eq!(summe(&array), 10);
assert_eq!(summe(&vec), 60);
assert_eq!(summe(&vec[1..]), 50);
assert_eq!(summe(&[]), 0); // leerer Slice
}Vier verschiedene Aufrufer-Formen, eine einzige Funktions-Signatur. Clippy warnt mit clippy::ptr_arg, wenn du &Vec<T> statt &[T] schreibst.
Slices sind Copy
Eine Slice-Referenz ist Copy (8/16-Byte-Pointer-Bytes):
fn main() {
let v = vec![1, 2, 3];
let s1: &[i32] = &v;
let s2 = s1; // Kopie der Referenz
let s3 = s1; // weitere Kopie — s1 weiterhin nutzbar
assert_eq!(s1.len() + s2.len() + s3.len(), 9);
}Mehrfache Verwendung in derselben Funktion ohne Borrow-Probleme.
Slices und Lifetimes
Ein Slice ist immer an die Lebenszeit seiner Quelle gebunden:
fn ersten_drei(daten: &[i32]) -> &[i32] {
&daten[..3.min(daten.len())]
}
// Implizit: fn ersten_drei<'a>(daten: &'a [i32]) -> &'a [i32]
fn main() {
let v = vec![1, 2, 3, 4, 5];
let teil = ersten_drei(&v);
println!("{teil:?}"); // [1, 2, 3]
}teil lebt nicht länger als v. Wenn v gedroppt würde, wäre teil ungültig — der Compiler verhindert das. Mehr im Lifetimes-Kapitel.
Praxis: &[T] im echten Code
Statistik-Library
pub fn mittel(werte: &[f64]) -> Option<f64> {
if werte.is_empty() { return None; }
let summe: f64 = werte.iter().sum();
Some(summe / werte.len() as f64)
}
pub fn varianz(werte: &[f64]) -> Option<f64> {
let m = mittel(werte)?;
let summe: f64 = werte.iter()
.map(|v| (v - m).powi(2))
.sum();
Some(summe / werte.len() as f64)
}
fn main() {
let messungen = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(mittel(&messungen), Some(3.0));
assert_eq!(varianz(&messungen), Some(2.0));
}&[f64] als Parameter — keine Allocation, kein Move. Aufrufer kann das gleiche Array für mehrere Berechnungen wiederverwenden.
Lookup-Tabelle
pub fn finde_status(
sortierte_codes: &[(u16, &str)],
code: u16,
) -> Option<&str> {
sortierte_codes
.binary_search_by_key(&code, |&(c, _)| c)
.ok()
.map(|i| sortierte_codes[i].1)
}
fn main() {
let table = [
(200, "OK"),
(404, "Not Found"),
(500, "Internal Server Error"),
];
assert_eq!(finde_status(&table, 404), Some("Not Found"));
assert_eq!(finde_status(&table, 999), None);
}binary_search_by_key ist O(log n) und greift auf Tupel-Felder per Closure zu — sehr effizient für sortierte Tabellen.
Network-Buffer-Verarbeitung
pub fn ist_magic_pattern(buffer: &[u8]) -> bool {
buffer.starts_with(&[0xDE, 0xAD, 0xBE, 0xEF])
}
pub fn extrahiere_payload(buffer: &[u8]) -> &[u8] {
// Erste 4 Bytes sind Magic, dann der Payload
if buffer.len() < 4 || !ist_magic_pattern(buffer) {
return &[];
}
&buffer[4..]
}
fn main() {
let pkt = [0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03];
assert_eq!(extrahiere_payload(&pkt), &[0x01, 0x02, 0x03]);
}&[u8]-Parameter — funktioniert mit Vec<u8>, [u8; N], oder einem Bereich aus einem größeren Buffer.
Sort mit Custom-Closure
pub fn sortiere_nach_laenge(strings: &mut [String]) {
strings.sort_by_key(|s| s.len());
}
fn main() {
let mut v: Vec<String> = vec![
"ccc".into(),
"a".into(),
"bb".into(),
];
sortiere_nach_laenge(&mut v);
assert_eq!(v, vec!["a".to_string(), "bb".into(), "ccc".into()]);
}&mut [T] für In-Place-Sortierung. Funktioniert mit Vec, Array und Sub-Slice.
Histogramm-Generator
use std::collections::HashMap;
pub fn haeufigkeiten<T: Eq + std::hash::Hash + Clone>(items: &[T]) -> HashMap<T, u32> {
let mut counts = HashMap::new();
for item in items {
*counts.entry(item.clone()).or_insert(0) += 1;
}
counts
}
fn main() {
let v = vec!["a", "b", "a", "c", "b", "a"];
let h = haeufigkeiten(&v);
assert_eq!(h.get("a"), Some(&3));
assert_eq!(h.get("b"), Some(&2));
}Generisch über T: Eq + Hash + Clone — funktioniert mit jedem hashbaren Typ.
Median-Berechnung
pub fn median(daten: &[i32]) -> Option<f64> {
if daten.is_empty() { return None; }
let mut sortiert = daten.to_vec();
sortiert.sort();
let mitte = sortiert.len() / 2;
if sortiert.len() % 2 == 0 {
Some((sortiert[mitte - 1] + sortiert[mitte]) as f64 / 2.0)
} else {
Some(sortiert[mitte] as f64)
}
}to_vec() alloziert eine eigene Kopie — das Original bleibt unverändert.
Erste-N-Werte-Extraktion
pub fn top_n<T: Ord + Clone>(daten: &[T], n: usize) -> Vec<T> {
let mut v = daten.to_vec();
v.sort_by(|a, b| b.cmp(a)); // absteigend sortieren
v.into_iter().take(n).collect()
}
fn main() {
let scores = [42, 15, 87, 9, 64];
let top3 = top_n(&scores, 3);
assert_eq!(top3, vec![87, 64, 42]);
}Image-Pixel-Average
pub fn durchschnitts_pixel(rgb: &[u8]) -> Option<[u8; 3]> {
if rgb.len() < 3 || rgb.len() % 3 != 0 {
return None;
}
let mut r: u64 = 0;
let mut g: u64 = 0;
let mut b: u64 = 0;
let pixel_anzahl = (rgb.len() / 3) as u64;
for chunk in rgb.chunks_exact(3) {
r += chunk[0] as u64;
g += chunk[1] as u64;
b += chunk[2] as u64;
}
Some([
(r / pixel_anzahl) as u8,
(g / pixel_anzahl) as u8,
(b / pixel_anzahl) as u8,
])
}chunks_exact(3) iteriert in 3-Byte-Schritten über RGB-Tripel — sehr typisch für Bild-Verarbeitung.
FAQ
Soll ich &[T] oder &Vec nehmen?
Immer &[T]. Funktioniert mit Vec, Array, Sub-Slice — viel flexibler. &Vec<T> schließt Arrays und Sub-Slices aus. Clippy warnt mit clippy::ptr_arg.
Was ist der Unterschied zwischen [T] und &[T]?
[T] ist ein „unsized" Typ — der Compiler kennt seine Größe zur Compile-Zeit nicht. Du kannst keine Variable vom Typ [T] haben. Was du immer hast: eine Referenz (&[T], &mut [T]) oder einen Heap-Wrapper (Box<[T]>, Vec<T>).
iter() liefert &T, into_iter() liefert T.
Bei &[T] ist es immer iter() — du borrowst, du kannst nicht konsumieren. into_iter() würde den Slice verbrauchen, was bei einer Referenz nicht passt. Für konsumierende Iteration: ein Vec besitzen und v.into_iter() aufrufen.
Slice-Indices sind usize.
s[i] erwartet i: usize. Negative Indices gibt es nicht. Für „letztes Element" gibt es s.last() oder s[s.len() - 1].
get(i) ist die fallible Variante von [i].
s.get(i) gibt Option<&T> zurück — None bei Out-of-Bounds. Bei User-Input immer get statt direkter Index-Zugriff, um Panics zu vermeiden.
contains(&x) erwartet eine Referenz.
s.contains(&30) — das & ist nötig, weil contains einen &T-Parameter hat. Bei Strings/Slices oft Verwechslung mit dem normalen Wert.
binary_search braucht sortierten Input.
Die O(log n)-Effizienz funktioniert nur, wenn der Slice sortiert ist. Bei unsortierten Daten gibt es undefiniertes Resultat (nicht UB, aber falsche Ok/Err-Antworten). Sortier mit s.sort() (für Ord-Typen) oder s.sort_by(|a, b| ...).
Slice-Referenzen sind Copy.
Sowohl &[T] als auch &str sind 16 Bytes große, kopierbare Werte. Mehrfach in derselben Funktion zu nutzen, geht ohne Probleme — der Borrow Checker stört nicht.
Weiterführende Ressourcen
Externe Quellen
- std::primitive.slice
- std::slice — Iteratoren
- The Rust Book – Slice Type
- Clippy – ptr_arg
- Rust Reference – Slice types