Step 9: clone_into refills instead of allocating¶
How to learn ToOwned › Step 9 of 10 · back: Step 8 — The traps are steps 5 and 6 together · next: Step 10 — Implementing it: on the referent, or not at all
Level: 201 · a step on a learning path
One line: The provided method writes into an owned value you already have. It saves something only when that value already has room: into a fresh String::new() it allocates exactly as to_owned would.
The pair¶
fn to_owned(&self) -> Self::Owned; // a new owner, every call
fn clone_into(&self, target: &mut Self::Owned) { ... } // reuse the one you have
The provided body is *target = self.to_owned(); — it builds a new owner and drops the old one, so by itself it saves nothing. The saving comes from the impls that override it, and there are three:
strand[T], which copy into the target's existing buffer and only grow it when the new contents do not fit;- the blanket impl from step 6, which overrides it with
target.clone_from(self). So everyClonetype gets whatever itsclone_fromdoes — andString's andVec's reuse the buffer.
That is also why a String can have an efficient clone_into without an impl ToOwned for String of its own, which would be E0119: the efficient part lives in impl Clone for String, and the blanket impl calls it.
It is the ToOwned twin of Clone::clone_from from step 4, and it exists for the same reason: in a loop, the allocation is the expensive part, and the buffer from the last iteration is already sitting there.
When it pays, and when it does not¶
- A buffer with room — same address, same capacity, no allocation. The checkpoint.
- A
Stringwith no capacity —String::new(), so the first call allocates, exactly liketo_owned. Empty is not the test: the checkpoint's buffer has length 0 and still reuses, because it has capacity. - A buffer that is too small —
with_capacity(4)receiving eight bytes has to grow, which is an allocation. - A loop over rows — one buffer for all of them, as long as it is big enough for the longest.
- A target you are about to move away — nothing is left to reuse next time; the
clone_intopage counts this and two more.
Checkpoint¶
Predict before you open the answer. buf was made by String::with_capacity(64). Does "reuse me".clone_into(&mut buf) change its capacity? Does it keep the same buffer?
The answer
Verified output of clone_into_refills.rs — regenerated by tools/run_examples.py, never hand-typed.
Checkpoint. buf was made by String::with_capacity(64). Does clone_into change its capacity?
capacity unchanged: true, same buffer: true, buf = "reuse me"
Into an empty String there is no room, so it allocates like to_owned would
capacity before: 0
capacity after holds the text: true
Length zero is not the test; capacity is
with_capacity(4), 8 bytes in: capacity had to grow: true
Every Clone type gets the reuse too: the blanket impl forwards to clone_from
String::clone_into -> same buffer: true
Vec<i32>::clone_into -> same buffer: true, [3, 1, 2]
A loop that refills one buffer, instead of making a String per row
Ada same buffer: true
Grace same buffer: true
Barbara same buffer: true
What this step sets up¶
- Step 10: if you implement
ToOwned,clone_intois the one provided method worth overriding. - After the steps: allocation is a property of the impl a call lands on, never of the trait's name.
Go deeper¶
Read: clone_into — what it saves, counted, three ways it does not pay and which types the reuse reaches.
Around it in the library:
- Building a string, the other ways to write into a buffer you already hold
- What a clone costs, where the allocations of an ordinary clone are added up
- The
Vecand what aVecguarantees, for capacity and when it grows - When
Stringis too slow
Docs: ToOwned::clone_into ↗ · Clone::clone_from ↗, the method it generalizes
Kata: Four loops that all look like reuse — predict the allocations, then count them.
Po polsku¶
clone_into(&self, target: &mut Self::Owned) zapisuje do wartości posiadanej, którą już masz, zamiast budować nową. Oszczędza tylko wtedy, gdy ta wartość ma miejsce: bufor z String::with_capacity(64) zostaje ten sam, a do pustego String::new() i tak trzeba przydzielić pamięć — dokładnie jak przy to_owned.
Domyślna implementacja to po prostu *target = self.to_owned();, więc sama niczego nie oszczędza. Zysk dają str i [T], które ją nadpisują. To odpowiednik Clone::clone_from dla ToOwned, a naturalne miejsce na niego to pętla z jednym buforem.
Szukaj po polsku: rust clone_into · rust clone_from reuse allocation · ponowne użycie bufora
How to learn ToOwned › Step 9 of 10 · back: Step 8 · next: Step 10 — Implementing it: on the referent, or not at all