为什么不能将一个已封装(boxed)的结构体作为特质(trait)来借用?

3

给定一个实现trait T的结构体S,为什么Box<S>不能实现Borrow<dyn T>

我本以为下面的代码可以编译通过,但实际上不行:

trait T{}
struct S{}
impl T for S{}

fn f1(s: &S) -> &dyn T {
    s
}

fn f2(s: &Box<S>) -> &dyn T {
    std::borrow::Borrow::borrow(s)
}

为什么f1能编译通过,而f2不能?(在第一种情况下,从&S&dyn T的转换被执行了,而在第二种情况下没有执行)。

2个回答

4
这与类型推断和类型转换的工作方式有关。 Borrow 特质的参数是被借用值的类型,并且类型检查器需要知道它是什么。
如果您只写:
std::borrow::Borrow::borrow(s)

那么,在 Borrow<B> 中的类型B 将从周围的代码中推断出来。在您的情况下,它被推断为 dyn T,因为这是返回值。然而,dyn T 是与 S 完全不同的类型,所以它无法通过类型检查。
一旦类型检查器知道返回值的类型是 &S,那么它就可以将其强制转换为 &dyn T,但您需要提供该信息:
fn f2(s: &Box<S>) -> &dyn T {
    let s: &S = std::borrow::Borrow::borrow(s);
    s
}

或者,更简洁地说:

fn f2(s: &Box<S>) -> &dyn T {
    std::borrow::Borrow::<S>::borrow(s)
}

Sébastien Renauld的回答有效的原因是因为Deref使用的是关联类型而不是类型参数。由于每个类型只能有一个Deref实现,所以类型检查器可以轻松推断出<S as Deref>::Target,并且相关的Target类型是唯一确定的。与此不同的是Borrow,因为Box<S> 可能 实现Borrow<()>, Borrow<i32>, Borrow<Box<Option<Vec<bool>>>>,... 所以你需要更明确地指定你想要的实现方式。


1

&Box<S>并不直接等于Box<&S>,这就是为什么它不能直接编译的原因。

你可以通过取消引用相对容易地修复这个问题,像这样:

use std::ops::Deref;
trait T{}
struct S{}
impl T for S{}

fn f1(s : &S) -> &(dyn T) {
    s
}

fn f2(s : &Box<S>) -> &(dyn T) {
    s.deref()
}

(特质Deref的存在使阅读稍微容易了一些)
调用deref()是在&self上操作的,所以拥有&Box<S>就足以调用它。它只是简单地返回&S,并且由于它实现了T,类型检查通过。

&Box<S>并不直接等同于Box<&S>。但是在这两种情况下,&Box<S>::borrow()的类型实际上都是&S,不是吗? - lovasoa

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