如何绑定装箱结构体的多个字段而不出现“使用已移动的值”错误?

7
我将尝试编写一个通用的递归数据结构。实际上,当我想要访问所拥有的结构值的多个字段时,我无法进行操作。
我定义了一个将保存列表的结构体:
struct ListNode<T> {
    val: T,
    tail: List<T>
}

struct List<T>(Option<Box<ListNode<T>>>);

空列表用List(None)表示。
我希望能够向列表中添加元素:
impl<T> List<T> {
    fn append(self, val: T) -> List<T> {
        match self {
            List(None) => List(Some(Box::new(ListNode {
                val: val,
                tail: List(None),
            }))),
            List(Some(node)) => List(Some(Box::new(ListNode {
                val: node.val,
                tail: node.tail.append(val),
            }))),
        }
    }
}

这会导致一个容易理解的错误:

error[E0382]: use of moved value: `node`
  --> src/main.rs:17:23
   |
16 |                 val: node.val,
   |                      -------- value moved here
17 |                 tail: node.tail.append(val),
   |                       ^^^^^^^^^ value used here after move
   |
   = note: move occurs because `node.val` has type `T`, which does not implement the `Copy` trait

我在寻找如何使用一个结构体的多个字段时,发现了避免使用具有多个字段的结构体时出现部分移动值错误的方法,所以我会这样做:

List(Some(node)) => {
    let ListNode {
        val: nval,
        tail: ntail,
    } = *node;
    List(Some(Box::new(ListNode {
        val: nval,
        tail: ntail.append(val),
    })))
}

好的,没有,仍然是同样的错误。显然这个链接不再像以前那样工作。

我也尝试使用refs:

List(Some(node)) => {
    let ListNode {
        val: ref nval,
        tail: ref ntail,
    } = *node;
    List(Some(Box::new(ListNode {
        val: *nval,
        tail: (*ntail).append(val),
    })))
}

这次解构过程已成功,但新节点的创建失败,出现以下错误信息:
error[E0507]: cannot move out of borrowed content
  --> src/main.rs:21:26
   |
21 |                     val: *nval,
   |                          ^^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:22:27
   |
22 |                     tail: (*ntail).append(val),
   |                           ^^^^^^^^ cannot move out of borrowed content

我是否漏掉了一些显而易见的东西?如果没有,那么访问未通过引用传递的结构体的多个字段的正确方法是什么?我正在使用 Rust 1.1。

2个回答

6

Box 相关的交互行为有些奇怪。您需要添加一个中间的 let 语句来解开盒子。

List(Some(node)) => {
    let node = *node; // this moves the value from the heap to the stack
    let ListNode { val, tail } = node; // now this works as it should
    List(Some(Box::new(ListNode { val: val, tail: tail.append(value) })))
}

请注意,我将您的函数参数重命名为value,以便我可以使用简短的形式进行解构而无需重命名。

在playground中试一试。


谢谢!这确实有效 :) 不过,为什么 Box 会那样工作呢?有合理的解释吗? - ebvalaim
1
我非常确定这与拆箱解引用是一些编译器魔法有关。 - oli_obk

1

非词法生命周期,自 Rust 2018 开始提供支持,允许您的原始代码直接编译:

struct ListNode<T> {
    val: T,
    tail: List<T>
}

struct List<T>(Option<Box<ListNode<T>>>);

impl<T> List<T> {
    fn append(self, val: T) -> List<T> {
        match self {
            List(None) => List(Some(Box::new(ListNode {
                val: val,
                tail: List(None),
            }))),
            List(Some(node)) => List(Some(Box::new(ListNode {
                val: node.val,
                tail: node.tail.append(val),
            }))),
        }
    }
}

fn main() {}

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