What Cow explanations get wrong, run¶
How to learn ToOwned › Beside the steps · read after: Step 7 — Borrow is the way back
Level: 201 · a companion to the path
One line: Most explanations of Cow get the enum right and a method, a signature or a cost wrong. Ten claims that turn up again and again — in articles, book chapters, chat answers and diagrams — each checked against rustc 1.98.0, eight by a program and two by the compiler's refusal.
The type, before the claims¶
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
Everything below follows from four facts in those six lines. Borrowed holds a reference to B, not a B. Owned holds B's owned twin — String for B = str, from step 1 — not a B. ?Sized allows an unsized B, it does not require one (step 2). And nothing in it is a trait object: reading a Cow is a match, not a virtual call.
Two claims the compiler refuses¶
"Cow has into_owned and into_borrowed." There is no into_borrowed. rustc 1.98.0: "no method named into_borrowed found for enum Cow<'_, str> in the current scope" (E0599). Nor is there a stable is_borrowed method — cow.is_borrowed() is E0599 too — so on stable you ask with matches!(cow, Cow::Borrowed(_)). The one conversion out of a Cow is into_owned.
"?Sized means trait objects can go in a Cow." Cow<'_, dyn Display> is E0277: "the trait bound dyn std::fmt::Display: ToOwned is not satisfied". A trait object has no owned twin — Clone cannot be called through dyn — so the blanket impl from step 6 does not reach it. The ?Sized is there for str, [T], Path, OsStr and CStr.
Eight claims, run¶
Verified output of cow_claims_checked.rs — regenerated by tools/run_examples.py, never hand-typed.
1. "to_mut() gives you a &mut str"
to_mut() returns &mut alloc::string::String: the Owned type, not B
2. "calling replace or to_uppercase on a borrowed Cow clones it into Owned"
replace returned alloc::string::String, and the Cow is still Borrowed
3. "as_ref() returns a Cow"
as_ref() returns &str
4. "Owned means the data was cloned"
String.into() -> Owned, nothing cloned; &'static str.into() -> Borrowed
5. "?Sized means B has to be unsized" / "Cow has a size cost"
Cow<i32> compiles: Borrowed 42
in words: Cow<str> 3, String 3, &str 2; Cow<i32> 2, &i32 1
6. "+= on a Cow<str> appends without allocating"
before: Borrowed, after +=: Owned "jimb, Esq."
7. "a capitalize that returns Cow only allocates when it has to"
ada common: Owned "Ada" fixed: Owned "Ada"
Ada common: Owned "Ada" fixed: Borrowed "Ada"
élan common: Borrowed "élan" fixed: Owned "Élan"
common: Borrowed "" fixed: Borrowed ""
8. Rc::make_mut is clone-on-write too: it copies only when the value is shared
shared: now separate allocations: true, "roster!" and "roster"
alone: same allocation, nothing copied: true
Reading them in order:
to_mut()returns&mut <B as ToOwned>::Owned— a&mut Stringfor aCow<str>, which is why you canpush_strthrough it. Explanations that say&mut Bor&mut strdescribe a method you could not push into. Step 7 runs it twice.- Methods reached through
Derefnever touch theCow.replaceandto_uppercasearestr's methods; they borrow the text and return a newString, and theCowstaysBorrowed. What changes the variant is a call on theCowitself:to_mut(), or an operator it implements, such as the+=in claim 6.into_owned()does not change it — it consumes theCowand hands back theString. as_ref()isAsRef<str>, and returns a&str, the same thing&*cowgives you.Ownedmeans owned, not cloned. AStringhanded to.into()becomesOwnedwithout a copy; a&'static strbecomesBorrowed. That pair is how the usual error-message function returns"out of memory".into()from most arms andformat!(…).into()from the one that needs data.Bmay be sized, and aCow<i32>compiles. As for size:Cow<str>is exactly aString— the tag is free — but it is a word more than the&strit replaces, and aCow<i32>is twice an&i32. "Possible size increase" is fair against a reference, never against the owned type.Cow<'a, str>implementsAdd<&str>andAddAssign<&str>, and+=on aBorrowedone that already holds text allocates: the result has to own both halves, so theCowbecomesOwned.- The
capitalizethat circulates allocates for text that is already capitalized, and never capitalizes a non-ASCII letter. It slices byte0..1, which is not a character boundary in"élan", sogetreturnsNoneand the text comes back untouched. Askingchars().next()whether the first character is lowercase fixes both, and allocates only for"ada"and"élan". Rc::make_mutandArc::make_mutare clone-on-write too, as theCowdocs point out: they clone only when the value is shared, and write in place when the count is one.
One warning to act on¶
Almost every example online writes fn f(input: &str) -> Cow<str>. On rustc 1.98.0 that compiles with a warning, "hiding a lifetime that's elided elsewhere is confusing". The return type borrows from input, and the signature should say so: -> Cow<'_, str>. Every example in this library does.
Where this leaves you¶
- The
Cowpage itself:Cow: borrow until somebody writes — you do not have to match on it,to_mut()is the write, when not to reach for it. - The compiler errors around
Cow— a missing lifetime, a borrowed local, a'staticcache,to_mutwithoutmut: EveryToOwnederror, and its fix. - The traps with
.to_owned()on aCow: step 8. - A std function that hands you a
Cow:String::from_utf8_lossy. - Why
"élan"broke claim 7: Walking a string and Four lengths. - The elided lifetime in the warning: Lifetime annotations.
- Articles and chapters on
Cow, with what each gets right: Helpful resources —Cow, specifically. - A
to_ownedthat changes the value, and whatto_mut()then does to aCow: ThreeToOwnedkatas, run. - The same kind of page for smart pointers in general: Smart pointer claims, run.
Docs: Cow ↗ · Cow::to_mut ↗ · Rc::make_mut ↗
Po polsku¶
Cow łatwo opisać prawie dobrze. Z definicji wynikają cztery fakty: Borrowed trzyma referencję do B, Owned trzyma posiadanego bliźniaka B (dla str — String), ?Sized pozwala na typ bez rozmiaru, ale go nie wymaga, a w całym typie nie ma żadnego obiektu cechy — odczyt to zwykłe match.
Sprawdzone na rustc 1.98.0: metody into_borrowed nie ma (E0599); Cow<dyn Display> się nie kompiluje (E0277); to_mut() zwraca &mut String, nie &mut str; replace czy to_uppercase wołane przez Deref nie zmieniają wariantu; as_ref() daje &str; Owned nie znaczy „sklonowane”; += na Borrowed alokuje. Popularna funkcja capitalize alokuje nawet dla „Ada” i nigdy nie zmieni „élan”, bo tnie po bajcie, nie po znaku. I drobiazg: sygnatura -> Cow<str> daje ostrzeżenie — pisz -> Cow<'_, str>.
Szukaj po polsku: rust Cow to_mut returns · rust Cow into_owned · rust mismatched_lifetime_syntaxes · kopiowanie przy zapisie
How to learn ToOwned › Beside the steps · back to Step 7 · After the steps — Clone, ToOwned or From?