Why does a function that accepts a Box<MyType> complain of a value being moved when a function that accepts self works?

谁说胖子不能爱 提交于 2019-12-06 07:09:52

Box is very special in Rust; it is well-known to the compiler and certain tricks happen. For example, you can move out of a box by dereferencing it. This is related to that.

When your function accepts self, it's equivalent to saying:

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

Rust performs a number of dereference steps automatically, so your Box<Node> is dereferenced to just a Node, and then the function can be called. The expanded form of your original code is thus:

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

Because the entire struct has been transferred to the function, it is safe to tear it apart and have invalid struct members. When you do so, the compiler tracks which members are valid and which are not in order to properly call (or not call) their destructors.


In your second form, the entire Box<Node> is passed to the function. When you try to modify a member of the the struct, you are attempting to reach into it and remove ownership of a member. You aren't allowed to take ownership of something that you only have a reference to — there's no way to inform the true owner of the reference which pieces are valid and which are not.

In this case, Box isn't special enough for the compiler to deal with this.

You can replicate what the compiler does by explicitly moving out of the Box. You also have to create the new Box when you return it:

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 solves the "maybe invalid maybe not" problem in a different way. Option::take replaces one valid value with another valid value. If the destructors need to be run for whatever reason, everything will still be safe and well-defined.


You can even use Box<...> as the self parameter typeBox is special!

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))
}

You can only destructure a struct (move non-Copy elements out of it) if you have the struct itself in hand. Having a pointer to the struct on the heap is not sufficient, even if it's an owning pointer (e.g. Box). Shepmaster's answer describes in more detail why this is the case.

Fortunately, node.left is an Option<_>, so there is an easy fix for this: Option::take. .take() on an Option gives you the internal value (if there is one), but without consuming the Option, putting None in its place. So you can use .take() to swap None in temporarily while calling Node::insert, and then replace it with the return value.

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.

(I have renamed Btree and Node to follow Rust naming conventions and removed the <'a> lifetime.)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!