考虑以下示例代码:

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

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

我将以这种方式实施ToOwnedDataRef:

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中go 掉#[derive(Clone)].(然而,在我的真实 case 中,我不能这样做,因为这是一个突破性的改变,没有意义)


然后是第二个问题,它实现Borrow<Self>ToOwned requires的相关类型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不应该被实现for a reference type,但是对于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种类型,总是出现在一些通用指针类型(例如&strBox<str>&Path)或专用指针类型(StringPathBufVec)中.ToOwned的目的是允许从reference到数据(不是您称之为FooRef的数据,而是实际的&)转换为专用指针类型,转换的方式是reversible and consistent(这就是Borrow<Self>的意义).

如果你想获得BorrowToOwned的好处,你需要定义一个类型,它不是一个引用,而是一个引用可以指向的东西,比如:

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) }
    }
}

然而,请注意,该策略only适用于单个连续数据块(例如str中的UTF-8字节).对于包含two个字符串的DataOwned来说,没有办法实现Borrow+ToOwned.(这可以针对一个字符串和一些固定大小的数据进行,但这仍然是一个挑战,因为Rust还不太支持自定义的动态大小的类型.)

对于String包装器来说,通常不值得这么做,但如果您想要对内容实施更强大的类型/有效性不变性(例如,"所有字符都是ASCII"或"字符串(或字符串片段)是格式良好的JSON片段"),and您可能需要能够与预期实现ToOwned的现有通用代码交互.

如果你只想在DataRef上调用一个.to_owned()方法,那么就不用担心ToOwned特性;只需写一个固有的(非trait )方法.

Rust相关问答推荐

如何在Rust中在屏幕中间打印内容?

制作一片连续整数的惯用Rust 方法?

在rust sqlx中使用ilike和push bind

在IntoIter上调用.by_ref().Take().rev()时会发生什么情况

返回Result<;(),框<;dyn错误>>;工作

考虑到Rust不允许多个可变引用,类似PyTorch的自动区分如何在Rust中工作?

为什么某些类型参数仅在特征边界中使用的代码在没有 PhantomData 的情况下进行编译?

需要哪些编译器优化来优化此递归调用?

存储返回 impl Trait 作为特征对象的函数

Rust 文件未编译到 dll 中

在描述棋盘时如何最好地使用特征与枚举

Rust 生命周期:这两种类型声明为不同的生命周期

如果不满足条件,如何在 Rust 中引发错误

将 Futures 的生命周期特征绑定到 fn 参数

使用 `clap` 在 Rust CLI 工具中设置布尔标志

判断 is_ok 后重用结果

只有一个字符被读入作为词法分析器的输入

意外的正则表达式模式匹配

火箭整流罩、tokio-scheduler 和 cron 的生命周期问题

Rust:如果我知道只有一个实例,那么将可变borrow 转换为指针并返回(以安抚borrow 判断器)是否安全?