Skip to content

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:

  • str and [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 every Clone type gets whatever its clone_from does — and String's and Vec'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 String with no capacity — String::new(), so the first call allocates, exactly like to_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_into page 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_into is 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:

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