Skip to content

How to learn ToOwned: ten steps, in order

Level: 101 → 201 · a learning path

One line: ToOwned itself adds one idea — a separate owned type — and the confusion lives in five ideas underneath it: unsized types, owned/borrowed pairs, Clone, method lookup and blanket impls. Re-reading the trait does not help; learning those five in order does, one page per step.

Each step is its own page: the idea, a checkpoint to predict, the verified answer from a program CI runs, and links out to the lesson that teaches it in full and to the official documentation. Predict each checkpoint before you open its answer. A wrong prediction means stay on that step: the next one assumes it.

Start at step 1 — or, if something specific surprised you, find its row below and start there. Step 1 states the question the path answers; steps 2 to 6 build what it takes to answer it; 7 to 9 apply it; 10 is optional, because most code never implements ToOwned at all.

Before step 1: the path assumes that a String owning a heap buffer, and a &str borrowing a view of one, is physical to you rather than abstract — you can draw both handles and the one buffer they share. If it is not yet, start in the library's Ownership section — Stack and heap and Ownership and moves are the two pages this path leans on — or with The Book's ch. 4.1, What Is Ownership? ↗. Three of its sections are this path's layer zero: The Stack and the Heap ↗; Memory and Allocation ↗, where a String is a pointer, a length and a capacity with its bytes on the heap, and a literal's text is hardcoded into the executable; and Variables and Data Interacting with Clone ↗, which step 4 picks up.

Find your step

Each surprise below is one missing idea, not a mystery about ToOwned. Start at the step in your row and work down from there.

If this surprised you Start at step
ToOwned exists at all, when Clone already does 1
size_of::<&str>() is not the size of a pointer 2
name.clone() on a &str gave back a &str 4
r.clone() on a &String gave a String, not a reference 5
.clone() on a &&String gave a &String, but on a &mut String a String 5
someone said .to_owned() on a &str works "by autoderef" 5
Clone::clone(&r) gave a &String, but Clone::clone(r) and r.clone() gave a String 5
a.to_owned() on a &Foo gave a &Foo, or E0308 said expected Foo, found &Foo 6
42_i32.to_owned() compiles, and is 42 6
"hi".to_owned() is a String, but ToOwned::to_owned(&s) on a &str is a &str 6
HashMap<String, _>::get accepts a &str 7
a Cow diagram showed to_mut() and push as one step 7
.to_owned() on an Rc did not copy the string 8
.to_owned() on a Cow::Borrowed is still borrowed 8
an article said Cow has into_borrowed, or that to_mut() gives a &mut str the Cow claims, run
x.to_owned() inside a map over a &[&T] gave &Ts, or E0277 about FromIterator<&T> 6
a kata had you write a to_owned that canonicalizes, and Cow::to_mut() changed your text three ToOwned katas, run
clone_into into a String::new() saved nothing, but into an empty String::with_capacity(64) it did 9
impl ToOwned for MyType is E0119 10
you cannot write Borrow for your view struct 10
you are not sure whether to write clone, to_owned, to_string or into after the steps

The steps

Step Page What clicks Checkpoint
1 ToOwned is Clone with a separate owned type type Owned, and the impl sitting on str, not on &str what "hi", [1, 2][..] and a Path turn into
2 Some types have no size why you only ever hold a pointer to a str, and why it is two words is &str the size of &String?
3 Owned and borrowed are two different types the borrowed half is the type behind the &, named by Deref the types of *s and *v
4 Clone hands back Self — and every &T is Clone why str cannot be Clone, and why every reference is is a usable after let b = a;?
5 The dot takes the first receiver that fits the candidate list, and where a deref does — and does not — happen three clones and three to_owneds
6 One blanket impl covers every Clone type impl<T: Clone> ToOwned for T, what it does on a &Foo, and why a &str has two impls 42_i32.to_owned(), a &Ticket, and one &str two ways
7 Borrow is the way back type Owned: Borrow<Self>, HashMap::get, and how a Cow reads and writes seats.get("Ada"), and to_mut() twice
8 The traps are steps 5 and 6 together Rc and Cow are Clone, so the wrapper is what gets copied a strong count and a Cow variant
9 clone_into refills instead of allocating reuse needs room already there a roomy buffer's capacity
10, optional Implementing it — on the referent, or not at all E0119, the unsized wrapper, and the inherent method two spellings of one call
after Clone, ToOwned or From? the three side by side, with the heap read off the addresses —

Beside the steps:

  • ToOwned in real code — fourteen excerpts from std at 1.98.0 and from serde_json, bstr, camino, regex, borrowme and beef, each behind a pinned permalink; read it after step 10.
  • Lints around ToOwned: bad and good, run — twenty-two rustc and clippy warnings, each with a program that triggers it, the warning, a silent version and when the lint is right, plus the to_owned mistakes no lint catches.
  • Every ToOwned error, and its fix — twenty compiler errors around ToOwned, Clone, Borrow and Cow, findable by code or by message: the code, rustc's words, the mistake, and a fix that compiles.
  • Where Clone will not do: code only — ten pairs of fences, .clone() that fails to compile beside ToOwned that works, each checked by the compiler on every build.
  • What Cow explanations get wrong, run — ten claims about Cow from articles, books and chat answers, each checked against the compiler; read it after step 7.
  • Three ToOwned katas, run — a circulating set of three katas with tests and solutions: which are sound, the one wrong word in the second, and the third's canonicalizing to_owned, which breaks Cow and map lookups, repaired.
  • Helpful resources for the path — book chapters, the Reference, the Rustonomicon, articles and talks for every step, with the ones to read with care.

Everything else ToOwned touches

Explanations of ToOwned tend to arrive as a list of neighbouring topics. Each has a place on the path and a page in the library:

Topic On the path In the library
ownership, moves, the stack and the heap before step 1 Ownership, Stack and heap, Ownership and moves
dynamically sized types, ?Sized, wide pointers step 2 str is unsized, Marker traits
owned types and their heap buffers step 3 String vs &str, Stack and heap
Clone, Copy, and &T being Copy step 4 Copy vs Clone
method resolution, autoref and autoderef, Deref step 5 Method resolution, Coercion
blanket impls and coherence step 6 Extension traits
smart pointers: Rc, Arc, Box<str> step 8 Rc, The third owned form
Borrow (and BorrowMut, its &mut twin) step 7 Borrow
Cow, clone-on-write steps 7 and 8 Cow
allocation cost and reuse steps 8, 9 and after clone_into, What a clone costs
to_owned vs to_string vs From/Into after the steps Making a string, From and Into
implementing it for your own type step 10 Implementing ToOwned
the std documentation page itself step 1 Reading the ToOwned docs

Katas along the path

The steps say what to understand; these make you write it. Each lives on the page its step reads, in the order the steps unlock them.

After step Kata On
1 Write the function that hands back the owned twin — one generic line, six arguments, and the ?Sized that lets str in Step 1
2 Measure both halves of a reference — &str, &[i32], &dyn Display and &i32, and the ?Sized that takes all four str is unsized
3 One &str parameter, three callers — then flip it to String and count what each call site pays String vs &str
4 One E0382, three fixes — and what each costs the caller Copy vs Clone
5 Predict the receiver, then take it away from the dot — five calls, three that are not a String, each fixed two ways Step 5
6 Give every slice a .middle() — one trait, two methods, and only one of them reachable through a dot Extension traits
6 Own every borrow in a &[&T] — the obvious closure, loud in one place and quiet in another, fixed three ways Step 6
6 Predict the owned twin before you run it — six receivers, and the two everybody gets wrong ToOwned
7 One lookup for every kind of key — three maps searched by their borrowed forms, zero allocations counted Borrow
7 Pay only when you have to — a Cow that copies only the rows it changes Cow
8 Predict the count four times — one roster shared by three tallies, and the edge that leaks Rc
8 Freeze a candidate column three ways — ending on the .to_owned() that clones a pointer instead of the text The third owned form
9 Four loops that all look like reuse — predict the allocations, then count them clone_into
10 A slice that promises its order — Sorted<T> with Borrow, ToOwned and a Cow Implementing ToOwned
10 Repair the username kata — canonicalize at the door, copy in to_owned, and watch Cow and HashMap agree again Three ToOwned katas, run

Every step has at least one. The full sequence, with every other kata in the library, is KATAS.md.

Tests for anything else you read

Most explanations of ToOwned get the trait right and one step underneath it wrong. The tell for each:

  • "&str implements ToOwned to produce a String." str does, with a hand-written impl. There is no hand-written impl ToOwned for &str: the blanket impl<T: Clone> ToOwned for T covers &str because &str is Clone, and that impl produces a &str — step 1, then step 6.
  • ".to_owned() works on a &str through autoderef" — or through autoref. Neither happens: str's to_owned(&self) already takes a &str, so the receiver is passed as it is. Had a & been added or removed, the call would have returned a &str — step 5.
  • "On a reference, clone() just copies the pointer." True only when the pointee is not Clone — step 5. On a &String it allocates a new String.
  • "ToOwned almost always allocates on the heap." 42_i32.to_owned() touches no heap, and .to_owned() on an Rc bumps a count. It allocates when the impl it lands on does — step 8 and after the steps.
  • "After to_mut(), writing to a Cow never allocates." The clone happens at most once, on the first to_mut(). Growth is a separate cost: the fresh String has no spare capacity, so the first push reallocates, as it would for any String — step 7.
  • "ToOwned's Owned form can be a transformed value, not just a clone." It compiles, and it breaks everything built on the trait: Cow::to_mut() rewrites the text before you write, and a map lookup by the borrowed form misses. Every std impl copies; put the transform in a constructor — three ToOwned katas, run.
  • "To implement ToOwned for your struct, write type Owned = Self." It compiles only while the struct is not Clone, is Clone under another name, and becomes E0119 the day someone adds #[derive(Clone)] — step 10.
  • "clone_into is another way to get owned data." It is the way to refill owned data. Into a String with no capacity it saves nothing; into one with enough capacity it reuses the buffer, even at length zero — step 9.
  • "Prefer to_owned() to to_string(), because it is faster." Not since Rust 1.9; the ToOwned page has the measurements. The argument that survives is that to_owned names what changes — the owner.

If you are coming from another language

  • C++ — steps 2 and 3 are std::string and std::string_view, except that Rust also names the thing the view points at. Step 4 is a copy constructor you have to call by name. Steps 5 and 6 are where C++ intuition misleads, and not because of spelling: in C++, copying the pointer (auto q = p;) and copying the pointee (auto s = *p;) look different, while Rust's r.clone() is one spelling that the method search resolves to either, with nothing at the call site to say which.
  • Python — the trap has a Python twin. b = a gives the same list a second name, while b = a[:] or copy.copy(a) gives a new list, and the spelling tells you which you got. Rust's r.clone() can be either, with the same spelling: on a &String it is a[:], a new String; on a &str it is b = a, the same text under a second reference (step 5). What has no Python twin is step 4: being copyable at all is a trait a type may decline to implement.

See also

Po polsku

Jeśli ToOwned wciąż się „nie klei”, to prawie na pewno nie z powodu samej cechy (trait). Ona wnosi jedną nową informację — typ powiązany Owned, czyli osobny typ dla wersji posiadanej. Zamieszanie siedzi w pięciu pojęciach pod spodem: typy bez znanego rozmiaru (str, [T], Path), para własność–pożyczka jako dwa różne typy (String i str), Clone zwracające Self, wyszukiwanie metody po kropce i implementacja zbiorcza (blanket impl). Dlatego ponowne czytanie dokumentacji ToOwned nie pomaga, a przejście tych kroków po kolei — tak. Każdy krok ma teraz własną stronę.

Krok 1 zaczyna od sedna: implementacja jest na str, nie na &str — skoro Self to str, to &self to &str. Najważniejsze są potem kroki 4–6 razem. Każda referencja współdzielona &T jest Copy, więc i Clone, więc — przez impl<T: Clone> ToOwned for T — ma to_owned(). Kiedy Foo nie jest Clone, (&foo).to_owned() nie znajduje niczego na Foo i spada na implementację dla &Foo: dostajesz kopię wskaźnika, a z adnotacją typu — E0308 bez słowa o Clone. Na &String jest odwrotnie: String::clone pasuje pierwsze, więc dostajesz nowy String. Te same znaki, dwa wyniki, a rozstrzyga kolejność wyszukiwania — bez żadnej „autodereferencji” w przypadku &str.

Każdy krok ma punkt kontrolny: najpierw przewidź wynik, potem rozwiń zweryfikowany wydruk na stronie kroku. Błędna przepowiednia znaczy „zostań na tym kroku” — następny zakłada, że poprzedni już siedzi. Przy każdym kroku są linki do lekcji, do oficjalnej dokumentacji (Docs) i do słowniczka, a tabela Katas along the path podaje ćwiczenia w tej samej kolejności co kroki. Książki i rozdziały do każdego kroku zebrano na stronie z zasobami.

Szukaj po polsku: typy bez znanego rozmiaru · implementacja zbiorcza · rust to_owned vs clone · rust autoref method resolution · rust clone on reference returns reference