我怎样才能实现std :: convert :: From这样它不会消耗它的输入?

apt*_*002 5 traits rust borrow-checker

我设法使Rust类型检查器进入无限循环.一个非常类似的程序编译没有麻烦.为什么我不想编译的程序?

为了节省您的时间和精力,我已经制作了两个程序的最小版本来隔离问题.当然,最小版本是一个毫无意义的程序.你必须用你的想象力去看我的动力.

成功

让我从有效的版本开始.结构F<T>包装了一个T.该类型Target可以从F<T>提供的T罐中转换.

struct F<T>(T);

impl<T> From<F<T>> for Target where Target: From<T> {
    fn from(a: F<T>) -> Target {
        let b = Target::from(a.0);
        f(&b)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个示例调用者:

fn main() {
    let x = Target;
    let y = F(F(F(x)));
    let z = Target::from(y);
    println!("{:?}", z);
}
Run Code Online (Sandbox Code Playgroud)

运行和打印"Target".

失败

该函数f不使用其参数.如果From转换也没有消耗它的参数,我会更喜欢它,因为类型F<T>可能很昂贵或无法克隆.我可以编写一个自定义特征FromRef,它不同于std::convert::From接受不可变借用而不是拥有值:

trait FromRef<T> {
    fn from_ref(a: &T) -> Self;
}
Run Code Online (Sandbox Code Playgroud)

当然,我最终想要使用From<&'a T>,但通过定义我自己的特性,我可以更清楚地提出我的问题,而不会弄乱生命周期参数.(类型检查器的行为使用相同From<&'a T>).

这是我的实现:

impl<T> FromRef<F<T>> for Target where Target: FromRef<T> {
    fn from_ref(a: &F<T>) -> Target {
        let b = Target::from_ref(&a.0);
        f(&b)
    }
}
Run Code Online (Sandbox Code Playgroud)

这编译.但是,该main()功能不会:

fn main() {
    let x = Target;
    let y = F(F(F(x)));
    let z = Target::from_ref(y);
    println!("{:?}", z);
}
Run Code Online (Sandbox Code Playgroud)

会给出一个巨大的错误消息:

error[E0275]: overflow evaluating the requirement `_: std::marker::Sized`
  --> <anon>:26:13
   |
26 |     let z = Target::from_ref(y);
   |             ^^^^^^^^^^^^^^^^
   |
   = note: consider adding a `#![recursion_limit="128"]` attribute to your crate
   = note: required because of the requirements on the impl of `FromRef<F<_>>` for `Target`
   = note: required because of the requirements on the impl of `FromRef<F<F<_>>>` for `Target`
   = note: required because of the requirements on the impl of `FromRef<F<F<F<_>>>>` for `Target`
etc...
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

更新

随机修好了!

问题是,我忘了实现FromRef<Target>Target.

所以我现在想知道:编译器在想什么?我仍然无法将问题与错误消息联系起来.

Kor*_*nel 2

From您无法避免消耗标准/特征中的输入Into

它们被定义为始终消耗输入。它们的定义将输入和输出指定为拥有的类型,具有不相关的生命周期,因此您甚至不能通过尝试使用引用来“欺骗”。

  • 如果您要返回引用,则可以改为实现AsRef<T>。或者,如果您的类型是薄包装器/智能指针,Deref<T>. 你可以提供方法as_foo()

  • 如果您要返回一个新的(拥有的)对象,则约定是提供to_foo()方法。