Skip to content

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:

  1. to_mut() returns &mut <B as ToOwned>::Owned — a &mut String for a Cow<str>, which is why you can push_str through it. Explanations that say &mut B or &mut str describe a method you could not push into. Step 7 runs it twice.
  2. Methods reached through Deref never touch the Cow. replace and to_uppercase are str's methods; they borrow the text and return a new String, and the Cow stays Borrowed. What changes the variant is a call on the Cow itself: to_mut(), or an operator it implements, such as the += in claim 6. into_owned() does not change it — it consumes the Cow and hands back the String.
  3. as_ref() is AsRef<str>, and returns a &str, the same thing &*cow gives you.
  4. Owned means owned, not cloned. A String handed to .into() becomes Owned without a copy; a &'static str becomes Borrowed. That pair is how the usual error-message function returns "out of memory".into() from most arms and format!(…).into() from the one that needs data.
  5. B may be sized, and a Cow<i32> compiles. As for size: Cow<str> is exactly a String — the tag is free — but it is a word more than the &str it replaces, and a Cow<i32> is twice an &i32. "Possible size increase" is fair against a reference, never against the owned type.
  6. Cow<'a, str> implements Add<&str> and AddAssign<&str>, and += on a Borrowed one that already holds text allocates: the result has to own both halves, so the Cow becomes Owned.
  7. The capitalize that circulates allocates for text that is already capitalized, and never capitalizes a non-ASCII letter. It slices byte 0..1, which is not a character boundary in "élan", so get returns None and the text comes back untouched. Asking chars().next() whether the first character is lowercase fixes both, and allocates only for "ada" and "élan".
  8. Rc::make_mut and Arc::make_mut are clone-on-write too, as the Cow docs 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

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?