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

Rust中如何实现结构体闭包捕获自身成员可变引用?

Rust中实现命令式状态修改的替代方案

我尝试在Rust中实现一个通过命令映射闭包来修改内部状态的结构体,最初的代码如下:

use std::collections::HashMap;

struct TestStruct {
    map: HashMap<String, Box<FnMut(i32) -> ()>>,
    val: i32
}

impl TestStruct {
    fn new() -> Self {
        let mut ts = TestStruct{ map: Default::default(), val: 0 };
        ts.map.insert(String::from("add"), Box::new(|a| ts.val += a));
        ts.map.insert(String::from("mult"), Box::new(|a| ts.val *= a));
        ts
    }

    fn execute(&mut self, cmd: &str, arg: i32) {
        let f = self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

这段代码无法编译,原因是创建闭包时多次可变借用了ts,违反了Rust的借用规则。

我目前通过两种基于引用计数的方案实现了需求:

基于Rc+RefCell的实现

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

struct TestStruct {
    map: HashMap<String, Box<Fn(i32) -> ()>>,
    val: Rc<RefCell<i32>>
}

impl TestStruct {
    fn new() -> Self {
        let mut map: HashMap<String, Box<Fn(i32) -> ()>> = HashMap::new();
        let val = Rc::new(RefCell::new(0));
        let v1 = val.clone();
        map.insert(String::from("add"), Box::new(move |a| {
            let mut mutator = v1.borrow_mut();
            *mutator += a;
        }));
        let v1 = val.clone();
        map.insert(String::from("mult"), Box::new(move |a| {
            let mut mutator = v1.borrow_mut();
            *mutator *= a;
        }));
        TestStruct{ map, val }
    }

    fn execute(&mut self, cmd: &str, arg: i32) {
        let f = self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

基于Rc+Cell的参考实现

use std::collections::HashMap;
use std::rc::Rc;
use std::cell::Cell;

struct TestStruct {
    map: HashMap<String, Box<Fn(i32) -> ()>>,
    val: Rc<Cell<i32>>
}

impl TestStruct {
    fn new() -> Self {
        let mut map: HashMap<String, Box<Fn(i32) -> ()>> = HashMap::new();
        let val = Rc::new(Cell::new(0));
        let v1 = val.clone();
        map.insert(String::from("add"), Box::new(move |a| v1.set(v1.get() + a)));
        let v1 = val.clone();
        map.insert(String::from("mult"), Box::new(move |a| v1.set(v1.get() * a)));
        TestStruct{ map, val }
    }

    fn execute(&mut self, cmd: &str, arg: i32) {
        let f = self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

想请教是否存在完全不同的更优实现思路?


几种替代实现思路

方案1:用枚举封装操作(最简洁安全)

将命令对应的操作封装成枚举变体,完全避免闭包和引用计数,编译期安全且无运行时开销:

use std::collections::HashMap;

enum Operation {
    Add,
    Multiply,
}

struct TestStruct {
    map: HashMap<String, Operation>,
    val: i32,
}

impl TestStruct {
    fn new() -> Self {
        let mut map = HashMap::new();
        map.insert(String::from("add"), Operation::Add);
        map.insert(String::from("mult"), Operation::Multiply);
        TestStruct { map, val: 0 }
    }

    fn execute(&mut self, cmd: &str, arg: i32) {
        match self.map.get(cmd).unwrap() {
            Operation::Add => self.val += arg,
            Operation::Multiply => self.val *= arg,
        }
    }
}

方案2:让闭包接收可变引用

调整闭包定义,使其接收&mut i32作为参数,创建闭包时不捕获结构体,而是在执行时传递状态的可变引用:

use std::collections::HashMap;

struct TestStruct {
    map: HashMap<String, Box<dyn Fn(&mut i32, i32) -> ()>>,
    val: i32,
}

impl TestStruct {
    fn new() -> Self {
        let mut map = HashMap::new();
        map.insert(String::from("add"), Box::new(|val, a| *val += a));
        map.insert(String::from("mult"), Box::new(|val, a| *val *= a));
        TestStruct { map, val: 0 }
    }

    fn execute(&mut self, cmd: &str, arg: i32) {
        let f = self.map.get(cmd).unwrap();
        f(&mut self.val, arg);
    }
}

方案3:使用unsafe(不推荐)

如果一定要保留闭包捕获原始状态的形式,可以用unsafe绕过借用检查,但会失去Rust的内存安全保障,仅在特殊场景下考虑:

use std::collections::HashMap;
use std::ptr;

struct TestStruct {
    map: HashMap<String, Box<FnMut(i32) -> ()>>,
    val: i32,
}

impl TestStruct {
    fn new() -> Self {
        let mut ts = TestStruct { map: Default::default(), val: 0 };
        let val_ptr = ptr::addr_of_mut!(ts.val);
        
        ts.map.insert(String::from("add"), Box::new(move |a| unsafe {
            *val_ptr += a;
        }));
        ts.map.insert(String::from("mult"), Box::new(move |a| unsafe {
            *val_ptr *= a;
        }));
        
        ts
    }

    fn execute(&mut self, cmd: &str, arg: i32) {
        let f = self.map.get_mut(cmd).unwrap();
        f(arg);
    }
}

注意:此方案存在风险,若结构体被移动,指针会失效导致未定义行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 17:04:52