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

Rust中如何让两个结构体共享引用同一个结构体实例?

问题根因

你的代码存在两个和Rust所有权规则直接相关的核心问题:

  1. 编译报错E0382是因为Widget未实现Copy特征,调用User::new(user_widget)时已经把user_widget的所有权转移给了User实例,后续再执行self.widgets.push(user_widget)就是使用已经被移走的值,违反所有权规则。
  2. 就算你绕开编译错误(比如给Widget加#[derive(Copy, Clone)]),当前设计里User和Market的Vec持有的是两份独立的Widget副本,调用user1.update_count()修改的是User自己持有的副本,Vec里存的Widget值不会有任何变化,完全达不到“跨结构体共享修改同一个值”的目标。

你提到的引用计数智能指针是可行的实现方向,单线程场景下不需要用多线程专属的Arc<T>,用Rc<T>搭配提供内部可变性的RefCell<T>即可;不需要把整个Vec包裹在智能指针里,只需要让Vec和User都持有同一个Rc<RefCell<Widget>>实例的克隆即可——克隆Rc只会增加引用计数,不会复制底层Widget数据。

可行实现方案

方案1:Rc<RefCell> 共享所有权(最匹配你的封装目标)

这个方案下User和Market平等共享Widget的所有权,不需要在操作User时额外持有Market的引用,使用者只需要操作User实例就能修改对应Widget,Market同时也能访问到所有Widget的最新值,完全符合你要的简洁抽象要求。
修改后的可运行代码如下:

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

#[derive(Debug)]
pub struct Widget {
    counter: u16,
}

impl Widget{
    pub fn new() -> Widget {
        Widget { counter: 0 }
    }
}

pub struct Market {
    // Vec持有共享所有权的Widget
    widgets: Vec<Rc<RefCell<Widget>>>
}

impl Market {
    pub fn new() -> Market {
        Market { widgets: Vec::new() }
    }

    pub fn new_user(&mut self) -> User {
        // 创建被Rc<RefCell>包裹的Widget,分配在堆上
        let user_widget = Rc::new(RefCell::new(Widget::new()));
        // 克隆Rc交给User,仅增加引用计数,不复制底层Widget
        let user = User::new(Rc::clone(&user_widget));
        // 原Rc存入Market的Vec
        self.widgets.push(user_widget);
        user
    }
}

pub struct User {
    name: String,
    // User同样持有共享所有权的Widget
    widget: Rc<RefCell<Widget>>
}

impl User {
    pub fn new(user_widget: Rc<RefCell<Widget>>) -> User {
        User {
            name: "User1".to_string(),
            widget: user_widget
        }
    }

    pub fn update_count(&mut self) {
        // 运行时借用检查,拿到Widget的可变引用修改值
        self.widget.borrow_mut().counter +=1;
    }
}


pub fn main() {
    let mut market = Market::new();
    let mut user1 = market.new_user();
    println!("修改前:{:?}", market.widgets.iter().map(|w| w.borrow().counter).collect::<Vec<_>>());
    user1.update_count();
    println!("修改后:{:?}", market.widgets.iter().map(|w| w.borrow().counter).collect::<Vec<_>>());
}

运行输出:

修改前:[0]
修改后:[1]

注意事项:

  • Rc<T>只能用于单线程场景,跨线程共享需要换成Arc<T>,对应的内部可变性要换成Mutex<T>或者RwLock<T>。
  • RefCell<T>的借用检查在运行时执行,如果你同时持有同一个Widget的活跃可变借用和其他借用,程序会直接panic,要避免违反借用规则的写法。
  • 注意不要制造Rc循环引用(比如Widget里再持有指向User的Rc),否则会造成内存泄漏。

方案2:所有权归Market,User存索引句柄(零运行时开销)

如果你不接受智能指针的微量运行时开销,可以把Widget的唯一所有权完全交给Market的Vec管理,User里只存对应Widget在Vec里的usize类型索引,需要修改Widget时通过传入的Market可变引用+索引访问。
这个方案的所有借用检查都在编译期完成,没有额外运行时成本,缺点是调用User的修改方法时必须持有Market的可变引用,API使用上稍微繁琐一点。
核心修改点示例:

// User结构体改存索引
pub struct User {
    name: String,
    widget_idx: usize
}

impl Market {
    pub fn new_user(&mut self) -> User {
        let user_widget = Widget::new();
        self.widgets.push(user_widget);
        // 存入新Widget的索引
        User {
            name: "User1".to_string(),
            widget_idx: self.widgets.len() - 1
        }
    }

    // 把修改逻辑放在Market上,或者让User方法接收&mut Market作为参数
    pub fn update_user_count(&mut self, user: &User) {
        self.widgets[user.widget_idx].counter += 1;
    }
}

这种模式在ECS、游戏引擎这类对性能要求极高的场景很常用,只要你保证Vec里的Widget不会被随便删除(否则索引会失效对应到错误的Widget),稳定性完全没问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 10:39:20