为什么接受Box <mytype>的函数会在接受self的函数运行时抱怨值被移动?

时间:2017-10-06 13:12:29

标签: rust ownership

我有以下代码编译:

pub mod Btree {
    pub struct node {
        pub id: u32,
        pub red: bool,
        pub left: Option<Box<node>>,
        pub right: Option<Box<node>>,
    }

    impl<'a> node {
        pub fn insert(mut node_: Option<Box<node>>, id: u32) -> Option<Box<node>> {
            match node_ {
                None => Some(Box::new(node {
                    id: id,
                    red: true,
                    left: None,
                    right: None,
                })),
                Some(x) => x.insert_(id),
            }
        }

        pub fn insert_(mut self, id: u32) -> Option<Box<node>> {
            self.left = node::insert(self.left, id);
            Some(Box::new(self))
        }
    }
}

当我更改insert_()以使用Box<node>代替时:

pub mod Btree {
    pub struct node {
        pub id: u32,
        pub red: bool,
        pub left: Option<Box<node>>,
        pub right: Option<Box<node>>,
    }

    impl<'a> node {
        pub fn insert(mut node_: Option<Box<node>>, id: u32) -> Option<Box<node>> {
            match node_ {
                None => Some(Box::new(node {
                    id: id,
                    red: true,
                    left: None,
                    right: None,
                })),
                Some(x) => node::insert_(x, id),
            }
        }

        pub fn insert_(mut node_: Box<node>, id: u32) -> Option<Box<node>> {
            node_.left = node::insert(node_.left, id);
            Some(node_)
        }
    }
}

我明白了:

error[E0382]: use of partially moved value: `node_`
  --> src/main.rs:23:13
   |
23 |             node_.left = node::insert(node_.left, id);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^----------^^^^^
   |             |                         |
   |             |                         value moved here
   |             value used here after move
   |
   = note: move occurs because `node_.left` has type `std::option::Option<std::boxed::Box<Btree::node>>`, which does not implement the `Copy` trait

我不明白那是什么。代码非常相似,在这两种情况下都有一个举动。

2 个答案:

答案 0 :(得分:2)

如果你手头有结构,你只能构造一个结构(将非Copy元素移出它)。指向堆上结构的指针是不够的,即使它是一个拥有指针(例如Box)。 Shepmaster's answer更详细地描述了为什么会出现这种情况。

幸运的是,node.leftOption<_>,因此可以轻松解决此问题:Option::take.take()上的Option会为您提供内部值(如果有的话),但不会使用Option,而是将None放在其位置。因此,您可以在调用.take()时暂时使用None来交换Node::insert,然后将其替换为返回值。

pub mod btree {
    pub struct Node {
        pub id: u32,
        pub red: bool,
        pub left: Option<Box<Node>>,
        pub right: Option<Box<Node>>,
    }

    impl Node {
        pub fn insert(node: Option<Box<Node>>, id: u32) -> Option<Box<Node>> {
            match node {
                None => Some(Box::new(Node {
                    id: id,
                    red: true,
                    left: None,
                    right: None,
                })),
                Some(x) => Node::insert_(x, id),
            }
        }

        pub fn insert_(mut node: Box<Node>, id: u32) -> Option<Box<Node>> {
            node.left = Node::insert(node.left.take(), id);
            Some(node)
        }
    }
}

Playground.

(我已重命名BtreeNode以遵循Rust命名约定并删除了<'a>生命周期。)

答案 1 :(得分:2)

Box在Rust中非常特别;它是众所周知的编译器和某些技巧发生。例如,you can move out of a box by dereferencing it。这与此有关。

当你的函数接受self时,它等同于:

pub fn insert_(node: Node, id: u32) -> Option<Box<Node>>

Rust performs a number of dereference steps automatically,因此您的Box<Node> dereferenced仅为Node,然后可以调用该函数。因此,原始代码的扩展形式为:

Some(x) => Node::insert_(*x, id),
pub fn insert_(mut self: Node, id: u32) -> Option<Box<Node>> { /* ... */ }

因为整个结构已经转移到函数中,所以将它拆开并且具有无效的struct成员是安全的。执行此操作时,编译器会跟踪哪些成员有效以及哪些成员无法正确调用(或不调用)其析构函数。

在第二种形式中,整个Box<Node>将传递给函数。当您尝试修改结构的成员时,您试图进入它并删除成员的所有权。您不能拥有您只引用的内容的所有权 - 无法通知引用的 true 所有者哪些部分有效,哪些部分无效。

在这种情况下,Box 不够特别,编译器无法处理此问题。

您可以通过显式移出Box来复制编译器的功能。您还必须在返回时创建新的Box

Some(x) => Node::insert_(x, id),
pub fn insert_(node: Box<Node>, id: u32) -> Option<Box<Node>> {
    let mut node = *node;
    node.left = Node::insert(node.left, id);
    Some(Box::new(node))
}

trentcl's answer以不同的方式解决了“可能无效,可能无效”的问题。 Option::take replaces one valid value with another valid value。如果因任何原因需要运行析构函数,那么一切都将是安全且定义明确的。

您甚至可以use Box<...> as the self parameter type - Box特别!

Some(x) => x.insert_(id),
pub fn insert_(self: Box<Node>, id: u32) -> Option<Box<Node>> {
    let mut node = *self;
    node.left = Node::insert(node.left, id);
    Some(Box::new(node))
}