You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中同一内存区可变引用与可变指针共存是否为UB?求递归结构处理方案

问题:递归Node结构的无死分支实现与UB问题

我有如下递归结构:

struct Node<T> {
    children: Vec<Node>,
    content: Option<T>
}

我希望该结构不存在“死分支”(即多个节点指向content为None的情况)。为此,我在遍历结构时需要记录最后一个拥有多个子节点的节点。我原本的实现方式如下:

#[derive(Debug)]
struct Node<T> {
    children: Vec<Node<T>>,
    content: Option<T>
}

impl <T> Node<T> {
    fn remove_leftmost(&mut self) {
        // I have `node`, a single mutable reference to my data
        let mut node: &mut Node<T> = self;
        let mut last_crossroads = None;
        while !node.children.is_empty() {
            let mut ptr = std::ptr::null_mut();
            if node.children.len() > 1 {
                // Now I also have `ptr`, a mutable pointer to my data
                ptr = node;
            }
            // But then, I modify `node` so that it references another node
            node = &mut node.children[0];
            if !ptr.is_null() {
                // `ptr` now refers to something that has no mutable reference to it
                // So `last_crossroads` should be ok, becoming the only mutable
                // reference to the precedent node
                last_crossroads = Some(unsafe { ptr.as_mut() }.unwrap());
            }
        }
        if let Some(cross) = last_crossroads {
            cross.children.remove(0);
        } else {
            // Out of the scope of the question
            todo!();
        }
    }
}

fn main() {
    let mut node = Node {
        children: vec![
            Node {
                children: vec![
                    Node {
                        children: vec![
                            Node {
                                children: vec![],
                                content: Some(4)
                            }
                        ],
                        content: None
                    }
                ],
                content: None
            },
            Node {
                children: vec![],
                content: Some(2)
            }
        ],
        content: Some(1)
    };
    node.remove_leftmost();
    println!("{:?}", node);
}

我知道持有同一内存区域的多个可变引用属于Undefined Behaviour(UB),但这种情况是否也适用于可变指针?如果不属于UB,这是否是正确的实现方式?

补充:根据Miri检测,该代码似乎存在UB,请问有什么可行的替代实现方案?


解答

关于可变指针与UB的问题

Rust的内存安全规则同样适用于原始指针:即使使用原始指针,同时存在多个可写的指针/引用指向同一内存区域,且其中至少一个被用于修改,就会触发UB。你的代码中,当把node(可变引用)转换为ptr(可变指针)后,node仍然是有效的可变引用,此时相当于同一内存区域同时存在一个可变引用和一个可变指针,违反了Rust的别名规则,这就是Miri检测出UB的原因。

替代实现方案

我们可以通过遍历过程中只保留当前路径的可变引用,同时记录父节点的分叉状态来避免UB,这里提供一种简洁且安全的实现方案:

#[derive(Debug)]
struct Node<T> {
    children: Vec<Node<T>>,
    content: Option<T>
}

impl<T> Node<T> {
    fn remove_leftmost(&mut self) {
        let mut current = self;
        let mut last_crossroads: Option<&mut Node<T>> = None;

        loop {
            if current.children.len() > 1 {
                // 保存当前分叉节点的可变引用,此时之前的引用被覆盖,不存在别名冲突
                last_crossroads = Some(current);
            }

            // 尝试获取第一个子节点的可变引用,没有则终止遍历
            if let Some(first_child) = current.children.first_mut() {
                current = first_child;
            } else {
                break;
            }
        }

        if let Some(cross) = last_crossroads {
            cross.children.remove(0);
        } else {
            // 处理无分叉节点的场景
            todo!();
        }
    }
}

这个方案的核心逻辑是:每次更新current为子节点的可变引用时,之前的current引用会被自动丢弃,确保同一时间只有一个可变引用指向树中的某个节点,完全符合Rust的安全规则,不会触发UB,同时也能准确记录最后一个分叉节点并完成左子节点的移除操作。


内容的提问来源于stack exchange,提问作者er1t

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 22:30:31