Skip to content

ToOwned: Clone for types whose owned twin is a different type

Level: 201 · working knowledge

One line: Clone goes &T → T; ToOwned goes &T → whatever the owned version of T actually is — which for str is String, a different type entirely.

fn main() {
    let name: &str = "Adam";
    let owned: String = name.to_owned();          // &str  -> String
    let nums: &[i32] = &[3, 1, 2];
    let owned_nums: Vec<i32> = nums.to_owned();   // &[i32] -> Vec<i32>
    println!("{owned} {owned_nums:?}");           // Adam [3, 1, 2]
}

Two different owned types, one method name. Clone cannot express that: its signature is fn clone(&self) -> Self, so the answer is always the same type it started with.

The trait, in full

pub trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;                 // required
    fn clone_into(&self, target: &mut Self::Owned) {   // provided
        *target = self.to_owned();                     // the default just allocates
    }
}

The associated type is the whole mechanism — Owned is what makes str's answer String and [i32]'s answer Vec<i32>. The bound on it, Borrow<Self>, is the promise that you can always get back to the borrowed form, which is what lets Cow hold either half and hand out a &str regardless.

Why it has to exist

Because Clone requires Sized, and str is not sized.

That single sentence explains the behaviour everybody trips over. str has no Clone impl at all — it cannot have one. But &str does, because shared references are Copy and therefore Clone. So when you write name.clone() on a &str, method lookup goes looking for a clone whose receiver is &str, does not find one on str, falls through to the reference's own impl, and hands you back another &str.

rustc will tell you so itself:

warning: call to `.clone()` on a reference in this situation does nothing
   |
35 |     let cloned: &str = name.clone();
   |                            ^^^^^^^^ help: remove this redundant call
   |
   = note: the type `str` does not implement `Clone`, so calling `clone` on `&str`
           copies the reference, which does not do anything and can be removed
   = note: `#[warn(noop_method_call)]` on by default

On a &String the same syntax does something different, and for the same reason read the other way: String does implement Clone, so lookup finds that impl first and r.clone() gives you a String. Same characters, different outcome, decided by whether the pointee is Sized.

For everything else, they are the same call

impl<T: Clone> ToOwned for T { type Owned = T; ... }

That blanket impl means every Clone type gets to_owned() for free, doing exactly what clone() does. So outside the unsized cases the choice is stylistic, and the community genuinely disagrees about it — one camp finds .to_string() clearer, another finds .to_owned() more honest about what is being bought. The rule of thumb worth keeping: clone() reads as "I have a T and want another", to_owned() as "I have a borrow and want to own it".

The argument you will meet, and its expiry date

Search this question and you will find the same advice everywhere, usually word for word: use to_owned() on a string literal, because to_string() is generic, goes through Display, and may allocate more than once. That was true when it was written, in 2015. It stopped being true in April 2016, when ToString was specialized for str, which shipped in 1.9 that May — the case everybody was worried about no longer runs the formatting machinery at all.

A decade later the advice is still being republished with the performance reasoning intact, sometimes quoting the original thread's own "this may be fixed in the future with specialization" caveat without noticing that the same thread answered it in May 2016: "Now that specialization for str::to_string() has landed, we can safely say that to_string() has the same performance as to_owned()."

Measured at -O on 1.97.1 and 1.98, String::from, .into(), .to_string(), .to_owned() and format!("a literal") all land between 56 and 61 ns on a 13-byte literal. format! is in that list because a template with nothing in it never reaches the formatting machinery:

// alloc::fmt::format
args.as_str().map_or_else(|| format_inner(args), crate::borrow::ToOwned::to_owned)

A literal-only format! has an as_str(), so it is to_owned. What costs about a third more is a format! that formats something: format!("{}", s) for an s the optimizer cannot see through took 77 ns against 58. Clippy flags the literal one anyway, as useless_format, for a reason that was never about speed — it is a formatting call with nothing to format.

Check what a benchmark was built with before believing it. The most-linked measurement of this question prints target/debug in its own transcript: at -O0 the five spellings differ by about 2%, a spread far too small to carry the conclusion drawn from it. The conclusion — format! is the slow one — is half right, and the method could not have shown which half.

And check what the optimizer could see. The same format!("{}", s) measures 77 ns when s is hidden behind a black_box and 56 when it is a literal the compiler folds — one expression, a quarter apart, decided by the harness rather than by the code under test.

So the performance argument is dead, and the argument that outlived it is about documentation. dtolnay's case, made in that same thread in 2017 ↗, is that &str and String are both strings, so "convert this string to a string" names nothing; what actually differs is ownership, and to_owned() is the spelling that says so at the point where a reader is asking why the conversion is there at all. Same conclusion as the 2015 advice, reached for a reason that does not expire.

One naming note, because it is a common slip: to_ methods do not consume selfto_owned(&self) borrows, and into_ is the prefix that means consumed. The rule relaxes on Copy types, where consuming costs the caller nothing: f64::to_bits(self) takes self by value.

The trap that blanket impl sets

On an Rc or Arc, .to_owned() clones the pointer, not the data:

let shared = Rc::new(String::from("ballot"));
let second = shared.to_owned();
// strong_count is now 2, and Rc::ptr_eq(&shared, &second) is true

Rc<T> is Clone, so the blanket impl applies and to_owned is clone — which for an Rc means bumping the reference count. If you wanted the String copied, you have to dereference first: (*shared).clone(). Reaching for to_owned because it sounds like it makes an independent copy is exactly the wrong instinct here.

A Cow is caught by the same mechanism and lands somewhere worse: cow.to_owned() returns another Cow in the same variant, so a Cow::Borrowed is still borrowed afterwards. Section 8 of the run below watches it not happen. into_owned() is the method that makes a Cow owned.

Clippy has both cases, and how loudly it says so tracks how wrong the result is. The Cow one is suspicious_to_owned, warn-by-default"this to_owned call clones the Cow<'_, str> itself and does not cause its contents to become owned", offering into_owned() or clone() depending on which you meant. The Rc one is implicit_clone, pedantic and therefore silent unless you asked for it — "implicitly cloning a Rc by calling to_owned on its dereferenced type", suggesting r.clone(). The split is fair: on the Cow you did not get what the name implies, while on the Rc you got exactly the right value under a misleading spelling.

Can you implement it yourself?

Almost never, and the two refusals are worth meeting because between them they explain the shape of the whole trait.

A type that is Clone cannot have one. The blanket impl already covers it, so your impl is a second one:

error[E0119]: conflicting implementations of trait `ToOwned` for type `DataRef<'_>`
   |
   = note: conflicting implementation in crate `alloc`:
           - impl<T> ToOwned for T
             where T: Clone;

Since essentially every ordinary type derives Clone, that rules out essentially every ordinary type.

A reference-like type has nowhere to put one either, even after you drop the Clone. type Owned is bound by Borrow<Self>, and a DataOwned { text: String } cannot hand out a &DataRef<'_> — there is no DataRef stored anywhere to lend:

error[E0277]: the trait bound `DataOwned: Borrow<DataRef<'_>>` is not satisfied
   |
note: required by a bound in `std::borrow::ToOwned::Owned`

Be precise about what that error proves, because it is easy to over-read: the type checker is refusing the missing bound, not the design. Add impl<'a> Borrow<DataRef<'a>> for DataOwned with a todo!() body and the ToOwned impl compiles perfectly well. (Name the lifetime. The elided Borrow<DataRef<'_>> spelling is rejected for a signature mismatch — found fn(&'1 DataOwned) -> &'1 DataRef<'1> against an expected &'1 DataRef<'3> — which is about elision, not about this design.) The wall is one step further on, at the moment you have to write a borrow that returns a reference to a DataRef the DataOwned never stored. So the blocker is semantic rather than syntactic, and no compiler error will state it for you.

Which is why every impl in the standard library is on an unsized referent rather than on a reference — str, CStr, OsStr, Path, [T]. Those are the types that genuinely have a separate owned form and are always met through a pointer, which is the situation the trait was shaped for.

What does compile is a Sized type that is not Clone, with type Owned = Self — the run below has one. The blanket impl<T: ?Sized> Borrow<T> for T satisfies the bound and nothing conflicts. It also buys nothing: it is Clone under a different name, and adding #[derive(Clone)] later turns it into the E0119 above. If you want a .to_owned() method on your own type, write an inherent one and skip the trait.

The genuinely blocked case is a validated wrapper — an "ASCII-only string" newtype you want to use with Cow. Doing it properly needs a #[repr(transparent)] wrapper around str and an unsafe pointer cast in borrow, because Borrow/ToOwned predate GATs and there is no safe way to make a &MyNewtype out of a &str. That limitation is still open: an IntoOwned pre-RFC ↗ from late 2025 is one attempt at harmonizing the pair, and had not converged on a signature that survives the existing blanket impls.

The trap in generic code

<&str as ToOwned>::Owned is &str, not String. The blanket impl applies to the reference itself, so a bound written on the reference resolves to the wrong side of the pair:

fn foo<S, T>(s: S) -> T where S: ToOwned<Owned = T>, T: Borrow<S> { s.to_owned() }
let _s: String = foo("hi");    // E0308: expected `String`, found `&str`

fn bar<S: ?Sized, T>(s: &S) -> T where S: ToOwned<Owned = T>, T: Borrow<S> { s.to_owned() }
let s: String = bar("hi");     // "hi"

S unifies with &str, so T is &str and the annotation is what breaks. The fix is to take &S rather than S, so S is str and T is String — and then S: ?Sized is not optional, because str is precisely the unsized case the trait exists for. Leave it off and the signature that was supposed to be the fix fails on its own bound instead: "the size for values of type str cannot be known at compilation time … required by an implicit Sized bound in bar". Two errors, one cause, and the second is the page's whole thesis arriving as a compiler message. This was filed as a diagnostics bug in 2016 ↗ and closed in 2020 once the error learned to suggest a conversion method; the underlying surprise is unchanged, and it is the same one as section 2b above, one level of generics up.

clone_into, the provided method

clone_into(&self, target: &mut Self::Owned) writes into a buffer you already have instead of allocating a new one. str's impl overrides it to reuse the String's existing capacity — the run below fills a 64-byte buffer and the capacity does not move. Same family as mem::take: the standard library's habit of offering the in-place spelling beside the allocating one. It has a page of its own, where the saving is counted rather than described — and where the argument order turns out to run opposite to Clone::clone_from.

The verified output

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

1. The owned twin is a different TYPE — that is the whole point
   "Adam"      (&str)    .to_owned() -> String "Adam"
   [3, 1, 2]   (&[i32])  .to_owned() -> Vec    [3, 1, 2]

2. `.clone()` cannot do that: on a &str it hands back another &str
   name.clone()    is still a &str: "Adam"
   needs_string(name.to_owned()) = 4
   needs_string(name.clone())    would be E0308 — expected String, found &str
   size_of::<&str>()   = 16   pointer + length
   size_of::<String>() = 24   pointer + length + capacity

2b. WHY .clone() behaves differently on &str than on &String
   (&String).clone() -> String  "Ada"   <- String: Clone exists
   (&str).clone()    -> &str    "Adam"  <- str: Clone does NOT
   str is !Sized, and Clone requires Sized. That is the whole reason
   ToOwned exists at all.

3. For everything that is Clone, they are the SAME call
   s.clone()    = "Ada"
   s.to_owned() = "Ada"   <- blanket impl<T: Clone> ToOwned for T

4. The trap that blanket impl sets: on an Rc it clones the POINTER
   strong_count before      = 1
   strong_count after       = 2
   same allocation?           true
   (*shared).clone() is the real copy: "ballot"

5. `clone_into`: the provided method that reuses a buffer
   capacity 64 -> 64 , contents "reuse me"   (no new allocation)

6. Why the trait is shaped that way: Cow pays only when it must
   "one  two"   -> "one two"   owned — one allocation
   "one two"    -> "one two"   borrowed — nothing allocated

7. Implementing it yourself: legal, and pointless
   Tally { seats: 3, name: "Ada" }.to_owned() -> Tally { seats: 3, name: "Ada" }
   type Owned = Self, so this is Clone wearing a different name.
   Give Tally a #[derive(Clone)] and it is E0119 instead: the
   blanket impl<T: Clone> ToOwned for T already covers it.

8. The same trap on a Cow — the instance clippy catches by default
   Cow::Borrowed.to_owned()   -> Cow::Borrowed — STILL borrowed
   Cow::Borrowed.into_owned() -> "ballot", a String
   to_owned clones the Cow. into_owned is what makes it owned.

Practice

Predict the owned twin before you run it. For a &str, a &[i32], a &Path, a &String, a plain i32 and an Rc<String>, write down what .to_owned() returns — the type, not the value — then check yourself with std::any::type_name_of_val.

Two of the six are the ones worth getting wrong. Say what 42.to_owned() gives you and why that is not a bug but the blanket impl working as designed. Then say how many heap buffers exist after Rc::new(String::from("ballot")).to_owned(), and what you would have written instead to get two.

Solution

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

//! Kata solution: `.to_owned()` answers to the SOURCE, not to `String`.
//!
//!   rustc --edition 2024 to_owned_kata.rs -o /tmp/tok && /tmp/tok

use std::any::type_name_of_val;
use std::path::Path;
use std::rc::Rc;

fn main() {
    println!("1. Four receivers, four different owned twins");
    let text: &str = "Ada";
    let nums: &[i32] = &[5, 2, 0];
    let path: &Path = Path::new("/tmp/ballot");
    let already: String = String::from("Ben");

    let a = text.to_owned();
    let b = nums.to_owned();
    let c = path.to_owned();
    let d = (&already).to_owned();

    println!("   &str     .to_owned() -> {}", type_name_of_val(&a));
    println!("   &[i32]   .to_owned() -> {}", type_name_of_val(&b));
    println!("   &Path    .to_owned() -> {}", type_name_of_val(&c));
    println!("   &String  .to_owned() -> {}", type_name_of_val(&d));
    println!("   One method name, four answers: `type Owned` is chosen by the source.");

    println!();
    println!("2. So it is NOT a stringifying operation");
    // The prediction most people get wrong. `i32: Clone`, so the blanket
    // `impl<T: Clone> ToOwned for T` applies and the owned twin of an i32 is
    // an i32 — this does not produce text and never could.
    let n = 42.to_owned();
    println!("   42.to_owned()  -> {:<3}  ({})", n, type_name_of_val(&n));
    println!("   42.to_string() -> {:<5}({})", format!("{:?}", 42.to_string()), type_name_of_val(&42.to_string()));
    println!("   `to_string` is about TEXT. `to_owned` is about OWNERSHIP.");
    println!("   They coincide on a &str and nowhere else.");

    println!();
    println!("3. The trap: on an Rc it clones the POINTER");
    let shared = Rc::new(String::from("ballot"));
    let second = shared.to_owned();
    println!("   type          {}", type_name_of_val(&second));
    println!("   strong_count  {}", Rc::strong_count(&shared));
    println!("   same buffer?  {}", Rc::ptr_eq(&shared, &second));
    let deep: String = (*shared).clone();
    println!("   the real copy is (*shared).clone() -> {deep:?}");

    println!();
    println!("4. Why `Owned: Borrow<Self>` is in the trait");
    // Every owned twin above can lend its borrowed half back, which is what
    // lets one signature take either side of the pair.
    fn shout(s: &str) -> String { s.to_uppercase() }
    println!("   shout(&a) = {:?}   <- String lends a &str back", shout(&a));
    println!("   the bound is the round trip, and it is what makes Cow possible.");
}

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

1. Four receivers, four different owned twins
   &str     .to_owned() -> alloc::string::String
   &[i32]   .to_owned() -> alloc::vec::Vec<i32>
   &Path    .to_owned() -> std::path::PathBuf
   &String  .to_owned() -> alloc::string::String
   One method name, four answers: `type Owned` is chosen by the source.

2. So it is NOT a stringifying operation
   42.to_owned()  -> 42   (i32)
   42.to_string() -> "42" (alloc::string::String)
   `to_string` is about TEXT. `to_owned` is about OWNERSHIP.
   They coincide on a &str and nowhere else.

3. The trap: on an Rc it clones the POINTER
   type          alloc::rc::Rc<alloc::string::String>
   strong_count  2
   same buffer?  true
   the real copy is (*shared).clone() -> "ballot"

4. Why `Owned: Borrow<Self>` is in the trait
   shout(&a) = "ADA"   <- String lends a &str back
   the bound is the round trip, and it is what makes Cow possible.

See also

  • clone_into — the other half of this trait: filling a buffer you already own, and what that is worth measured in allocations
  • Making a String — the five spellings that produce a String, and which to prefer; this page is the trait behind one of them
  • Concatenating strings — where s1.to_owned() + s2 comes from: + needs an owned left operand
  • String vs &str — the owned/borrowed pair this trait converts between
  • Six kinds of stringOsString/Path/Cow and the rest of the owned-borrowed pairs
  • Copy vs Clone — the trait this one generalizes
  • What a trait is — associated types, which are what make Owned possible
  • Strings: links, books and videos — where to read more, since &str is the case this trait exists for

Sources

The long-running style thread, worth reading for how much its reasoning changed while its conclusion did not: to_string() vs to_owned() for string literals ↗ (2015–2021). On implementing the trait yourself: Stack Overflow — implement ToOwned for user-defined types ↗, whose answer is the source of the referent-not-reference framing above.

The two threads this page settles, both worth reading for how much disagreement a "simple" question produced: Stack Overflow — the difference between clone and to_owned (the accepted answer is right; the sharpest explanation is BallpointBen's comment underneath it, on deref coercion and !Sized) and r/rust on the same question ↗ — where the top answer is correct, the "it doesn't matter" answer is nearly correct, and one upvoted reply claiming ToOwned is how you get the data out of an Rc is wrong in the exact way the trap section above demonstrates.

Po polsku

Nazwa mówi wszystko, jeśli przeczytać ją po polsku: to_owned() nie znaczy „zamień na tekst”, tylko „weź na własność”. Clone ma sygnaturę fn clone(&self) -> Self, więc odpowiedź zawsze jest tego samego typu; ToOwned ma typ powiązany type Owned i dlatego bliźniakiem str jest String, bliźniakiem [i32]Vec<i32>, a bliźniakiem PathPathBuf. Stąd bierze się też najczęściej mylona para: to_string() dotyczy tekstu, to_owned() dotyczy własności, a pokrywają się tylko na &str i nigdzie indziej — 42.to_owned() daje 42, a nie "42".

Powód istnienia tej cechy (trait) mieści się w jednym zdaniu: Clone wymaga Sized, a str nie ma rozmiaru znanego w czasie kompilacji. Dlatego str w ogóle nie ma implementacji Clone — i dlatego name.clone() na &str oddaje drugi &str: wyszukiwanie metody nie znajduje niczego na str i spada na implementację samej referencji, bo referencje współdzielone są Copy. Kompilator mówi to wprost ostrzeżeniem noop_method_call. Na &String ten sam zapis zachowuje się inaczej, bo String jest Clone, więc wraca String. Te same znaki, inny wynik, a rozstrzyga o tym Sized.

Rada, którą znajdziesz w niemal każdym omówieniu — także w polskich wpisach, bo wszystkie są potomkami jednego wątku z 2015 roku — brzmi: „na literale używaj to_owned(), bo to_string() idzie przez Display i alokuje dwa razy”. To przestało być prawdą w kwietniu 2016, gdy ToString zostało wyspecjalizowane dla str (wydanie 1.9). Pomiary przytoczone wyżej kładą wszystkie zapisy w przedziale 56–61 ns, razem z format!("literał"). Przy okazji morał ogólniejszy, przydatny przy każdym benchmarku znalezionym w sieci: sprawdź, czy autor mierzył z optymalizacją — najczęściej linkowany pomiar tej kwestii ma w swoim własnym wydruku target/debug. Argument, który to przeżył, dotyczy czytelności, a nie szybkości: „skonwertuj łańcuch na łańcuch” niczego nie nazywa, a to_owned() nazywa to, co faktycznie się zmienia — właściciela.

Zostają dwie pułapki, które zastawia implementacja zbiorcza impl<T: Clone> ToOwned for T:

  • Na Rc i Arc .to_owned() klonuje wskaźnik, a nie dane: licznik referencji rośnie, bufor zostaje ten sam. Prawdziwą kopię robi (*shared).clone(). Clippy widzi to jako implicit_clone, ale to lint pedantyczny, więc domyślnie milczy.
  • Na Cow .to_owned() zwraca kolejny Cow w tym samym wariancie, więc Cow::Borrowed po tej operacji nadal jest pożyczony. Metodą, która naprawdę przejmuje na własność, jest into_owned(). Tutaj Clippy ostrzega domyślnie (suspicious_to_owned) — i słusznie, bo to przypadek, w którym nazwa obiecuje coś, czego się nie dostaje.

Własnej implementacji ToOwned w praktyce się nie pisze: implementacja zbiorcza zajmuje już każdy typ, który jest Clone (próba kończy się na E0119), a wszystkie implementacje z biblioteki standardowej siedzą na typach bez znanego rozmiarustr, [T], Path, OsStr, CStr — bo dokładnie dla nich ta cecha powstała. I reguła nazewnicza na koniec, bo łatwo się o nią potknąć: przedrostek to_ nie pochłania self (to_owned(&self) tylko pożycza), a into_ — pochłania.

Szukaj po polsku: typy o nieznanym rozmiarze · przejęcie na własność · rust to_owned vs to_string · rust clone on a reference does nothing · rust noop_method_call · rust suspicious_to_owned Cow