Skip to content

Step 1: ToOwned is Clone with a separate owned type

How to learn ToOwned › Step 1 of 10 · next: Step 2 — Some types have no size

Level: 101 · a step on a learning path

One line: Clone turns a &T into another T; ToOwned turns a &B into a B::Owned, which may be a different type — a &str into a String — and the impl that does it sits on str, the type behind the &, not on &str.

Two signatures

// Clone: the copy is the type you started with
fn clone(&self) -> Self;             // &String -> String

// ToOwned: the copy is whatever Owned names
fn to_owned(&self) -> Self::Owned;   // &str -> String

That is the whole difference, and the rest of the path is about why it has to exist. To see it decide whether code compiles before reading any further, Where Clone will not do is ten pairs of fences and no prose. Here is the trait in full:

pub trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;
    fn clone_into(&self, target: &mut Self::Owned) { ... }
}
  • type Owned is the one new idea: an associated type, a type the impl names. It is what lets str's answer be String. How associated items work in general is on What a trait is.
  • Borrow<Self> is the way back from the owned type to the borrowed one — step 7.
  • clone_into is a provided method that refills an owned value you already have — step 9.
  • And notice what is not there. Clone is declared Clone: Sized; ToOwned is not, so its Self may be a type with no size, such as str. Step 2 is why that matters.

The impl is on the type behind the &

Self Self::Owned so &self is
str String &str
[T], for T: Clone Vec<T> &[T]
Path PathBuf &Path
OsStr OsString &OsStr
CStr CString &CStr
any T: Clone T itself &T — the blanket impl, step 6

Read the first row slowly, because it is the one most explanations blur: Self is str, so &self is &str, and the result is a String. A &str is the argument, not the implementing type.

One sentence you will meet gets this wrong: "&str implements ToOwned to produce a String". It is str that does. Whether &str has a ToOwned of its own — it does, and it is not the one you want — is step 6's surprise, once steps 4 and 5 have put the pieces in place.

Checkpoint

Predict before you open the answer. What does each of "hi".to_owned(), [1_i32, 2][..].to_owned() and Path::new("notes.txt").to_owned() turn into — and which type's impl produced each one?

The answer

Verified output of clone_vs_to_owned.rs — regenerated by tools/run_examples.py, never hand-typed.

Clone: the copy is the type you started with
   Clone::clone(&name)   name: String   -> alloc::string::String
   Clone::clone(&votes)  votes: Vec<i32> -> alloc::vec::Vec<i32>

Checkpoint. What does each .to_owned() turn into?
   "hi".to_owned()                   -> alloc::string::String
   [1_i32, 2][..].to_owned()         -> alloc::vec::Vec<i32>
   Path::new("notes.txt").to_owned() -> std::path::PathBuf

Which type carries the impl: the one behind the &
   <str as ToOwned>::Owned   = alloc::string::String
   <[i32] as ToOwned>::Owned = alloc::vec::Vec<i32>
   <Path as ToOwned>::Owned  = std::path::PathBuf
   <OsStr as ToOwned>::Owned = std::ffi::os_str::OsString
   <CStr as ToOwned>::Owned  = alloc::ffi::c_str::CString

The questions this step leaves open

Each is the next step's job, so it is fine that they are still open:

Practice

Write the function that hands back the owned twin. Write owner, generic over a borrowed type B, that takes a &B and returns B's owned type. The body is one line; the signature is the exercise. Then, before running anything, write down what it returns for "Ada", for &text where text: &str, for &[1, 2][..], for Path::new("a.txt"), for OsStr::new("a.txt") and for &7.

Last, delete ?Sized from your bound and say which of the six calls stop compiling, and why.

Solution

clone_vs_to_owned_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

//! Kata solution for step 1: one generic function that hands back the owned
//! twin of whatever it is lent — and the argument's type decides which twin.
//!
//!   rustc --edition 2024 clone_vs_to_owned_kata.rs -o /tmp/cvtok && /tmp/cvtok

use std::any::type_name_of_val;
use std::ffi::OsStr;
use std::path::Path;

/// `?Sized` is what lets `B` be `str`, `[i32]`, `Path` or `OsStr`.
fn owner<B: ToOwned + ?Sized>(borrowed: &B) -> B::Owned {
    borrowed.to_owned()
}

fn main() {
    println!("One function, six arguments: the type of the argument picks B");
    let text: &str = "Ada";
    println!("   owner(text)                 B = str   -> {}", type_name_of_val(&owner(text)));
    println!("   owner(&text)                B = &str  -> {}", type_name_of_val(&owner(&text)));
    println!("   owner(&[1, 2][..])          B = [i32] -> {}", type_name_of_val(&owner(&[1, 2][..])));
    println!("   owner(Path::new(\"a.txt\"))   B = Path  -> {}", type_name_of_val(&owner(Path::new("a.txt"))));
    println!("   owner(OsStr::new(\"a.txt\"))  B = OsStr -> {}", type_name_of_val(&owner(OsStr::new("a.txt"))));
    println!("   owner(&7)                   B = i32   -> {}", type_name_of_val(&owner(&7)));

    println!();
    println!("The one that surprises: an extra & changes B, and so the answer");
    let same = owner(&text);
    println!("   owner(&text) is the same &str, same address: {}", std::ptr::eq(same, text));
}

Verified output of clone_vs_to_owned_kata.rs — regenerated by tools/run_examples.py, never hand-typed.

One function, six arguments: the type of the argument picks B
   owner(text)                 B = str   -> alloc::string::String
   owner(&text)                B = &str  -> &str
   owner(&[1, 2][..])          B = [i32] -> alloc::vec::Vec<i32>
   owner(Path::new("a.txt"))   B = Path  -> std::path::PathBuf
   owner(OsStr::new("a.txt"))  B = OsStr -> std::ffi::os_str::OsString
   owner(&7)                   B = i32   -> i32

The one that surprises: an extra & changes B, and so the answer
   owner(&text) is the same &str, same address: true

Without ?Sized, four calls fail with E0277, "the size for values of type str cannot be known at compilation time" (and the same for [{integer}] and [u8], which is what Path and OsStr wrap): owner(text), owner(&[1, 2][..]), owner(Path::new(…)) and owner(OsStr::new(…)) all need B to be unsized. owner(&text) and owner(&7) keep compiling, because &str and i32 have a size — which is also why those two are the calls whose answers are not the owned type you expected. Step 2 is the ?Sized half and step 6 the &str half.

Go deeper

Read: ToOwned, its first three sections — the trait, in full, why it has to exist and for everything else, they are the same call. Then Reading the ToOwned docs, from the declaration to the associated type.

Around it in the library: Path and PathBuf for the third pair, Six kinds of string for OsStr and CStr, and String vs &str for the pair you will use most.

Docs: ToOwned ↗ · Clone ↗ · Associated types ↗, in the Reference

Words: Clone, Borrow<Q> and associated function in the glossary.

Po polsku

Clone robi z &T kolejne T — ten sam typ. ToOwned robi z &B wartość typu B::Owned, który może być innym typem: z &str powstaje String. Najważniejszy szczegół tego kroku: implementacja jest na str, czyli na typie za referencją, a nie na &str. Skoro Self to str, to &self to &str — i dlatego s.to_owned() daje String, bez żadnej dereferencji.

Często powtarzane zdanie „&str implementuje ToOwned, dając String” jest więc błędne — robi to str. Czy &str ma własną implementację ToOwned? Ma, i nie jest to ta, której szukasz; to temat kroku 6, gdy kroki 4 i 5 będą już za tobą.

Szukaj po polsku: typ powiązany w Ruscie · rust ToOwned vs Clone · rust impl ToOwned for str


How to learn ToOwned › Step 1 of 10 · next: Step 2 — Some types have no size