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

Rust中如何为子模块设置父结构体裸指针?代码异常求解

Rust中Player子模块互相引用的问题(C++转Rust新手)

我是一名从C转来的Rust新手,英文水平有限请见谅。Player结构体包含多个子模块,这些子模块通常需要互相引用。在C中我们习惯保存指向Player的指针,以便轻松访问其他模块或方法,但我不清楚如何在Rust的安全检查机制下组织这类代码。

我尝试使用裸指针,但仍然失败了。以下是我的代码:

struct Task {
    id: u32,
    status: u32,
}

struct TaskMgr {
    tasks: Vec<Task>,
    pl: *mut Player
}

impl TaskMgr {
    fn get_owner(&mut self) -> &mut Player {
        unsafe {
            &mut *self.pl
        }
    }
    fn submit_task(&mut self, id: u32) {
        unsafe {
            (*self.pl).add_item(1000, 1);
        }
    }
    fn on_add_item(&mut self, id: u32, cnt: u32) {
        println!("task on add item {} {} items len:{}", id, cnt, self.tasks.len());
        self.tasks[0].status =2;
    }
}

struct Item {
    id: u32,
    cnt: u32,
}

struct ItemMgr {
    items: Vec<Item>,
    pl: *mut Player
}

struct Player
{
    task_mgr: TaskMgr,
    item_mgr: ItemMgr,
}

impl Default for Player {
    fn default() -> Self {
        let mut r = Self {
            task_mgr: TaskMgr { tasks: vec![Task { id: 1, status: 0 }], pl: std::ptr::null_mut() },
            item_mgr: ItemMgr { items: vec![Item { id: 1000 , cnt: 1}],pl: std::ptr::null_mut() },
        };
        unsafe {
            r.task_mgr.pl = std::ptr::addr_of_mut!(r);
            r.item_mgr.pl = &mut r;
        }
        r
    }
}

impl Player {
    fn submit_task(&mut self, id: u32) {
        self.task_mgr.submit_task(id);
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        self.task_mgr.on_add_item(id, cnt);
    }

    fn add_item(&mut self, id: u32, cnt: u32) {
        let i = self.item_mgr.items.iter_mut().find(|x| x.id == id);
        if i.is_some() {
            i.unwrap().cnt += 1;
        }
        self.on_add_item(id, cnt);
    }
}

fn main() {
    let mut pl = Player::default();
    pl.submit_task(1);

}

程序输出为:

task on add item 1000 2 items len:486381840784

其中items的长度明显错误,这是为什么?该如何修复?
无论是使用std::ptr::addr_of_mut!(r)还是&mut r都无效,但将指针赋值代码移到default()函数外似乎能正常运行:

let mut pl = Player::default();
unsafe {
    pl.task_mgr.pl = &mut pl as *mut Player;
    pl.item_mgr.pl = &mut pl as *mut Player;
}

但这样会让代码使用起来很麻烦,请问如何在创建Player时就完成裸指针的赋值?


错误原因分析

你在Default实现中给子模块赋值的裸指针指向的是函数内的栈临时变量r。当default()执行完毕返回r时,这个临时变量的内容会被移动到调用者的内存位置(比如main里的pl),原栈地址会被回收或覆盖,导致保存的指针变成悬垂指针。后续通过这个指针访问TaskMgr的tasks时,读取的是无效内存区域,因此得到错误的长度值。

解决方案

方案1:安全的引用计数+弱指针(推荐)

如果不需要多线程,使用Rc<RefCell<Player>>配合Weak<RefCell<Player>>可以完全在Rust安全规则内实现子模块对Player的引用,避免裸指针的不安全操作:

use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Task {
    id: u32,
    status: u32,
}

struct TaskMgr {
    tasks: Vec<Task>,
    pl: Weak<RefCell<Player>>,
}

impl TaskMgr {
    fn submit_task(&mut self, id: u32) {
        // 尝试升级Weak指针为强引用
        if let Some(pl) = self.pl.upgrade() {
            let mut pl = pl.borrow_mut();
            pl.add_item(1000, 1);
        }
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        println!("task on add item {} {} items len:{}", id, cnt, self.tasks.len());
        self.tasks[0].status = 2;
    }
}

struct Item {
    id: u32,
    cnt: u32,
}

struct ItemMgr {
    items: Vec<Item>,
    pl: Weak<RefCell<Player>>,
}

struct Player {
    task_mgr: TaskMgr,
    item_mgr: ItemMgr,
}

impl Player {
    fn new() -> Rc<RefCell<Self>> {
        // 创建Player的引用计数实例
        let pl = Rc::new(RefCell::new(Self {
            task_mgr: TaskMgr { tasks: vec![Task { id: 1, status: 0 }], pl: Weak::new() },
            item_mgr: ItemMgr { items: vec![Item { id: 1000, cnt: 1 }], pl: Weak::new() },
        }));

        // 给子模块设置Weak指针
        let mut pl_mut = pl.borrow_mut();
        pl_mut.task_mgr.pl = Rc::downgrade(&pl);
        pl_mut.item_mgr.pl = Rc::downgrade(&pl);

        pl
    }

    fn submit_task(&mut self, id: u32) {
        self.task_mgr.submit_task(id);
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        self.task_mgr.on_add_item(id, cnt);
    }

    fn add_item(&mut self, id: u32, cnt: u32) {
        if let Some(item) = self.item_mgr.items.iter_mut().find(|x| x.id == id) {
            item.cnt += 1;
        }
        self.on_add_item(id, cnt);
    }
}

fn main() {
    let pl = Player::new();
    pl.borrow_mut().submit_task(1);
}

这种方式通过Weak指针避免了循环引用,完全符合Rust的内存安全规则,无需手动管理裸指针。

方案2:封装裸指针的安全构造

如果你坚持使用裸指针,可以通过封装构造函数来确保指针指向最终的有效内存:

struct Task {
    id: u32,
    status: u32,
}

struct TaskMgr {
    tasks: Vec<Task>,
    pl: *mut Player,
}

impl TaskMgr {
    fn submit_task(&mut self, id: u32) {
        unsafe {
            (*self.pl).add_item(1000, 1);
        }
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        println!("task on add item {} {} items len:{}", id, cnt, self.tasks.len());
        self.tasks[0].status = 2;
    }
}

struct Item {
    id: u32,
    cnt: u32,
}

struct ItemMgr {
    items: Vec<Item>,
    pl: *mut Player,
}

struct Player {
    task_mgr: TaskMgr,
    item_mgr: ItemMgr,
}

impl Player {
    fn new() -> Self {
        // 先创建初始实例,指针设为null
        let mut pl = Self {
            task_mgr: TaskMgr { tasks: vec![Task { id: 1, status: 0 }], pl: std::ptr::null_mut() },
            item_mgr: ItemMgr { items: vec![Item { id: 1000, cnt: 1 }], pl: std::ptr::null_mut() },
        };

        // 此时pl已经是即将返回给调用者的实例,内存位置固定,赋值指针
        unsafe {
            pl.task_mgr.pl = &mut pl;
            pl.item_mgr.pl = &mut pl;
        }

        pl
    }

    fn submit_task(&mut self, id: u32) {
        self.task_mgr.submit_task(id);
    }

    fn on_add_item(&mut self, id: u32, cnt: u32) {
        self.task_mgr.on_add_item(id, cnt);
    }

    fn add_item(&mut self, id: u32, cnt: u32) {
        if let Some(item) = self.item_mgr.items.iter_mut().find(|x| x.id == id) {
            item.cnt += 1;
        }
        self.on_add_item(id, cnt);
    }
}

fn main() {
    let mut pl = Player::new();
    pl.submit_task(1);
}

这里的核心是:new()函数中创建的pl是最终会被调用者持有实例,此时它的内存位置已经确定,赋值的指针不会变成悬垂指针。而之前的Default实现中,临时变量r在返回时会被移动,原地址失效,导致指针无效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:50:54