Cow<'a, T> (Clone on Write) ist ein Wrapper-Enum mit zwei Varianten: Borrowed(&'a T) oder Owned(T::Owned). Du kannst eine Cow lesen, ohne zu wissen, ob sie gerade auf geliehene oder eigene Daten zeigt — beide verhalten sich nach außen gleich. Sobald du mutieren willst, sorgt Cow automatisch dafür, dass eine Owned-Variante existiert: war es vorher Borrowed, wird der Wert geklont; war es Owned, wird direkt mutiert. Der Effekt: kein unnötiges Allozieren. Funktionen, die in den meisten Fällen den Input unverändert lassen, geben den Input einfach geliehen zurück; nur in den Mutations-Fällen entsteht eine Allocation. Das ist eines der nützlichsten Performance-Patterns der Stdlib.

Was Cow ist

Cow<'a, T> lebt im Modul std::borrow. Die Definition (vereinfacht):

Rust Cow-Definition (vereinfacht)
// Vereinfachte Darstellung der Stdlib-Definition
// pub enum Cow<'a, B: ?Sized + ToOwned + 'a> {
//     Borrowed(&'a B),
//     Owned(<B as ToOwned>::Owned),
// }

Die zwei Varianten:

  • Borrowed(&'a B) — eine Referenz auf einen bestehenden Wert.
  • Owned(B::Owned) — der eigentliche Wert (etwa String als Owned-Form von str).

Cow implementiert Deref<Target = B> — du kannst eine Cow wie eine Referenz auf den inneren Wert verwenden, unabhängig von der Variante.

Die häufigsten Cow-Typen sind:

  • Cow<'a, str> — entweder &str (Borrowed) oder String (Owned).
  • Cow<'a, [T]> — entweder &[T] (Borrowed) oder Vec<T> (Owned).
  • Cow<'a, Path> — entweder &Path oder PathBuf.

Erste Beispiele

Rust Cow Basics
use std::borrow::Cow;

fn main() {
    // Borrowed-Variante: zeigt auf einen bestehenden &str
    let borrowed: Cow<str> = Cow::Borrowed("hallo");
    println!("{borrowed}");        // "hallo"

    // Owned-Variante: hält einen eigenen String
    let owned: Cow<str> = Cow::Owned(String::from("welt"));
    println!("{owned}");           // "welt"

    // Beide werden gleich behandelt — Deref zu &str
    for s in &[borrowed, owned] {
        println!("len = {}", s.len());
    }
}

Beide Varianten verhalten sich nach außen wie ein &str — du kannst Methoden wie len(), chars(), split() aufrufen, ohne dich um die Variante zu kümmern.

Das eigentliche Pattern — Funktionen, die manchmal mutieren

Der zentrale Use-Case: eine Funktion, die ihren Input meistens unverändert lässt, gelegentlich aber mutieren muss.

Rust Klassisches Cow-Pattern
use std::borrow::Cow;

fn strip_leading_spaces(s: &str) -> Cow<str> {
    if s.starts_with(' ') {
        Cow::Owned(s.trim_start().to_string())     // alloziert
    } else {
        Cow::Borrowed(s)                           // gibt den Input direkt zurück
    }
}

fn main() {
    let a = "Hello";                  // kein führendes Space
    let b = "   Hello";               // führendes Space

    let r1 = strip_leading_spaces(a);     // Borrowed (keine Allocation!)
    let r2 = strip_leading_spaces(b);     // Owned (Allocation nötig)

    println!("{r1}, {r2}");
}

Was hier passiert:

  • Im 99%-Fall (kein führendes Space) gibt die Funktion eine Cow::Borrowed-Variante zurück, die auf den Input zeigt. Keine Allocation.
  • Im seltenen Fall (führendes Space muss entfernt werden) gibt sie eine Cow::Owned zurück. Allocation passiert hier, weil sie wirklich nötig ist.

Vergleich zur naiven Variante:

Rust Ohne Cow — immer Allocation
fn strip_leading_spaces_naive(s: &str) -> String {
    s.trim_start().to_string()    // immer Allocation, auch wenn nichts zu tun ist
}

Die naive Variante alloziert immer. Bei einem hot path mit Millionen Aufrufen, von denen 95% nichts zu tun haben, ist das ein massiver Unterschied.

to_mut — Mutation On-Demand

Wer eine Cow hat und sie mutieren will, ruft cow.to_mut() auf. Das ist eine clevere Methode:

  • Falls die Cow gerade Borrowed ist: klont den Wert und macht sie zu Owned. Gibt eine &mut B::Owned-Referenz zurück.
  • Falls die Cow gerade Owned ist: gibt direkt die &mut-Referenz auf den Owned-Wert.
Rust to_mut
use std::borrow::Cow;

fn main() {
    let mut c: Cow<str> = Cow::Borrowed("hello");

    // Mutation gewünscht → to_mut klont (Borrowed → Owned)
    c.to_mut().push_str(" world");
    println!("{c}");      // "hello world"

    // Nochmal mutieren — c ist jetzt Owned, kein zweiter Klon nötig
    c.to_mut().push_str("!");
    println!("{c}");      // "hello world!"
}

Das ist genau das „Copy on Write"-Verhalten: solange du nur liest, ist alles geliehen; sobald du schreibst, wird kopiert.

into_owned — endgültig Owned machen

Manchmal willst du die Owned-Variante haben, egal welche Variante gerade aktiv ist. Etwa wenn du das Ergebnis aus einer Funktion zurückgibst und der Caller den Wert behalten will, ohne von der ursprünglichen Borrow-Lifetime abhängig zu sein.

Rust into_owned
use std::borrow::Cow;

fn main() {
    let cow: Cow<str> = Cow::Borrowed("hello");
    let owned: String = cow.into_owned();
    println!("{owned}");
    // owned ist ein voller String, lebt unabhängig.
}

Bei Cow::Borrowed klont into_owned() den Wert. Bei Cow::Owned gibt es den Wert ohne Klonen heraus. So oder so ist das Ergebnis ein owned String/Vec/PathBuf etc.

Cow mit Bytes und Pfaden

Das Pattern funktioniert nicht nur mit Strings:

Rust Cow für Bytes
use std::borrow::Cow;

fn strip_nulls(data: &[u8]) -> Cow<[u8]> {
    if data.contains(&0) {
        Cow::Owned(data.iter().copied().filter(|&b| b != 0).collect())
    } else {
        Cow::Borrowed(data)
    }
}

fn main() {
    let a = b"hello world";
    let b = b"hello\0world";

    let r1 = strip_nulls(a);          // Borrowed
    let r2 = strip_nulls(b);          // Owned (Nullen wurden entfernt)
    println!("a -> {} bytes", r1.len());
    println!("b -> {} bytes", r2.len());
}
Rust Cow für Path
use std::borrow::Cow;
use std::path::{Path, PathBuf};

fn normalize(p: &Path) -> Cow<Path> {
    let path_str = p.to_string_lossy();
    if path_str.contains("//") {
        let cleaned = path_str.replace("//", "/");
        Cow::Owned(PathBuf::from(cleaned))
    } else {
        Cow::Borrowed(p)
    }
}

fn main() {
    let a = Path::new("/foo/bar");
    let b = Path::new("/foo//bar");

    let r1 = normalize(a);        // Borrowed
    let r2 = normalize(b);        // Owned (// wurde zu / korrigiert)
    println!("{}", r1.display());
    println!("{}", r2.display());
}

Lifetime-Implikationen

Cow trägt eine Lifetime: Cow<'a, T>. Bei Cow::Borrowed(&'a T) ist die Lifetime an die geliehene Referenz gebunden. Bei Cow::Owned(...) ist sie unabhängig vom Borrow — der Owned-Wert lebt für sich.

In der Praxis heißt das: wenn deine Funktion einen Cow zurückgibt, dessen Lifetime an den Input gebunden ist, kann der Caller den Wert nur so lange nutzen, wie der Input lebt:

Rust Lifetime-Verbindung
use std::borrow::Cow;

fn trim<'a>(s: &'a str) -> Cow<'a, str> {
    if s.starts_with(' ') {
        Cow::Owned(s.trim().to_string())
    } else {
        Cow::Borrowed(s)
    }
}

fn main() {
    let result;
    {
        let input = String::from("Hello");
        result = trim(&input);
        println!("{result}");         // OK: input lebt noch
    }
    // input ist hier dropped — result darf nicht mehr genutzt werden,
    // falls es eine Borrowed-Variante war.
    // println!("{result}");          // FEHLER (im Borrowed-Fall)
}

Wenn du den Wert über die Input-Lifetime hinaus brauchst, ruf into_owned():

Rust Mit into_owned
# use std::borrow::Cow;
# fn trim<'a>(s: &'a str) -> Cow<'a, str> { Cow::Borrowed(s) }
fn main() {
    let result;
    {
        let input = String::from("Hello");
        result = trim(&input).into_owned();   // jetzt String, eigene Lifetime
    }
    println!("{result}");                       // OK: result ist owned
}

into_owned() macht aus der Cow einen vollen owned Wert, der unabhängig vom Input lebt. Damit ist die Lifetime-Bindung aufgelöst.

Cow in der Stdlib

Mehrere Stdlib-Funktionen geben Cow zurück — wo die Eingabe meist unverändert weitergegeben werden kann:

  • str::to_lowercase() — gibt String zurück, immer alloziert. Aber: Path::to_string_lossy() gibt Cow<str> zurück. Wenn der Pfad valides UTF-8 ist (häufig), gibt es ihn direkt geliehen; nur bei Invalid-UTF-8 wird konvertiert.
  • String::from_utf8_lossy — gibt Cow<str> zurück. Bei gültigem UTF-8: Borrowed. Bei ungültigem: Owned (mit Ersatz-Zeichen).
  • Regex::replace (in der regex-Crate) — Cow als Rückgabe.

Schau bei Stdlib-APIs, die Konvertierungen / Bereinigungen anbieten: oft ist Cow dort die Rückgabe, weil "manchmal Allocation nötig" das natürliche Pattern ist.

Wann Cow nutzen, wann nicht

Cow lohnt sich, wenn:

  • Die Funktion die meiste Zeit den Input unverändert zurückgeben kann.
  • Eine Allocation teuer wäre (häufiger Aufruf, große Daten).
  • Du eine klare API für „lese-only oder mutiert" zeigen willst.

Cow lohnt sich nicht, wenn:

  • Die Funktion immer mutieren muss. Dann ist -> String ehrlicher.
  • Die Funktion nie mutieren muss. Dann ist -> &str direkter.
  • Die Performance-Frage irrelevant ist (selten aufgerufen, kleine Daten). Cow fügt Komplexität hinzu, die nur lohnt, wenn der Performance-Gewinn da ist.

Cow ist auch nicht das Allheilmittel — manchmal ist eine direkte String-Rückgabe sauberer, manchmal ein eigener Wrapper-Typ besser. Aber in den passenden Fällen ist Cow eine der eleganten Lösungen, die Rust ausmacht.

Praxis: Cow im echten Code

HTML-Escape-Funktion

Rust HTML-Escape
use std::borrow::Cow;

fn html_escape(s: &str) -> Cow<str> {
    // Nur wenn es etwas zu escapen gibt, allozieren
    if s.contains('<') || s.contains('>') || s.contains('&') {
        let mut out = String::with_capacity(s.len());
        for c in s.chars() {
            match c {
                '<' => out.push_str("&lt;"),
                '>' => out.push_str("&gt;"),
                '&' => out.push_str("&amp;"),
                _ => out.push(c),
            }
        }
        Cow::Owned(out)
    } else {
        Cow::Borrowed(s)
    }
}

fn main() {
    let a = "Hello World";
    let b = "<script>alert(1)</script>";

    println!("{}", html_escape(a));        // "Hello World" (Borrowed)
    println!("{}", html_escape(b));        // "&lt;script&gt;alert(1)&lt;/script&gt;" (Owned)
}

Klassisches Web-Pattern: meiste Strings haben keine HTML-Spezialzeichen, also keine Allocation. Nur bei den wenigen, die welche enthalten, wird kopiert.

Path-Normalisierung

Rust Path
use std::borrow::Cow;

fn collapse_slashes(s: &str) -> Cow<str> {
    if s.contains("//") {
        let mut out = String::with_capacity(s.len());
        let mut prev_slash = false;
        for c in s.chars() {
            if c == '/' {
                if !prev_slash { out.push(c); }
                prev_slash = true;
            } else {
                out.push(c);
                prev_slash = false;
            }
        }
        Cow::Owned(out)
    } else {
        Cow::Borrowed(s)
    }
}

fn main() {
    let a = "/foo/bar/baz";
    let b = "/foo//bar///baz";

    println!("{}", collapse_slashes(a));   // Borrowed
    println!("{}", collapse_slashes(b));   // Owned
}

Path-Normalisierung ist ein klassisches Cow-Pattern. Die meisten Pfade sind sauber; die wenigen mit Doppel-Slashes werden bereinigt.

Lokalisierung mit Defaults

Rust i18n
use std::borrow::Cow;
use std::collections::HashMap;

struct Translator {
    language: String,
    translations: HashMap<String, String>,
}

impl Translator {
    fn new(language: impl Into<String>) -> Self {
        Translator {
            language: language.into(),
            translations: HashMap::new(),
        }
    }

    fn add(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.translations.insert(key.into(), value.into());
    }

    // Liefert Übersetzung oder Original-Key
    fn t<'a>(&'a self, key: &'a str) -> Cow<'a, str> {
        match self.translations.get(key) {
            Some(v) => Cow::Borrowed(v.as_str()),
            None => Cow::Borrowed(key),     // Fallback: Key selbst zurückgeben
        }
    }
}

fn main() {
    let mut tr = Translator::new("de");
    tr.add("hello", "Hallo");
    tr.add("world", "Welt");

    // Beide Cow::Borrowed (geliehen aus den HashMap-Strings oder dem Key)
    println!("{}", tr.t("hello"));     // "Hallo"
    println!("{}", tr.t("missing"));   // "missing" (Fallback)
}

I18n-Service mit Fallback: gibt entweder die Übersetzung oder den Original-Key zurück. Beides als Cow::Borrowed — keine Allocations.

CSV-Zellen-Bereinigung

Rust CSV
use std::borrow::Cow;

fn csv_quote(s: &str) -> Cow<str> {
    if s.contains(',') || s.contains('"') || s.contains('\n') {
        let escaped = s.replace('"', "\"\"");
        Cow::Owned(format!("\"{escaped}\""))
    } else {
        Cow::Borrowed(s)
    }
}

fn main() {
    let cells = ["Alice", "30", "Berlin, DE", "Schul\"e"];
    let csv_line: Vec<String> = cells.iter()
        .map(|c| csv_quote(c).into_owned())
        .collect();
    println!("{}", csv_line.join(","));
    // Alice,30,"Berlin, DE","Schul""e"
}

CSV-Quoting: nur quoten, wenn nötig (Komma, Anführungszeichen, Newline). Die meisten Zellen sind harmlos.

Konvertierungs-Pipeline mit selektiver Transformation

Rust Pipeline
use std::borrow::Cow;

fn normalize(s: &str, lowercase: bool, trim: bool) -> Cow<str> {
    let need_trim = trim && (s.starts_with(' ') || s.ends_with(' '));
    let need_lower = lowercase && s.chars().any(|c| c.is_uppercase());

    if !need_trim && !need_lower {
        Cow::Borrowed(s)
    } else {
        let mut t = s;
        let trimmed = if need_trim { t = t.trim(); t.to_string() } else { t.to_string() };
        let result = if need_lower { trimmed.to_lowercase() } else { trimmed };
        Cow::Owned(result)
    }
}

fn main() {
    assert!(matches!(normalize("hello", true, true), Cow::Borrowed(_)));
    assert!(matches!(normalize("  Hello  ", true, true), Cow::Owned(_)));
}

Konfigurierbare String-Normalisierung. Bei optimalen Inputs (schon klein, schon trimmed) Allocation komplett vermieden.

Lazy-Konvertierung in Stdlib-Manier

Rust String::from_utf8_lossy
use std::borrow::Cow;

fn main() {
    // Stdlib-Beispiel: from_utf8_lossy gibt Cow zurück
    let bytes_ok = b"Hello".to_vec();
    let bytes_broken = vec![0xFF, 0x48, 0x69];

    let s1: Cow<str> = String::from_utf8_lossy(&bytes_ok);
    let s2: Cow<str> = String::from_utf8_lossy(&bytes_broken);

    println!("{s1}");       // "Hello" (Borrowed — gültiges UTF-8)
    println!("{s2}");       // "�Hi" (Owned — Ersatz-Zeichen wurden eingefügt)

    // Beide verhalten sich nach außen wie &str
    assert_eq!(s1.len(), 5);
    assert!(s2.contains("Hi"));
}

Stdlib-Showcase. from_utf8_lossy ist genau das Cow-Pattern: bei korrekten Bytes zero-allocation, bei beschädigten Bytes Owned mit Ersatz-Zeichen.

Besonderheiten

Cow = Clone on Write zwischen Borrowed und Owned.

Zwei Varianten: Borrowed(&'a T) oder Owned(T::Owned). Verhalten nach außen identisch (via Deref); intern wird bei Bedarf konvertiert. Klassisch für Funktionen, die meist nichts tun müssen, manchmal aber mutieren.

Häufigste Typen: Cow, Cow<[T]>, Cow.

Für Strings, Slices, Pfade. Owned-Form ist jeweils String, Vec<T>, PathBuf. Cow trägt die 'a-Lifetime — bei Borrowed gebunden an den Borrow, bei Owned unabhängig.

to_mut() macht Mutation möglich.

Wenn Borrowed: kloned vor dem Schreiben (→ Owned). Wenn schon Owned: direkt mutieren. Genau das „Copy on Write"-Verhalten.

into_owned() löst die Lifetime auf.

Macht aus Cow einen vollen owned Wert (String, Vec, ...). Wichtig, wenn du den Wert über die Input-Lifetime hinaus brauchst.

Klassisches Use-Case: 95% nichts tun, 5% mutieren.

HTML-Escape, Path-Normalize, String-Sanitize. Wenn die meisten Inputs unverändert durchgehen, ist Cow die Performance-Optimierung gegenüber „immer allozieren".

Stdlib-Beispiele: String::from_utf8_lossy, Path::to_string_lossy.

Beide nutzen Cow für „meistens Borrowed, im Edge-Case Owned". Schau bei Stdlib-Konvertierungen — wenn da Cow zurückgegeben wird, ist das das Pattern dahinter.

Nicht überall sinnvoll.

Wenn die Funktion immer mutieren muss, ist -> String ehrlicher. Wenn sie nie mutieren muss, ist -> &str direkter. Cow lohnt sich, wenn beide Welten möglich sind und Performance zählt.

Cow ist kein Smart Pointer im klassischen Sinn.

Es alloziert nichts auf dem Heap (außer in der Owned-Variante über String/Vec). Es ist eher ein Enum mit zwei Varianten plus Deref-Magie. Aber das Pattern „verhalten wie eine Referenz" qualifiziert es als Smart Pointer.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Smart Pointers

Zur Übersicht