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

Rust In-Place-Mutation
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 mut sein (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

Rust iter_mut
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

Rust swap
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

Rust sort
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-Key

sort 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

Rust reverse
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

Rust fill
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

Rust rotate
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

Rust chunks_mut
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?

Rust Doppel-mut — verboten
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.

Rust split_at_mut
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

Rust Swap-via-split
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:

Rust split_first_mut
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:

Rust get_mut
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

Rust Iter + push
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

Rust Index-Konflikt
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

Rust iter_mut statt iter
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

Rust Pixel-Helligkeit
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

Rust Min-Max-Normalize
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

Rust retain (auf Vec)
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

Rust In-Place-Add
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

Rust XOR-Cipher
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

Rust Sort + Tracking
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

Rust Manueller Reverse
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

Rust Audio-Helper
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

Rust Safe Index
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

/ Weiter

Zurück zu Slices & Views

Zur Übersicht