如何实现std::convert::From,使其不会消耗输入?

10
我成功地让Rust类型检查器进入了一个无限循环。一个非常相似的程序可以顺利编译。为什么我想要的程序不能编译?
为了节省您的时间和精力,我制作了两个程序的最小版本,以便隔离问题。当然,这个最小版本是一个无意义的程序。您需要发挥想象力来看到我的动机。
成功
让我从正常工作的版本开始。结构体>包装了一个T。如果T可以被转换,类型Target可以从F转换而来。
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)
    }
}

这是一个调用示例:

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

这个运行并打印"Target"

失败

函数f不会消耗其参数。我希望From转换也不会消耗其参数,因为类型F<T>可能很昂贵或无法克隆。我可以编写一个自定义特性FromRef,它与std::convert::From不同,它接受一个不可变借用而不是拥有的值:

trait FromRef<T> {
    fn from_ref(a: &T) -> Self;
}

当然,我最终想使用From<&'a T>,但通过定义自己的trait,我可以更清楚地提出我的问题,而不会弄乱生命周期参数。(使用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)
    }
}

这段代码可以编译通过,但是main()函数无法正常运行:

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

开始给出一个巨大的错误消息

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...

我做错了什么吗?

更新

我已经随机修复了它

问题在于我忘记为Target实现FromRef<Target>。因此,我现在想知道编译器在想什么?我仍然无法将问题与错误消息联系起来。

2
可能相关:https://dev59.com/kJrga4cB1Zd3GeqPpJmc - Francis Gagné
看起来合理。虽然我仍然不理解我的情况。 - apt1002
至少有两个人认为某些相似的行为是编译器的错误: https://github.com/rust-lang/rust/issues/34137 - apt1002
1个回答

2
你无法避免在标准的From/Into特质中消耗输入。它们被定义为始终消耗输入。它们的定义指定了输入和输出作为拥有的类型,具有不相关的生命周期,因此即使尝试消耗引用,你也无法“欺骗”。
  • 如果你返回一个引用,你可以实现AsRef<T>。或者如果你的类型是一个薄包装/智能指针,可以使用Deref<T>。你可以提供方法as_foo()

  • 如果你返回一个新的(拥有的)对象,惯例是提供to_foo()方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接