Trait问题:借用的数据逃逸到关联函数之外。

6

我正在尝试实现二叉树。我希望节点数据是分离的,因为有很多不同的方式可以实现它,而对树上的算法应该是通用的,与数据存储方式无关。

但是我遇到了借用检查器的奇怪问题。基本上,当我将 impl<TValue> Display for dyn Tree<TValue> 替换为 impl<TValue> Display for TreeNode<TValue> 时,问题就消失了。但我不知道为什么。Trait 是导致这个问题的原因吗?

我的代码如下:

use std::fmt::{Display, Formatter};

struct TreeNode<TValue> {
    value: TValue,
    left: Option<Box<TreeNode<TValue>>>,
    right: Option<Box<TreeNode<TValue>>>,
}

trait Tree<TValue> {
    fn value(&self) -> &TValue;
    fn left(&self) -> Option<&dyn Tree<TValue>>;
    fn right(&self) -> Option<&dyn Tree<TValue>>;
}

impl<TValue> Display for dyn Tree<TValue>
where
    TValue: Display,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str("(")?;
        Display::fmt(self.value(), f)?;
        f.write_str(", ")?;

        match self.left() {
            Some(ref x) => x.fmt(f)?,
            None => f.write_str("None")?,
        }

        f.write_str(", ")?;

        match self.right().as_ref() {
            Some(x) => x.fmt(f)?,
            None => f.write_str("None")?,
        }

        f.write_str(")")
    }
}

impl<TValue> Tree<TValue> for TreeNode<TValue>
where
    TValue: Display,
{
    fn value(&self) -> &TValue {
        &self.value
    }

    fn left(&self) -> Option<&dyn Tree<TValue>> {
        self.left.as_ref().map(|x| &**x as &dyn Tree<TValue>)
    }

    fn right(&self) -> Option<&dyn Tree<TValue>> {
        self.right.as_ref().map(|x| &**x as &dyn Tree<TValue>)
    }
}

fn main() {
    let tree = Box::new(TreeNode {
        value: 1,
        left: Some(Box::new(TreeNode {
            value: 2,
            left: None,
            right: None,
        })),
        right: Some(Box::new(TreeNode {
            value: 3,
            left: None,
            right: None,
        })),
    }) as Box<dyn Tree<i32>>;

    println!("{}", tree);
}

编译器会打印出:

error[E0521]: borrowed data escapes outside of associated function
  --> src\main.rs:24:15
   |
19 |     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
   |            -----
   |            |
   |            `self` declared here, outside of the associated function body
   |            `self` is a reference that is only valid in the associated function body
   |            let's call the lifetime of this reference `'1`
...
24 |         match self.left() {
   |               ^^^^^^^^^^^
   |               |
   |               `self` escapes the associated function body here
   |               argument requires that `'1` must outlive `'static`

对我来说,这没有意义。函数体中没有捕获此值并试图在此函数范围之外保留它。这是借用检查器的限制吗?

1
这是一个非常令人困惑的诊断,所以我认为无论原因如何都应该提交问题。 - kmdreko
@Bamontan 这不是我想要的。编译器似乎强制我将其变成一个特质对象。我不确定为什么。但如果我在特质本身中不使用 dyn,那么所有其他地方都需要 dyn,而且 dyn 将无法使用,因为 Tree 不是特质对象。这很奇怪。我相信 Rust 的编译器消息中仍然存在许多盲点。 - Mara
不是说动态分派不好,但是有什么阻止你这样做的东西吗:playground - Bamontan
@Bamontan 我不确定为什么这对我没用,你似乎在尝试我也在尝试的事情。我明天会检查一下。在这种情况下,动态分派实际上是不好的,因为它不是零成本抽象。而我的树算法不应该强制在所有东西上使用动态分派。 - Mara
我能看到唯一可能阻碍你的事情是,你想为所有实现了Tree的类型实现Display,但你无法使用静态分发来实现它,因为你不拥有Display特质。请注意,动态方式只为dyn Tree实现Display,而不是所有实现Tree的类型。 - Bamontan
显示剩余2条评论
1个回答

5
编译器需要跳过一些心理障碍来推理你的代码。 原因是当您使用“dyn Tree”(在其中实现)时,约束类型的生命周期默认为“static”。 因此,当您调用“x.fmt(f)”时,“x”类型必须为“static”以实现“fmt”方法,并且向后查找意味着用于获取“x”的“self.left()”中的“self”也必须是“static”。 但它不是,因此出现错误。
简单的解决方法是使用任何生命周期实现“dyn Tree”的“Display”。
impl<'a, TValue> Display for dyn Tree<TValue> + 'a

可以是 for dyn Tree<TValue> + '_'。但我不明白的是,这里的 self 不是 &'static dyn Tree,而是 &'_ (dyn Tree + 'static),所以这应该可以正常工作。 - Chayim Friedman
@ChayimFriedman 因为 .left() 的签名是 fn<'a>(&'a self) -> Option<&'a (dyn Tree<TValue> + 'a)>(展开省略的生命周期),所以即使 self&'a (dyn Tree + 'static).left() 的结果也是 &'a (dyn Tree + 'a)(而不是 'static)。我感叹特质对象的省略生命周期取决于它们被写在哪里。 - kmdreko
哦,对了,我忘记了trait对象是不变的。 - Chayim Friedman

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