不安全指针损坏

Unsafe pointer becoming corrupted

我试图更好地理解 Rust 中的不安全指针。在下面的代码中,我创建了一棵树,其中每个节点都指向其父节点。当我创建一个叶子并将其作为子节点添加到节点时,一切看起来都很好。一旦我获取该节点并将其添加到另一个节点,叶子的父指针就会损坏。为什么叶子的父指针损坏了,而中间节点的父指针却完好无损?哪些更改会使它起作用?

#[derive(Clone)]
pub struct Shape<'a> {
    pub id: usize,
    pub parent: Option<*mut Group<'a>>,
}

type Node<'a> = Box<Group<'a>>;

#[derive(Clone)]
pub struct Group<'a> {
    pub shape: Shape<'a>,
    pub children: Vec<Node<'a>>,
}

pub struct GroupBuilder<'a> {
    id: usize,
    children: Vec<Node<'a>>,
}

impl<'a> GroupBuilder<'a> {
    pub fn new(id: usize) -> GroupBuilder<'a> {
        GroupBuilder {
            id,
            children: Vec::new(),
        }
    }

    pub fn build(&self) -> Node<'a> {
        unsafe {
            let group: *mut Group = Box::into_raw(Box::new(Group {
                shape: Shape {
                    id: self.id,
                    parent: Option::None,
                },
                children: self.children.clone(),
            }));

            for child in (*group).children.iter_mut() {
                child.shape.parent = Option::Some(group.clone());
            }

            Box::from_raw(group)
        }
    }

    pub fn add_child(&mut self, child: Node<'a>) -> &mut GroupBuilder<'a> {
        self.children.push(child);
        self
    }
}

pub fn main() {
    unsafe {

        // THIS WORKS!
        let leaf = GroupBuilder::new(1).build();

        assert_eq!(leaf.shape.id, 1);
        assert_eq!(leaf.shape.parent, Option::None);

        // THIS WORKS AS WELL!
        let node = GroupBuilder::new(2).add_child(leaf).build();

        assert_eq!(node.shape.id, 2);
        assert_eq!(node.children[0].shape.id, 1);
        assert_eq!((*node.children[0].shape.parent.unwrap()).shape.id, 2);

        // THIS WORKS FOR ROOT -> NODE, BUT BREAKS ON ROOT -> NODE -> X
        let root = GroupBuilder::new(3).add_child(node).build();

        assert_eq!(root.shape.id, 3);
        assert_eq!(root.shape.parent, Option::None);
        assert_eq!(root.children[0].shape.id, 2);
        assert_eq!((*root.children[0].shape.parent.unwrap()).shape.id, 3);
        assert_eq!(root.children[0].children[0].shape.id, 1);

        // THINGS BREAK HERE!
        assert_eq!(
            (*root.children[0].children[0].shape.parent.unwrap())
                .shape
                .id,
            2
        );
    } 
}

我收到以下错误:

Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.54s
     Running `target/debug/playground`
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `93906121591344`,
 right: `2`', src/main.rs:77:9

Rust Playground link.

问题出在 build 中的 self.children.clone()。让我们跟随 step-by-step 创建 root:

时会发生什么
  • 已创建临时 GroupBuilder(我们将其命名为 tmp)。
  • node 移入 tmp.children.
  • tmp.children被克隆,克隆被放入root.children。由于 nodeBox,它也被克隆了。
  • 是临时的,tmp 在语句末尾被删除,所以 tmp.children 被删除,所以 node 被释放,leaf 中的指针变得悬空。

通过允许 build 获得 GroupBuilder 的所有权,我们可以使最终断言成功 - playground(注意我还更改了 add_child 的签名从 &mut Self -> &mut SelfSelf -> Self,因此链接仍然可用。


但是请注意,此代码示例仍被 Miri 视为 UB - 当有指针指向 Box 的内部时,它可能认为您无法移动 Box .这可能是 Miri 的限制,但也有可能你确实做了一些不合理的事情。