'Implement ToOwned for user-defined types

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)



Solution 1:[1]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1