考虑以下示例代码:
#[derive(Clone)]
struct DataRef<'a> {
text: &'a str,
}
#[derive(Clone)]
struct DataOwned {
text: String,
}
Run Code Online (Sandbox Code Playgroud)
我将以这种方式ToOwned实现:DataRef
impl ToOwned for DataRef<'_> {
type Owned = DataOwned;
fn to_owned(&self) -> DataOwned {
DataOwned {
text: self.text.to_owned(),
}
}
}
Run Code Online (Sandbox Code Playgroud)
从字面上看,这句话有道理吧?但也存在一些问题。
第一个问题是,因为ToOwned提供了一个全面的实现:
impl<T> ToOwned for T where T: Clone { ... }
Run Code Online (Sandbox Code Playgroud)
上面的代码会出现编译错误:
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;
Run Code Online (Sandbox Code Playgroud)
好吧,我们可以做出一些妥协。让我们#[derive(Clone)]从 中删除DataRef。(但是,在我的实际情况中我不能这样做,因为这是一个破坏性的改变并且没有意义)
然后是第二个问题,关联类型Owned要求ToOwned 它实现Borrow<Self>。
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;
Run Code Online (Sandbox Code Playgroud)
如果我们按照定义实现Borrowfor :DataOwned
pub trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
...
}
Run Code Online (Sandbox Code Playgroud)
这显然是不可能的,因为我们无法将 a 存储DataRef在某个地方。
所以我的问题是:
ToOwned对于上面的例子,有什么办法可以实现吗?
考虑一下上面的问题,是ToOwned不是应该由用户手动实现呢?因为我无法想象一个真实的例子来反对这一点。
(可选答案)如果允许更改 std 的定义ToOwned,是否有任何可能的改进可以使其更好?(允许不稳定和未实现的 Rust 功能)
Kev*_*eid 13
您看到的问题是因为ToOwned不应该针对引用类型实现,而是针对引用对象实现。请注意标准库实现的样子:
impl ToOwned for str\nimpl ToOwned for CStr\nimpl ToOwned for OsStr\nimpl ToOwned for Path\nimpl<T> ToOwned for [T]\nRun Code Online (Sandbox Code Playgroud)\n这些都是!Sized, !Clone总是出现在某些通用指针类型(例如&str,,,)或专门拥有的指针类型(包含 a ;包含 a ;包含 a )内部Box<str>的所有类型。的目的是允许从对数据 \xe2\x80\x94 的引用(不是您所说的东西,而是实际的\xe2\x80\x94 )转换为专用的拥有指针类型,这样转换是可逆且一致的(这就是它的意义所在)。&PathStringstrPathBufPathVec<T>[T]ToOwnedFooRef&Borrow<Self>
Borrow如果您想获得和的好处ToOwned的好处,则需要定义一个类型,该类型不是引用,但引用可以指向某些内容,如下所示:
use std::borrow::Borrow;\n#[repr(transparent)]\nstruct Data {\n text: str,\n}\n\n#[derive(Clone)]\nstruct DataOwned {\n text: String,\n}\n\nimpl Borrow<Data> for DataOwned {\n fn borrow<\'s>(&\'s self) -> &\'s Data {\n // Use unsafe code to change type of referent.\n // Safety: `Data` is a `repr(transparent)` wrapper around `str`.\n let ptr = &*self.text as *const str as *const Data;\n unsafe { &*ptr }\n }\n}\n\nimpl ToOwned for Data {\n type Owned = DataOwned;\n fn to_owned(&self) -> DataOwned {\n DataOwned { text: String::from(&self.text) }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n但请注意,此策略仅适用于单个连续数据块(例如 a 中的 UTF-8 字节str)。没有办法以适用于包含两个字符串的a 的方式来实现Borrow+ 。(这可以针对一个字符串和一些固定大小的数据完成,但这仍然具有挑战性,因为 Rust 尚未很好地支持自定义动态大小的类型。)ToOwnedDataOwned
通常不值得为String包装器执行所有这些操作,但如果您想对内容强制执行一些更强的类型/有效性不变性(例如“所有字符都是 ASCII”或“字符串(或字符串切片)”,则可能是值得的)是一个格式良好的 JSON 片段”),并且您希望能够与期望ToOwned实现的现有通用代码进行交互。
如果您只想能够调用.to_owned()a 上的方法DataRef,请不要理会该ToOwned特征;只需编写一个固有(非特征)方法即可。