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

从容器中移除trait对象:地址转usize的方式是否正确?

用Trait对象地址转usize实现取消订阅的正确性及更优方案

原实现的正确性分析

这种方式能在多数场景下运行,但并不安全。它依赖对象内存地址作为唯一标识,存在两个核心问题:

  • 内存地址可复用:当某个Observer被销毁后,后续分配的新对象可能复用同一个内存地址,会导致unsubscribe误删无关对象。
  • 裸指针转换不安全:self as *const Self as usize属于裸指针操作,绕过了Rust的安全检查机制,不符合Rust的安全编程范式,存在潜在的未定义行为风险。

更安全、符合Rust风格的实现方案

推荐两种惯用实现方式:

方式一:利用Rc的指针比较

因为Subject中存储的是Rc<RefCell<dyn IObserver>>,可以直接通过Rc::ptr_eq判断是否为同一个对象,这是Rust标准库提供的安全指针比较方式:

use std::rc::Rc;
use std::cell::RefCell;

trait IObserver {
    fn update(&mut self, message: &str);
}

struct Observer;

impl IObserver for Observer {
    fn update(&mut self, message: &str) {
        // 实现具体更新逻辑
    }
}

struct Subject {
    observers: Vec<Rc<RefCell<dyn IObserver>>>
}

impl Subject {
    fn unsubscribe(&mut self, observer: &Rc<RefCell<dyn IObserver>>) {
        self.observers.retain(|x| !Rc::ptr_eq(x, observer));
    }

    fn subscribe(&mut self, observer: Rc<RefCell<dyn IObserver>>) {
        self.observers.push(observer);
    }
}

这种方式的优势:

  • 完全安全:Rc::ptr_eq是标准库提供的安全API,不会触发未定义行为。
  • 规避地址复用:只要Rc实例存在,对应对象就不会被销毁,内存地址不会被复用。

方式二:为Observer分配唯一ID

如果场景不适合直接传递Rc,可以给每个Observer分配全局唯一ID,用原子类型保证线程安全(单线程场景可简化为普通整数):

use std::sync::atomic::{AtomicUsize, Ordering};
use std::rc::Rc;
use std::cell::RefCell;

static NEXT_OBSERVER_ID: AtomicUsize = AtomicUsize::new(0);

trait IObserver {
    fn update(&mut self, message: &str);
    fn id(&self) -> usize;
}

struct Observer {
    id: usize,
}

impl Observer {
    fn new() -> Self {
        let id = NEXT_OBSERVER_ID.fetch_add(1, Ordering::Relaxed);
        Self { id }
    }
}

impl IObserver for Observer {
    fn update(&mut self, message: &str) {
        // 实现具体更新逻辑
    }

    fn id(&self) -> usize {
        self.id
    }
}

struct Subject {
    observers: Vec<Rc<RefCell<dyn IObserver>>>
}

impl Subject {
    fn unsubscribe(&mut self, observer: &dyn IObserver) {
        let target_id = observer.id();
        self.observers.retain(|x| x.borrow().id() != target_id);
    }
}

这种方式的优势:

  • 完全脱离内存地址依赖,彻底避免地址复用问题。
  • 灵活性高:即使Observer被移动或销毁(只要ID已记录),也能准确识别目标。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 13:50:26