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 Ownedis the one new idea: an associated type, a type the impl names. It is what letsstr's answer beString. 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_intois a provided method that refills an owned value you already have — step 9.- And notice what is not there.
Cloneis declaredClone: Sized;ToOwnedis not, so itsSelfmay be a type with no size, such asstr. 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:
- Why can
strnot simply beClone? → Step 2: some types have no size, then step 4:Clonehands backSelf - Why is the owned twin a different type at all? → Step 3: owned and borrowed are two different types
- Does
&strhave aToOwnedimpl too — and if so, why does"hi".to_owned()still give aString? → Step 5: the dot takes the first receiver that fits, then step 6: one&str, two impls - Where does
42_i32.to_owned()come from, when nobody wrote an impl fori32? → Step 6: one blanket impl - So when do I write
clone,to_ownedorFrom? → After the steps:Clone,ToOwnedorFrom?
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