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

Rust工作区使用module-manager时Vec意外清空问题求助

解决Rust工作区中module-manager跨包结构体Vec意外释放的问题

问题概述

我开发的module-manager crate允许通过结构体名称在代码任意位置访问结构体,核心逻辑是分配存储空间存储结构体、存储指针并通过解引用实现全局访问。单包环境下运行正常,但在Rust工作区中,当结构体定义在不同成员包时,修改其中一个结构体的Vec会导致另一个结构体的Vec被释放或清空。

复现代码示例:

#[derive(Debug, Default, Module)]
pub struct SomeStruct {
    test_u8: u8,
    test_vec: Vec<u8>,
}

#[derive(Debug, Default, Module)]
pub struct OtherStruct {
    pub test_vec: Vec<u8>,
}

fn main() {
    let bundle = ModuleBundleBuilder::default()
        .add_module::<SomeStruct>()
        .add_module::<OtherStruct>()
        .build()
        .unwrap();

    module_manager::init(bundle);

    let other_struct = get_module_mut::<OtherStruct>();
    other_struct.test_vec = Vec::new();
    other_struct.test_vec.push(2);
    other_struct.test_u8 = 2;

    dbg!(&other_struct.test_vec.as_ptr());
    //[app/src/main.rs:25:5] &other_struct.test_vec.as_ptr() = 0x000058ff06362c50
    get_module_mut::<SomeStruct>().test_vec = Vec::new();
    dbg!(&other_struct.test_vec.as_ptr());
    //[app/src/main.rs:27:5] &other_struct.test_vec.as_ptr() = 0x0000000000000001
    dbg!(&other_struct.test_u8);
    //[app/src/main.rs:30:5] &other_struct.test_u8 = 2
}

核心原因分析

问题出在跨包场景下的类型标识冲突:

  • Rust工作区的每个成员包是独立编译单元,若Module宏仅以类型名作为全局存储的标识键,不同包中同名的结构体(甚至不同名但宏生成的键重复)会共享同一个全局存储位置,导致指针被覆盖。
  • 若全局存储依赖TypeId,但宏或初始化逻辑未正确处理跨包类型的TypeId唯一性(跨包同路径同名类型的TypeId是不同的),会导致结构体指针被错误映射。
  • init函数若未保证全局存储的幂等性,跨包重复初始化会覆盖已存储的结构体指针。

解决方案

1. 基于完整类型路径生成唯一全局标识

修改Module派生宏,生成全局存储键时包含结构体的完整路径(包名+模块路径+类型名),避免跨包类型冲突:

#[proc_macro_derive(Module)]
pub fn derive_module(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    let ty_name = &ast.ident;
    // 生成包含完整路径的唯一键
    let module_key = format!("{}::{}", module_path!(), ty_name);
    // 用该键关联全局存储的结构体指针
    // ... 其余宏代码
}

2. 使用TypeId作为全局存储的索引

利用std::any::TypeId的跨包唯一性,将其作为全局哈希表的key,确保每个类型(即使跨包同名)拥有独立的存储条目:

use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Mutex;

static GLOBAL_MODULES: Mutex<HashMap<TypeId, *mut ()>> = Mutex::new(HashMap::new());

// 注册模块时存储TypeId和指针
pub fn register_module<T: Any>(ptr: *mut T) {
    let mut modules = GLOBAL_MODULES.lock().unwrap();
    modules.insert(TypeId::of::<T>(), ptr as *mut ());
}

// 获取模块时通过TypeId查找
pub fn get_module_mut<T: Any>() -> &'static mut T {
    let modules = GLOBAL_MODULES.lock().unwrap();
    let ptr = modules.get(&TypeId::of::<T>()).unwrap() as *mut T;
    unsafe { &mut *ptr }
}

3. 确保初始化逻辑的幂等性

修改module_manager::init函数,仅在全局存储未初始化时执行初始化,防止跨包重复调用覆盖已有数据:

static INITIALIZED: Mutex<bool> = Mutex::new(false);

pub fn init(bundle: ModuleBundle) {
    let mut initialized = INITIALIZED.lock().unwrap();
    if *initialized {
        return;
    }
    // 执行初始化逻辑
    // ...
    *initialized = true;
}

4. 检查宏生成的静态变量命名

若宏生成了静态变量存储指针,确保变量名包含唯一标识(如类型路径哈希),避免跨包同名冲突:

// 宏中生成唯一静态变量示例
static mut MODULE_#[concat!(stringify!($ty), "_", module_path!())]: Option<*mut $ty> = None;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:04:57