'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
ToOwnedfor the above example?Consider the problems above, is
ToOwnednot 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
ToOwnedare 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 |
