12

Consider the following example code:

#[derive(Clone)]
struct DataRef<'a> {
    text: &'a str,
}

#[derive(Clone)]
struct DataOwned {
    text: String,
}

I am going to implement ToOwned for DataRef in this way:

impl ToOwned for DataRef<'_> {
    type Owned = DataOwned;

    fn to_owned(&self) -> DataOwned {
        DataOwned {
            text: self.text.to_owned(),
        }
    }
}

Literally, it makes sense right? But there are some problems.


The first problem is, since ToOwned provides a blanket implementation:

impl<T> ToOwned for T where T: Clone { ... }

the above code will give a compile error:

error[E0119]: conflicting implementations of trait `std::borrow::ToOwned` for type `DataRef<'_>`
  --> src/main.rs:13:1
   |
13 | impl ToOwned for DataRef<'_> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `alloc`:
           - impl<T> ToOwned for T
             where T: Clone;

Well, we can make some compromises. Let's remove #[derive(Clone)] from DataRef. (However, I can't do that in my real case, because it's a breaking change and doesn't make sense)


And then the second problem, the associated type Owned of ToOwned requires that it implements Borrow<Self>.

pub trait ToOwned {
    type Owned: Borrow<Self>;

    fn to_owned(&self) -> Self::Owned;

    ...
}

If we implement Borrow for DataOwned as it is defined:

impl<'a> Borrow<DataRef<'a>> for DataOwned {
    fn borrow(&self) -> &DataRef<'a> {
        DataRef { text: &self.text }
    }
}

this is obviously impossible, because we cannot store a DataRef somewhere.


So my questions are:

  • Is there any way to implement ToOwned for the above example?

  • Consider the problems above, is ToOwned not supposed to be implemented manually by users? Because I can't imagine a real example to oppose this point.

  • (Optional Answer) If changes to the definition of std ToOwned are allowed, are there any possible improvements to make it better? (Unstable and unimplemented Rust features are allowed)

4
  • borrow::ToOwned is closely tied to borrow:Cow<T>, which requires T: ToOwned. There are impl of PartialEq, Ord and such for Cow, which is where the Borrow requirement comes from: Because Borrow guarantees that the owned and the borrowed variant behave the same, Cow can compare and order to itself, irrespective of whether the values are of the borrowed or owned variant. Cow works because ToOwned and Borrow make it possible to go seamlessly from one to the other. Can you give some details on why you want to implement ToOwned? Commented May 3, 2022 at 21:51
  • @user2722968 Thanks for pointing out that it is related to Cow. I just want my types to be robust and user-friendly, and to_owned is another option besides into. I literally think it makes sense to implement ToOwned, which is somewhat similar to implementing it for str and Path (although I know these are unsized). Commented May 3, 2022 at 22:19
  • Is your last question "assuming I can change std", or "if I want to write my own trait"? Commented May 3, 2022 at 23:26
  • @ChayimFriedman It's "assuming you can change std". Commented May 3, 2022 at 23:29

1 Answer 1

16

The problems you are seeing are because ToOwned isn't supposed to be implemented for a reference type but for a referent. Notice what the standard library implementations look like:

impl ToOwned for str
impl ToOwned for CStr
impl ToOwned for OsStr
impl ToOwned for Path
impl<T> ToOwned for [T]

These are all !Sized, !Clone types which always appear inside of some generic pointer type (e.g. &str, Box<str>, &Path) or specialized owning pointer type (String contains a str; PathBuf contains a Path; Vec<T> contains a [T]). The purpose of ToOwned is to allow conversion from a reference to the data — not something you called FooRef but an actual & — to the specialized owning pointer type, in such a way that the transformation is reversible and consistent (that's what the Borrow<Self> is about).

If you wanted to get the benefits of Borrow and ToOwned, you'd need to define a type which isn't a reference but something a reference can point to, like this:

use std::borrow::Borrow;
#[repr(transparent)]
struct Data {
    text: str,
}

#[derive(Clone)]
struct DataOwned {
    text: String,
}

impl Borrow<Data> for DataOwned {
    fn borrow<'s>(&'s self) -> &'s Data {
        // Use unsafe code to change type of referent.
        // Safety: `Data` is a `repr(transparent)` wrapper around `str`.
        let ptr = &*self.text as *const str as *const Data;
        unsafe { &*ptr }
    }
}

impl ToOwned for Data {
    type Owned = DataOwned;
    fn to_owned(&self) -> DataOwned {
        DataOwned { text: String::from(&self.text) }
    }
}

However, note that this strategy only works for a single contiguous block of data (such as the UTF-8 bytes in a str). There is no way to implement Borrow + ToOwned in a way that would work for a DataOwned that contains two strings. (It could be done for one string and some fixed-sized data, but that would still be challenging because custom dynamically-sized types aren't very well-supported by Rust as yet.)

It isn't often worth doing all this for a String wrapper, but it might be worthwhile if you want to enforce some stronger type/validity invariant about the contents (e.g. "all characters are ASCII" or "the string (or string slice) is a well-formed JSON fragment"), and you want to be able to interact with existing generic code that expects ToOwned to be implemented.

If you just want to be able to call a .to_owned() method on a DataRef, don't bother with the ToOwned trait; just write an inherent (non-trait) method.

Sign up to request clarification or add additional context in comments.

3 Comments

The String wrapper is just an example, in my real case this structure contains more fields. I will consider your last suggestion. +1 for the detailed explanation.
I'd like to create str wrapper with some validation. Going wih your suggestion I'd write #[repr(transparent)] struct ValidStr(str);. But then. how could I create an instance of such type? In particular, I have no idea how to implement TryFrom<&str> for &ValidStr or something similar, is it even possible?
@Dekakaruk In your TryFrom implementation, you'd do the same thing as the Borrow implementation in my code does, except with a validation first.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.