Ein &mut [T] ist die mutable Variante des Element-Slice — eine Referenz auf einen Bereich, durch die du In-Place schreiben kannst. Anders als bei &[T] darf es zu jedem Zeitpunkt nur eine &mut-Referenz auf einen Wert geben (Aliasing-XOR-Mutability). Das macht mutable Slices mächtig, aber konfliktanfällig — vor allem, wenn du zwei Indices gleichzeitig modifizieren willst. Dieser Artikel zeigt die wichtigsten Methoden, das zentrale Werkzeug split_at_mut für disjunkte mutable Borrows, und durchläuft die häufigsten Borrow-Checker-Konflikte mit konkreten Lösungen.
Was &mut [T] kann
fn verdoppeln(slice: &mut [i32]) {
for x in slice.iter_mut() {
*x *= 2;
}
}
fn main() {
let mut v = vec![1, 2, 3, 4];
verdoppeln(&mut v);
assert_eq!(v, vec![2, 4, 6, 8]);
}Voraussetzungen:
- Die Original-Bindung im Aufrufer muss
mutsein (let mut v). - Beim Aufruf:
&mut v(mit explizitem&mut). - Solange das
&mut [T]aktiv ist, gibt es keine andere Referenz auf die gleichen Daten.
Die wichtigsten Methoden
iter_mut — pro Element mutieren
let mut v = vec![10, 20, 30];
for x in v.iter_mut() {
*x += 5;
}
assert_eq!(v, vec![15, 25, 35]);iter_mut() gibt einen Iterator über &mut T zurück — jedes Element kann durch *x = ... modifiziert werden.
swap — zwei Indices tauschen
let mut v = [1, 2, 3, 4, 5];
v.swap(0, 4);
assert_eq!(v, [5, 2, 3, 4, 1]);swap(i, j) tauscht die Elemente an den Positionen i und j. Funktioniert auch, wenn i == j (no-op).
sort — In-Place-Sortierung
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6];
v.sort();
assert_eq!(v, vec![1, 1, 2, 3, 4, 5, 6, 9]);
v.sort_by(|a, b| b.cmp(a)); // absteigend
assert_eq!(v, vec![9, 6, 5, 4, 3, 2, 1, 1]);
v.sort_by_key(|x| x.abs()); // nach Custom-Keysort ist stabil und O(n log n). Für nicht-Ord-Typen (z. B. Floats wegen NaN): sort_by mit partial_cmp oder total_cmp.
reverse — In-Place-Umkehren
let mut v = vec![1, 2, 3, 4, 5];
v.reverse();
assert_eq!(v, vec![5, 4, 3, 2, 1]);fill und fill_with — alle Werte ersetzen
let mut buffer = [0u8; 10];
buffer.fill(42);
assert_eq!(buffer, [42u8; 10]);
let mut counter = vec![0; 5];
let mut n = 0;
counter.fill_with(|| { n += 1; n });
assert_eq!(counter, vec![1, 2, 3, 4, 5]);fill setzt alle Elemente auf den gleichen Wert. fill_with ruft eine Closure pro Element auf.
rotate_left / rotate_right
let mut v = [1, 2, 3, 4, 5];
v.rotate_left(2);
assert_eq!(v, [3, 4, 5, 1, 2]);
v.rotate_right(1);
assert_eq!(v, [2, 3, 4, 5, 1]);chunks_mut / windows-Äquivalente
let mut v = vec![1, 2, 3, 4, 5, 6];
for chunk in v.chunks_mut(2) {
for x in chunk {
*x *= 10;
}
}
assert_eq!(v, vec![10, 20, 30, 40, 50, 60]);chunks_mut(n) gibt einen Iterator über &mut [T]-Chunks zurück. Sehr nützlich für Block-weise Verarbeitung.
split_at_mut — das Schlüssel-Werkzeug
Der Borrow Checker erlaubt nur eine &mut-Referenz auf einen Wert. Was, wenn du zwei Indices gleichzeitig modifizieren willst?
fn main() {
let mut v = vec![1, 2, 3];
// let a = &mut v[0];
// let b = &mut v[2]; // Fehler — zweites &mut auf v
// *a += *b;
}Lösung: split_at_mut. Sie teilt den Slice in zwei garantiert disjunkte Bereiche.
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let (links, rechts) = v.split_at_mut(2);
// links = [1, 2], rechts = [3, 4, 5]
// beide sind &mut [i32] — disjunkt, parallel mutierbar.
links[0] = 100;
rechts[0] = 300;
assert_eq!(v, vec![100, 2, 300, 4, 5]);
}Der Compiler weiß zur Bauzeit: links und rechts zeigen auf disjunkte Speicherbereiche. Zwei &mut-Refs auf den gleichen Wert wäre verboten — aber auf verschiedene Werte aus dem gleichen Container ist über split_at_mut legal.
Praxis-Beispiel: zwei Elemente kombinieren
fn tausche_und_addiere(v: &mut [i32], i: usize, j: usize) {
if i == j || i >= v.len() || j >= v.len() { return; }
let (lo, hi) = if i < j { (i, j) } else { (j, i) };
let (links, rechts) = v.split_at_mut(hi);
std::mem::swap(&mut links[lo], &mut rechts[0]);
}
fn main() {
let mut v = vec![10, 20, 30, 40, 50];
tausche_und_addiere(&mut v, 1, 3);
assert_eq!(v, vec![10, 40, 30, 20, 50]);
}Das idiomatische Pattern für „zwei Indices unabhängig modifizieren": Slice splitten, dann beide Hälften unabhängig bearbeiten.
split_first_mut / split_last_mut
Wenn du den ersten/letzten Wert separat brauchst:
fn main() {
let mut v = vec![1, 2, 3, 4];
if let Some((kopf, schwanz)) = v.split_first_mut() {
*kopf = 100;
for x in schwanz {
*x += 10;
}
}
assert_eq!(v, vec![100, 12, 13, 14]);
}kopf ist &mut T, schwanz ist &mut [T] — beide gleichzeitig mutable. Wieder dank disjunkter Bereiche.
get_mut — sicherer mutable Zugriff
Wie get für lesenden Zugriff gibt es get_mut für mutable:
let mut v = vec![10, 20, 30];
if let Some(x) = v.get_mut(1) {
*x = 999;
}
assert_eq!(v[1], 999);
// Out-of-bounds — kein Panic
if let Some(x) = v.get_mut(99) {
*x = 0; // nie ausgeführt
}Bei unsicheren Indices immer get_mut statt [i] — kein Panic-Risiko.
Häufige Borrow-Checker-Konflikte
Iteration während Mutation
fn main() {
let mut v = vec![1, 2, 3];
// for &x in &v { // shared borrow auf v
// if x > 1 { v.push(99); } // Fehler — &mut v + &v
// }
// Lösung: erst Indices sammeln, dann pushen
let zu_pushen: Vec<i32> = v.iter().filter(|&&x| x > 1).copied().collect();
v.extend(zu_pushen);
assert_eq!(v, vec![1, 2, 3, 2, 3]);
}Während for &x in &v läuft, ist v shared geborgt. push braucht &mut v — Konflikt. Lösung: zwei Phasen (Sammeln + Bearbeiten).
Doppel-Index in Methoden-Call
fn main() {
let mut v = vec![1, 2, 3];
// v[0] = v[2]; // Fehler? Nein — funktioniert,
// weil v[2] auf der rechten Seite zuerst gelesen wird (rvalue).
v[0] = v[2];
assert_eq!(v, vec![3, 2, 3]);
// ABER: v.push(v.len() as i32); würde NICHT gehen,
// weil v.len() braucht &v, v.push braucht &mut v.
let len = v.len() as i32;
v.push(len);
}Aufpassen: Method-Calls mit gemischten Borrows in einem Statement sind häufig Konflikt-Quellen. Lösung: Zwischenwerte vorab in lokale Bindungen extrahieren.
Mutation eines Elements während Iteration
fn main() {
let mut v = vec![1, 2, 3];
// for &x in &v {
// v[0] = x; // Fehler
// }
// Lösung — iter_mut() statt iter():
for x in v.iter_mut() {
*x *= 2;
}
assert_eq!(v, vec![2, 4, 6]);
}iter_mut() ist der idiomatische Weg, In-Place zu modifizieren. Der Borrow Checker akzeptiert das, weil iter_mut exklusive &mut T-Refs einzeln nacheinander herausgibt.
Praxis: Mutable Slices im echten Code
Bild-Bearbeitung In-Place
pub fn aufhellen(rgb: &mut [u8], prozent: u8) {
for kanal in rgb.iter_mut() {
let neu = (*kanal as u16 * (100 + prozent as u16) / 100).min(255);
*kanal = neu as u8;
}
}
fn main() {
let mut bild = vec![100u8, 50, 200, 30, 80, 150];
aufhellen(&mut bild, 20);
// Jeder Wert wurde um 20 % erhöht (mit Saturierung)
println!("{bild:?}");
}&mut [u8] für einen RGB-Buffer — funktioniert mit Vec, Array oder Sub-Slice.
Normalisierung mit zwei Durchgängen
pub fn min_max_normalisieren(daten: &mut [f64]) {
if daten.is_empty() { return; }
let min = daten.iter().cloned().fold(f64::INFINITY, f64::min);
let max = daten.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let spanne = max - min;
if spanne == 0.0 { return; }
for x in daten.iter_mut() {
*x = (*x - min) / spanne;
}
}
fn main() {
let mut werte = vec![5.0, 10.0, 0.0, 15.0];
min_max_normalisieren(&mut werte);
assert_eq!(werte, vec![1.0/3.0, 2.0/3.0, 0.0, 1.0]);
}Erst Min/Max berechnen (shared borrow durch iter), dann normalisieren (mutable borrow durch iter_mut). Die zwei Borrows sind sequentiell.
In-Place-Filter mit retain
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
v.retain(|&x| x % 2 == 0);
assert_eq!(v, vec![2, 4, 6]);
}retain ist Vec-spezifisch (nicht auf Slice direkt), aber sehr nützlich — In-Place-Filter mit O(n).
Vector-Add für zwei Slices
pub fn addiere_in_place(ziel: &mut [f64], quelle: &[f64]) {
for (z, q) in ziel.iter_mut().zip(quelle) {
*z += q;
}
}
fn main() {
let mut a = vec![1.0, 2.0, 3.0];
let b = vec![10.0, 20.0, 30.0];
addiere_in_place(&mut a, &b);
assert_eq!(a, vec![11.0, 22.0, 33.0]);
}&mut-Slice plus &-Slice — typisches Pattern für „akkumuliere in ein Ziel".
Crypt-Buffer XOR
pub fn xor_with_key(daten: &mut [u8], key: &[u8]) {
for (i, byte) in daten.iter_mut().enumerate() {
*byte ^= key[i % key.len()];
}
}
fn main() {
let mut nachricht = b"Geheimer Text".to_vec();
let key = b"key";
xor_with_key(&mut nachricht, key);
// Doppeltes XOR ergibt wieder Original
xor_with_key(&mut nachricht, key);
assert_eq!(nachricht, b"Geheimer Text");
}&mut [u8] für In-Place-Verschlüsselung — keine Allocation, kein neues Output.
Sortierung mit Stat-Update
pub fn sortiere_und_messe(v: &mut [i32]) -> (i32, i32) {
v.sort();
(v[0], v[v.len() - 1]) // min, max nach Sort
}
fn main() {
let mut data = vec![3, 1, 4, 1, 5, 9];
let (min, max) = sortiere_und_messe(&mut data);
assert_eq!((min, max), (1, 9));
}Sort mutiert das Slice, danach Lese-Zugriff für Min/Max. Zwei Operationen, einfach kombiniert.
Buffer-Reverse mit Split
pub fn reverse_manuell<T>(s: &mut [T]) {
let len = s.len();
for i in 0..len / 2 {
s.swap(i, len - 1 - i);
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
reverse_manuell(&mut v);
assert_eq!(v, vec![5, 4, 3, 2, 1]);
}swap(i, j) ist die Borrow-Checker-sichere Variante für „zwei Elemente tauschen" — intern nutzt sie mem::swap, das via Pointer arbeitet.
Audio-Sample-Normalisierung
pub fn audio_normalize(samples: &mut [f32]) {
let max_abs = samples.iter()
.map(|s| s.abs())
.fold(0.0_f32, f32::max);
if max_abs == 0.0 { return; }
let faktor = 1.0 / max_abs;
for s in samples.iter_mut() {
*s *= faktor;
}
}Read-then-Write — typisches Audio-DSP-Pattern.
Index-basierte Mutation mit get_mut
pub fn setze_index<T>(slice: &mut [T], i: usize, wert: T) -> bool {
if let Some(slot) = slice.get_mut(i) {
*slot = wert;
true
} else {
false
}
}
fn main() {
let mut v = vec![1, 2, 3];
assert!(setze_index(&mut v, 1, 999));
assert!(!setze_index(&mut v, 99, 0));
assert_eq!(v, vec![1, 999, 3]);
}get_mut für sicheren Index-Zugriff — Panic-frei bei out-of-bounds.
Häufige Stolperfallen
Nur EIN &mut [T] aktiv zur gleichen Zeit.
Aliasing-XOR-Mutability gilt auch für Slices. Wer zwei &mut-Refs auf dieselbe Sammlung braucht: split_at_mut. Disjunkte Bereiche sind erlaubt, gleiche oder überlappende nicht.
iter_mut() hält den Slice exklusiv geborgt.
Während for x in v.iter_mut() läuft, ist v als &mut geborgt. Im Loop-Body kein direkter v[...]-Zugriff möglich. Wer Index braucht: enumerate().
split_at_mut panickt bei zu großem Index.
v.split_at_mut(99) mit v.len() == 3 panickt. Für Safe-Variante: split_at_mut_checked (seit Rust 1.80) — gibt Option<(&mut [T], &mut [T])> zurück.
swap(i, j) ist Borrow-Checker-sicher.
v.swap(0, 1) funktioniert auch dann, wenn zwei &mut v[i] parallel nicht erlaubt wären. Intern nutzt es mem::swap mit Raw-Pointers — eine sichere Stdlib-Abstraktion.
sort auf Floats braucht sort_by.
f64 implementiert nicht Ord (wegen NaN). v.sort() schlägt fehl. Lösung: v.sort_by(|a, b| a.partial_cmp(b).unwrap()) oder seit Rust 1.62 v.sort_by(|a, b| a.total_cmp(b)).
chunks_mut(n) braucht n > 0.
v.chunks_mut(0) panickt. Bei dynamischem n immer vorher prüfen.
fill erfordert Clone auf T.
Bei Copy-Typen kein Problem. Bei String/Vec ohne Copy: fill_with(|| String::new()) mit einer Closure, die jedes Mal einen neuen Wert produziert.
&mut [T] ist NICHT Copy.
Anders als &[T], das Copy ist, ist &mut [T] exklusiv. Du kannst eine mutable Slice-Referenz nicht doppelt in einer Funktion verwenden, ohne Reborrowing.
Weiterführende Ressourcen
Externe Quellen
- std::primitive.slice – split_at_mut
- std::primitive.slice – iter_mut
- std::primitive.slice – sort
- std::primitive.slice – swap
- std::mem::swap