Step 7: Borrow is the way back¶
How to learn ToOwned › Step 7 of 10 · back: Step 6 — One blanket impl covers every Clone type · next: Step 8 — The traps are steps 5 and 6 together
Level: 201 · a step on a learning path
One line: type Owned: Borrow<Self> promises that an owned value can lend out its borrowed form — String: Borrow<str>. That is what lets HashMap<String, _>::get take a &str, and what lets a Cow hand you a &str from either arm.
The bound in the trait¶
to_owned goes out: &str → String. Borrow comes home: &String → &str. The bound makes the round trip part of the contract, so an impl whose owned type cannot lend out the borrowed one does not compile — E0277, "the trait bound TinyBuf: Borrow<Tiny> is not satisfied" (rustc 1.98.0). You meet that in step 10.
What get asks for¶
With K = String, Q may be str, because String: Borrow<str> — so seats.get("Ada") works without building a String to search with. Borrow also carries a promise the compiler cannot check: the borrowed form must hash and compare exactly as the owned one does. The Borrow page breaks it on purpose.
Cow is built on the pair¶
pub enum Cow<'a, B: ?Sized + 'a>
where
B: ToOwned,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
- Reading goes through
Derefand allocates nothing:Borrowedhands out its&'a B, andOwnedcalls.borrow()on the owned value — the promise above, in use. - std hands you one when it may have to repair text.
CStr::to_string_lossyandString::from_utf8_lossyreturnBorrowedwhen the bytes were already valid UTF-8, andOwnedonly when they had to putU+FFFDin place of bad ones — the checkpoint runs both. - Writing is
to_mut(). On aBorrowedit callsto_owned(), stores the result, and switches toOwned; on anOwnedit copies nothing. The checkpoint runs both. - The lifetime stays. A
Cow<'a, str>is still tied to'aafter it becomesOwned, because the type does not change when the variant does.into_owned()is how you leave it: it returns theStringitself, with no lifetime at all. ToOwneddoes not promise a heap.Ownedis aStringforB = str, but for a sizedClonetype it is the type itself. The heap comes fromString, not from the trait.- Nor does a
Cowhave a separate tag field.Cow<str>is three words, the size of aString: the tag is free.
Why Borrow, and not AsRef¶
Mechanically, a Cow only needs some way to turn a &String into a &str, and AsRef<str> would do that. What rules AsRef out is the blanket impl from step 6. It sets type Owned = T for every Clone type, so the bound has to hold for T: Borrow<T> — every type lending itself. Borrow has exactly that impl, impl<T: ?Sized> Borrow<T> for T. AsRef does not, and its docs say why: a reflexive impl would overlap with the one that lets AsRef see through references. So i32: AsRef<i32> is E0277 (rustc 1.98.0), and with type Owned: AsRef<Self> the blanket impl could not exist.
The Hash/Eq/Ord agreement that Borrow documents is a different matter. The compiler checks none of it, and ToOwned and Cow work without it. HashMap and BTreeMap are what depend on it, which is where the Borrow page breaks it on purpose.
Checkpoint¶
Predict before you open the answer. Does seats.get("Ada") compile on a HashMap<String, u32>, and what does it return? Then: after to_mut() on a Cow::Borrowed("Hello World"), is the buffer new — and does a second to_mut() copy again?
The answer
Verified output of borrow_the_way_back.rs — regenerated by tools/run_examples.py, never hand-typed.
Checkpoint. Does seats.get("Ada") compile on a HashMap<String, u32>?
yes, because String: Borrow<str> -> Some(3)
There and back: to_owned goes out, borrow comes home
"Ada" -> String -> &str "Ada"
[3, 1] -> Vec -> &[i32] [3, 1]
notes.txt -> PathBuf -> &Path "notes.txt"
A Cow<str> lends a &str from either arm
Cow::Borrowed -> "ballot", Cow::Owned -> "ballot"
std hands you a Cow when it may have to repair the text
CStr::to_string_lossy("thanksfish") -> Cow::Borrowed, "thanksfish"
CStr::to_string_lossy(b"caf\xe9") -> Cow::Owned, "caf�"
String::from_utf8_lossy(valid UTF-8) -> Cow::Borrowed, "café"
The write: to_mut calls to_owned only while the Cow is still Borrowed
before to_mut: Cow::Borrowed, points at the literal: true
after to_mut: Cow::Owned, new buffer: true, capacity == length: true
to_mut again: Cow::Owned, same buffer, nothing copied: true
push('!'): no spare capacity, so the String had to reallocate: true
the literal is untouched: "Hello World", the Cow reads "Hello World!"
The push('!') line is the one a diagram usually gets wrong, and it is not a second clone. Clone-on-write clones at most once: the first to_mut() on a Borrowed Cow, and the second to_mut() copied nothing. Growing is a different cost with a different cause. A String fresh from to_owned has no spare capacity, so the first push must reallocate — which may move the bytes — exactly as it would for any String, Cow or no Cow.
What this step sets up¶
- Step 8: the traps —
.to_owned()on thisCowis notinto_owned(), and on anRcit copies no text. - Step 9:
clone_into(&self, target: &mut Self::Owned)writes into the owned half instead of building a new one. - Step 10: implementing
ToOwnedfor your own type means implementingBorrowfor its owned twin, and the reason a view struct cannot do it.
Go deeper¶
Read: Borrow: look up an owned key with a borrowed one — what get asks for, why ToOwned needs it — then Cow: borrow until somebody writes, to_mut() is the write and when not to reach for it.
Around it in the library:
- Reading the
ToOwneddocs — 4.type Owned: Borrow<Self> - The
HashMap, the collectionBorrowwas designed around String::from_utf8_lossy, std's own function that returns aCow<str>Borrow— the trap:.borrow()on anRc<RefCell<T>>, the same method name on a different trait- Lifetime annotations — on a struct it becomes part of the type, why
Cow<'a, _>keeps its'a - Stack and heap, for where a
Borrowedliteral and anOwnedbuffer actually live - What
Cowexplanations get wrong, run —into_borrowed,&mut str,Cow<dyn Display>and seven more claims, checked
Docs: Borrow ↗, whose description is the HashMap case in full · HashMap::get ↗ · Cow ↗ · std::borrow ↗, the module all three live in
Words: Borrow<Q>, Borrow<str> and Cow<'a, B> in the glossary.
Katas: One lookup for every kind of key — three maps searched by their borrowed forms, zero allocations counted. Then pay only when you have to — a Cow that copies only the rows it changes.
Po polsku¶
type Owned: Borrow<Self> to obietnica drogi powrotnej: to_owned robi z &str String, a Borrow pożycza z String-a z powrotem &str. Bez tej implementacji własne impl ToOwned się nie skompiluje (E0277). Dzięki niej HashMap<String, _>::get przyjmuje &str.
Cow jest zbudowany na tej parze. Odczyt idzie przez Deref i nic nie alokuje: Borrowed oddaje referencję, Owned woła .borrow(). Zapis to to_mut(): na Borrowed woła to_owned() i przełącza się na Owned, na Owned niczego nie kopiuje. Czas życia 'a zostaje w typie także po przejściu na Owned — uwalnia od niego dopiero into_owned(). I uwaga na diagramy: świeżo utworzony String nie ma zapasu pojemności, więc pierwsze push przydziela pamięć od nowa.
Szukaj po polsku: rust Borrow trait HashMap get &str · rust Cow to_mut · kopiowanie przy zapisie
How to learn ToOwned › Step 7 of 10 · back: Step 6 · next: Step 8 — The traps are steps 5 and 6 together