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

Rust无动态内存分配的资源共享所有权实现方法

回答

现有实现情况

标准库没有提供专门适配句柄型资源的共享所有权智能指针。第三方生态中仅在面向无堆嵌入式的no_std场景下有少量同类实现,通用开发场景下几乎不会有人专门使用这类组件——毕竟对于i32这类小尺寸句柄,标准库Rc/Arc的堆分配开销已经微乎其微,完全没有做特殊替换的必要。
这里先澄清一个常见认知误区:不存在所有数据全存在栈上、不需要任何共享存储的共享所有权引用计数指针。多个独立所有者要同步同一个资源的引用计数,必须有一块所有持有者都能访问到的公共内存存放计数值,这块公共存储要么分配在堆上,要么放在全局静态内存区,不存在其他可行方案。
实际上你直接使用Arc<Resource>时,因为Resource内仅包含一个i32字段,堆上存储的内容就是2个usize长度的强/弱引用计数加一个i32句柄,64位系统下算上内存对齐总共仅占24字节,堆分配的开销极小。

最简实现方案

按照你描述的「仅维护引用计数和资源句柄」的需求,有两种逻辑非常简单的实现思路:

方案1:栈上存句柄,堆上存共享计数

这种实现和标准库Arc的逻辑几乎完全一致,只是把句柄从堆上挪到每个指针实例的栈空间中,堆上仅存放引用计数:

use std::ptr::NonNull;

// 堆上分配的共享计数块
struct CountBlock {
    strong: usize,
    weak: usize,
}

struct SharedResource {
    handle: i32,
    counts: NonNull<CountBlock>,
}

impl SharedResource {
    fn new() -> Self {
        let handle = unsafe { create_resource() };
        // 堆上分配计数块,初始强引用计数为1
        let counts = Box::new(CountBlock { strong: 1, weak: 1 });
        Self {
            handle,
            counts: NonNull::new(Box::into_raw(counts)).unwrap(),
        }
    }
}

impl Clone for SharedResource {
    fn clone(&self) -> Self {
        // 克隆时强引用计数加1
        unsafe {
            self.counts.as_ref().strong += 1;
        }
        Self {
            handle: self.handle,
            counts: self.counts,
        }
    }
}

impl Drop for SharedResource {
    fn drop(&mut self) {
        unsafe {
            let counts = self.counts.as_mut();
            counts.strong -= 1;
            if counts.strong == 0 {
                // 强计数归零时销毁资源
                destroy_resource(self.handle);
                // 简化实现省略弱引用逻辑,直接释放计数块
                drop(Box::from_raw(self.counts.as_ptr()));
            }
        }
    }
}

// 跨线程安全实现,单线程场景可去掉这两个trait实现
unsafe impl Send for SharedResource {}
unsafe impl Sync for SharedResource {}

这种方案没有全局状态,逻辑和标准库Arc完全对齐,缺点是每个资源仍需要一次极小的堆分配来存放计数块,相比直接用Arc<Resource>仅省掉了访问句柄时的一次指针解引用,实际性能差异几乎无法感知。

方案2:全局句柄表存计数,栈上仅存句柄

这种方案更贴合你描述的设计,不需要为单个资源做单独堆分配,仅用一个全局映射表维护所有句柄对应的引用计数,每个智能指针实例在栈上仅占一个i32的大小:

use std::collections::HashMap;
use std::sync::Mutex;

// 全局句柄表:key为资源句柄,value为对应强引用计数
static HANDLE_COUNTS: Mutex<HashMap<i32, usize>> = Mutex::new(HashMap::new());

struct SharedResource {
    handle: i32,
}

impl SharedResource {
    fn new() -> Self {
        let handle = unsafe { create_resource() };
        // 新资源初始引用计数为1
        HANDLE_COUNTS.lock().unwrap().insert(handle, 1);
        Self { handle }
    }
}

impl Clone for SharedResource {
    fn clone(&self) -> Self {
        let mut table = HANDLE_COUNTS.lock().unwrap();
        *table.get_mut(&self.handle).unwrap() += 1;
        Self { handle: self.handle }
    }
}

impl Drop for SharedResource {
    fn drop(&mut self) {
        let mut table = HANDLE_COUNTS.lock().unwrap();
        let count = table.get_mut(&self.handle).unwrap();
        *count -= 1;
        if *count == 0 {
            // 计数归零时移除表项、销毁资源
            table.remove(&self.handle);
            // 提前释放锁避免死锁
            drop(table);
            unsafe { destroy_resource(self.handle) };
        }
    }
}

unsafe impl Send for SharedResource {}
unsafe impl Sync for SharedResource {}

这种方案实现逻辑极简,栈上体积最小,但依赖全局可变状态,全局锁在高并发场景下的开销反而可能高于Arc的堆分配开销,仅适合句柄由底层API保证全局唯一的场景。

注:以上两种实现均为原理演示的简化版本,省略了弱引用支持、空值校验、panic安全等边界处理,生产环境直接使用标准库Arc<Resource>是更稳妥的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:06:31