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):
// 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 (etwaStringals Owned-Form vonstr).
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) oderString(Owned).Cow<'a, [T]>— entweder&[T](Borrowed) oderVec<T>(Owned).Cow<'a, Path>— entweder&PathoderPathBuf.
Erste Beispiele
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.
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::Ownedzurück. Allocation passiert hier, weil sie wirklich nötig ist.
Vergleich zur naiven Variante:
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
Borrowedist: klont den Wert und macht sie zuOwned. Gibt eine&mut B::Owned-Referenz zurück. - Falls die Cow gerade
Ownedist: gibt direkt die&mut-Referenz auf den Owned-Wert.
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.
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:
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());
}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:
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():
# 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()— gibtStringzurück, immer alloziert. Aber:Path::to_string_lossy()gibtCow<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— gibtCow<str>zurück. Bei gültigem UTF-8: Borrowed. Bei ungültigem: Owned (mit Ersatz-Zeichen).Regex::replace(in derregex-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
-> Stringehrlicher. - Die Funktion nie mutieren muss. Dann ist
-> &strdirekter. - 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
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("<"),
'>' => out.push_str(">"),
'&' => out.push_str("&"),
_ => 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)); // "<script>alert(1)</script>" (Owned)
}Klassisches Web-Pattern: meiste Strings haben keine HTML-Spezialzeichen, also keine Allocation. Nur bei den wenigen, die welche enthalten, wird kopiert.
Path-Normalisierung
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
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
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
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
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
- std::borrow::Cow
- std::borrow::ToOwned
- The Rust Book – kein dediziertes Cow-Kapitel, aber: ch15.06
- Rust By Example – Cow