为用户定义的类型实现 ToOwned
Implement ToOwned for user-defined types
考虑以下示例代码:
#[derive(Clone)]
struct DataRef<'a> {
text: &'a str,
}
#[derive(Clone)]
struct DataOwned {
text: String,
}
我将以这种方式为 DataRef
实施 ToOwned
:
impl ToOwned for DataRef<'_> {
type Owned = DataOwned;
fn to_owned(&self) -> DataOwned {
DataOwned {
text: self.text.to_owned(),
}
}
}
从字面上看,这很有道理吧?但是也有一些问题。
第一个问题是,因为 ToOwned
提供了 blanket implementation:
impl<T> ToOwned for T where T: Clone { ... }
以上代码会出现编译错误:
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;
好吧,我们可以做出一些妥协。让我们从 DataRef
中删除 #[derive(Clone)]
。 (然而,在我的真实案例中我不能这样做,因为这是一个破坏性的变化并且没有意义)
然后是第二个问题,ToOwned
requires that it implements Borrow<Self>
的关联类型Owned
。
pub trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
...
}
如果我们按照定义为 DataOwned
实现 Borrow
:
impl<'a> Borrow<DataRef<'a>> for DataOwned {
fn borrow(&self) -> &DataRef<'a> {
DataRef { text: &self.text }
}
}
这显然是不可能的,因为我们不能在某处存储一个DataRef
。
所以我的问题是:
上面的例子有什么方法可以实现ToOwned
吗?
考虑上面的问题,ToOwned
不应该是用户手动实现的吗?因为我想不出一个真实的例子来反对这一点。
(可选答案)如果允许更改 std ToOwned
的定义,是否有任何可能的改进使其变得更好? (允许不稳定和未实现的 Rust 特性)
您看到的问题是因为 ToOwned
不应该被实施 引用类型 而是 referent。 注意 the standard library implementations 的样子:
impl ToOwned for str
impl ToOwned for CStr
impl ToOwned for OsStr
impl ToOwned for Path
impl<T> ToOwned for [T]
这些都是 !Sized, !Clone
类型,它们总是出现在某些通用指针类型(例如 &str
、Box<str>
、&Path
)或专用指针类型(String
包含一个str
;PathBuf
包含一个Path
;Vec<T>
包含一个[T]
)。 ToOwned
的目的是允许从 reference 转换为数据——不是你所谓的 FooRef
而是实际的 &
—— 到专门的拥有指针类型,这样转换是 可逆和一致的 (这就是 Borrow<Self>
的意义所在)。
如果您想获得 Borrow
和 ToOwned
的好处,您需要定义一个不是引用但引用可以指向的类型,如下所示:
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) }
}
}
但是,请注意此策略仅 适用于单个连续数据块(例如 str
中的 UTF-8 字节)。无法以适用于包含 两个 字符串的 DataOwned
的方式实现 Borrow
+ ToOwned
。 (它可以对一个字符串和一些 fixed-sized 数据完成,但这仍然具有挑战性,因为自定义 dynamically-sized 类型还不是很 well-supported Rust。)
通常不值得为一个 String
包装器做所有这些,但如果你想对内容强制执行一些更强的 type/validity 不变性(例如“所有字符都是ASCII”或“字符串(或字符串切片)是 well-formed JSON 片段”), 和 您希望能够与现有的通用代码进行交互期望 ToOwned
得到实施。
如果您只想能够在 DataRef
上调用 .to_owned()
方法,请不要理会 ToOwned
特征;只需编写一个固有的(non-trait)方法。
考虑以下示例代码:
#[derive(Clone)]
struct DataRef<'a> {
text: &'a str,
}
#[derive(Clone)]
struct DataOwned {
text: String,
}
我将以这种方式为 DataRef
实施 ToOwned
:
impl ToOwned for DataRef<'_> {
type Owned = DataOwned;
fn to_owned(&self) -> DataOwned {
DataOwned {
text: self.text.to_owned(),
}
}
}
从字面上看,这很有道理吧?但是也有一些问题。
第一个问题是,因为 ToOwned
提供了 blanket implementation:
impl<T> ToOwned for T where T: Clone { ... }
以上代码会出现编译错误:
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;
好吧,我们可以做出一些妥协。让我们从 DataRef
中删除 #[derive(Clone)]
。 (然而,在我的真实案例中我不能这样做,因为这是一个破坏性的变化并且没有意义)
然后是第二个问题,ToOwned
requires that it implements Borrow<Self>
的关联类型Owned
。
pub trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
...
}
如果我们按照定义为 DataOwned
实现 Borrow
:
impl<'a> Borrow<DataRef<'a>> for DataOwned {
fn borrow(&self) -> &DataRef<'a> {
DataRef { text: &self.text }
}
}
这显然是不可能的,因为我们不能在某处存储一个DataRef
。
所以我的问题是:
上面的例子有什么方法可以实现
ToOwned
吗?考虑上面的问题,
ToOwned
不应该是用户手动实现的吗?因为我想不出一个真实的例子来反对这一点。(可选答案)如果允许更改 std
ToOwned
的定义,是否有任何可能的改进使其变得更好? (允许不稳定和未实现的 Rust 特性)
您看到的问题是因为 ToOwned
不应该被实施 引用类型 而是 referent。 注意 the standard library implementations 的样子:
impl ToOwned for str
impl ToOwned for CStr
impl ToOwned for OsStr
impl ToOwned for Path
impl<T> ToOwned for [T]
这些都是 !Sized, !Clone
类型,它们总是出现在某些通用指针类型(例如 &str
、Box<str>
、&Path
)或专用指针类型(String
包含一个str
;PathBuf
包含一个Path
;Vec<T>
包含一个[T]
)。 ToOwned
的目的是允许从 reference 转换为数据——不是你所谓的 FooRef
而是实际的 &
—— 到专门的拥有指针类型,这样转换是 可逆和一致的 (这就是 Borrow<Self>
的意义所在)。
如果您想获得 Borrow
和 ToOwned
的好处,您需要定义一个不是引用但引用可以指向的类型,如下所示:
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) }
}
}
但是,请注意此策略仅 适用于单个连续数据块(例如 str
中的 UTF-8 字节)。无法以适用于包含 两个 字符串的 DataOwned
的方式实现 Borrow
+ ToOwned
。 (它可以对一个字符串和一些 fixed-sized 数据完成,但这仍然具有挑战性,因为自定义 dynamically-sized 类型还不是很 well-supported Rust。)
通常不值得为一个 String
包装器做所有这些,但如果你想对内容强制执行一些更强的 type/validity 不变性(例如“所有字符都是ASCII”或“字符串(或字符串切片)是 well-formed JSON 片段”), 和 您希望能够与现有的通用代码进行交互期望 ToOwned
得到实施。
如果您只想能够在 DataRef
上调用 .to_owned()
方法,请不要理会 ToOwned
特征;只需编写一个固有的(non-trait)方法。