< 返回版块

祐祐 发表于 2024-08-16 23:52

Tags:可变借用,限制,语法,struct,闭包

救各路大神解答,

目的:我想要做一个解释器,有一个需求就是要从 rust 中的函数绑掉到给解释器用,作为解释器的全域上下文。

问题:我想要让从cx 捞出来的target_func 可以在将cx 传入target_func 作为其上下文,但是我怎实现都做不出来,请问下面这段程式怎么让编译通过。

use std::collections::HashMap;

struct Content {
    pub data: i32,
    pub map_env_fun: HashMap<String, EnvFunction>,
}
impl Content {
    fn new() -> Self {
        Content {
            data: 0,
            map_env_fun: HashMap::new(),
        }
    }
}

type BoxFnMut = Box<dyn FnMut(&mut Content) -> bool>;

enum EnvFunction {
    Func(BoxFnMut),
}

fn __fun(cx: &mut Content) -> bool {
    cx.data += 1;
    true
}

fn init_built_in_functions(cx: &mut Content) {
    let fun: BoxFnMut = Box::new(__fun);
    cx.map_env_fun
        .insert("key_1".to_string(), EnvFunction::Func(fun));
    cx.data += 1;
}

fn main() {
    let mut cx = Content::new();
    init_built_in_functions(&mut cx);

    let EnvFunction::Func(target_func) = cx.map_env_fun.get_mut("key_1").unwrap();
    // --------------------------------------------------------------
    target_func(&mut cx);      // Error:编译失败
    // --------------------------------------------------------------

    println!("Content data: {}", cx.data);
}

Ext Link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=072c17bf78b0cdb99dfa33e408007a7b

评论区

写评论
asuper 2024-08-21 09:25

学习了,提一个小点:

    let EnvFunction::Func(target_func) = cx.map_env_fun
        .get_mut("key_1")
        .map(|f| f.clone())
        .unwrap();

这里cargo clippy 会提示你, map(|f| f.clone()) 可以简化成 map(clone)

wangbyby 2024-08-19 22:05

我之前也遇到过这个问题。 用函数指针解决的。

type FuncPtr = fn(&mut Content) -> bool;
enum EnvFunction {
    Func(FuncPtr),
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=602719b3d787e684de28108660e2af49

elsejj 2024-08-18 22:54

都是些基本的概念,没什么特别的技巧,也无需客气,多看多写,与君共勉。

--
👇
祐祐

作者 祐祐 2024-08-18 16:37

e神您好,这边有点抱歉阿!一个没看清楚的误会~ 再来是真的很感谢你提供的思路,多亏了有你的帮助我已将您提供的方法合并到我的专案代码里了。

如果你这个实现,与我的实现有什么想深入探讨的地方,非常欢迎你的指教。

非常的感谢你(^ _ ^ ~)。

-- 👇 elsejj

作者 祐祐 2024-08-18 16:34

谢谢你提出问题,我竟然没发现弄错了,阿真的太困了~~

指针的实现也是很特别,感谢你提供的想法~

--
👇
small-white0-0

small-white0-0 2024-08-18 08:52

很高兴我的回复对你有帮助。不过,Arc的智能指针的方案,是 elsejj 提出的,我受之有愧。

你的RefCell的方案,也很好。内部可变性配合不可变引用,也是个好思路,学到了。

--
👇
祐祐

作者 祐祐 2024-08-17 15:44

关于你提到的我有想过,这在日后会损失相当大的灵活性,__func 不能完整取用cx: &Content,或许几个月后真的不需要这样的设计会考虑这样的实现。

--
👇
zylthinking

作者 祐祐 2024-08-17 15:30

确实开始很失望,不过看到你的实现,真的非常开心,非常感谢你。

我昨晚上一直都在弄这个,实现了一个RefCell 版本(而外话我弄超久从Rc<RefCell>演变到单个RefCell这样,一直到清晨),刚刚也还在研究你提供的第三个的版本,至于你提供的指标版很有趣本我在研究看看,只是来弄rust 通常就是不想有unsafe 代码所以先不考虑。

我后来跑了两个版本的测试,不是很严谨的基准测试,和你的实现性能相差不远,但你的版本更好。

我也稍微整理一下你提供的范例,然后方便比较~

我也把我的实现也丢上来。

我在研究这两个版本,这两个版本间的差异,是否有什么条件限制。

macro_rules! get_dyn_hashmap_func {
    ($name:ident, $cx:expr, $key:expr) => {
        let map_env_fun = $cx.map_env_fun.borrow();
        let EnvFunction::Func(target_func_1) = map_env_fun.get($key).unwrap();
        let $name = target_func_1;
    };
}

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

struct Content {
    pub data_i32: RefCell<i32>,
    pub data_str: RefCell<String>,
    pub map_env_fun: RefCell<HashMap<String, EnvFunction>>,
}

impl Content {
    fn new() -> Self {
        Content {
            data_str: RefCell::new(String::new()),
            data_i32: RefCell::new(0),
            map_env_fun: RefCell::new(HashMap::new()),
        }
    }
}

type BoxFnMut = Box<dyn Fn(&Content) -> bool>;

enum EnvFunction {
    Func(BoxFnMut),
}

fn __fun_key_1(cx: &Content) -> bool {
    *cx.data_i32.borrow_mut() += 1;
    cx.data_str.borrow_mut().push_str("1(0)..");
    true
}
fn __fun_key_2(cx: &Content) -> bool {
    cx.data_str.borrow_mut().push_str("2(1)..");
    *cx.data_i32.borrow_mut() += 1;
    // {
    //     let map_env_fun = cx.map_env_fun.borrow();
    //     let EnvFunction::Func(target_func_1) = map_env_fun.get("key_1").unwrap();
    //     target_func_1(cx);
    // }

    // 相等以下 =>
    {
        get_dyn_hashmap_func!(target_func_1, cx, "key_1");
        target_func_1(cx);
    }

    cx.data_str.borrow_mut().push_str("2(2)..");
    *cx.data_i32.borrow_mut() += 1;
    true
}

// 由于无法借出只好使用不卫生巨集来搞
// #[inline]
// fn get_dyn_hashmap_func<'a>(cx: &Content) -> BoxFnMut{
//     let map_env_fun = cx.map_env_fun.borrow();
//     let EnvFunction::Func(target_func_1) = map_env_fun.get("key_1").unwrap();
//     Box::new(target_func_1.)
// }

fn init_built_in_functions(cx: &Content) {
    let ls: Vec<(&str, fn(&Content) -> bool)> =
        vec![("key_1", __fun_key_1), ("key_2", __fun_key_2)];

    for (name, fun) in ls {
        // let fun: BoxFnMut = Box::new(fun);
        cx.map_env_fun.borrow_mut().insert(
            name.to_string(),
            EnvFunction::Func(Box::new(fun) as BoxFnMut),
        );
        // *cx.data.borrow_mut() += 1;
    }
}


pub fn run() {
    let ref cx = Content::new();
    init_built_in_functions(cx);

    // let cxx = cx.map_env_fun.borrow();
    // =========================================================
    //
    // if let Some(EnvFunction::Func(target_func_1)) = cxx.get("key_1"){
    //     target_func_1(cx);
    // }
    //
    // -----------  or -------------
    //
    // let EnvFunction::Func(target_func_1) = cxx.get("key_1").unwrap();
    // target_func_1(cx);
    //

    get_dyn_hashmap_func!(target_func_1, cx, "key_1");
    get_dyn_hashmap_func!(target_func_2, cx, "key_2");

    target_func_1(cx);
    target_func_2(cx);
    target_func_1(cx);

    // println!(">>>");
    for _ in 1..2000000 {
        target_func_1(cx);
        target_func_2(cx);
        target_func_1(cx);
    }
    // println!("1. Content data_str: {}", cx.data_str.borrow());

    // println!("2. Content data_i32: {}", cx.data_i32.borrow());
}

// fn main(){
//     run();
// }

这边整理一下你提供的范例方便比较~

use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;

struct Content {
    pub data_i32: i32,
    pub data_str: String,
    pub map_env_fun: HashMap<String, EnvFunction>,
}
impl Content {
    fn new() -> Self {
        Content {
            data_i32: 0,
            data_str: "".into(),
            map_env_fun: HashMap::new(),
        }
    }
}

// 因为会有多个共享,所以要 Rc/Arc
// FnMut 很麻烦,在这种场景应该可以不用
// https://rustcc.cn/article?id=8b6c5e63-c1e0-4110-8ae8-a3ce1d3e03b9

type ARrc<T> = Rc<T>; // Rc<T> or Arc<T>
type ARrcFn = ARrc<dyn Fn(&mut Content) -> bool>;

// 让EnvFunction可以clone
#[derive(Clone)]
enum EnvFunction {
    Func(ARrcFn),
}

fn __fun_key_1(cx: &mut Content) -> bool {
    cx.data_str.push_str("1(0)..".into());
    cx.data_i32 += 1;
    true
}

fn __fun_key_2(cx: &mut Content) -> bool {
    cx.data_str.push_str("2(1)..".into());
    cx.data_i32 += 1;

    let target_func = get_dyn_hashmap_func(cx, "key_1");
    target_func(cx);
    
    cx.data_str.push_str("2(2)..".into());
    cx.data_i32 += 1;
    true
}

#[inline]
fn get_dyn_hashmap_func(cx: &mut Content, func_name: &str) -> ARrcFn {
    // let EnvFunction::Func(target_func) =
    // // 获取到之后,做一个引用计数的 clone
    //     cx.map_env_fun.get_mut("key_2").map(|f| f.clone()).unwrap();
    // let target_func_c = target_func;
    let EnvFunction::Func(target_func) = cx.map_env_fun.get_mut(func_name).unwrap();
    target_func.clone()
}

// fn init_built_in_functions(cx: &mut Content) {
//     let fun_1: ARrcFn = ARrc::new(fun_1);
//     let fun_2: ARrcFn = ARrc::new(fun_2);
//     cx.map_env_fun
//         .insert("key_1".to_string(), EnvFunction::Func(fun_1));
//     cx.map_env_fun
//         .insert("key_2".to_string(), EnvFunction::Func(fun_2));
//     cx.data += 1;
// }

fn init_built_in_functions(cx: &mut Content) {
    let ls: Vec<(&str, fn(&mut Content) -> bool)> =
        vec![("key_1", __fun_key_1), ("key_2", __fun_key_2)];

    for (name, fun) in ls {
        cx.map_env_fun.insert(
            name.to_string(),
            EnvFunction::Func(ARrc::new(fun) as ARrcFn),
        );
    }
}

pub fn run() {
    let mut cx = Content::new();
    init_built_in_functions(&mut cx);

    // get_dyn_hashmap_func(&mut cx, "key_1")(&mut cx);
    // get_dyn_hashmap_func(&mut cx, "key_2")(&mut cx);
    // get_dyn_hashmap_func(&mut cx, "key_1")(&mut cx);

    let target_func_1 = get_dyn_hashmap_func(&mut cx, "key_1");
    let target_func_2 = get_dyn_hashmap_func(&mut cx, "key_2");

    
    target_func_1(&mut cx);
    target_func_2(&mut cx);
    target_func_1(&mut cx);

    // println!(">>>");
    for _ in 1..2000000{
        target_func_1(&mut cx);
        target_func_2(&mut cx);
        target_func_1(&mut cx);
    }
    //println!("1. Content data_str: {}", cx.data_str);

    // println!("2. Content data_i32: {}", cx.data_i32);

}

fn main(){
    run();
}

--
👇
small-white0-0: 首先恢复一下你的信心。rust的借用规则的选择是为了安全性牺牲了部分安全的使用场景。而你的方案涉及了多个借用问题,这个被借用规则拒绝也很正常。方法当然是有的,不过就看你选择是花你的时间还是机器时间了。

解决方法

方案1

高安全但是低性能。(花机器时间)

获取函数的时候,就不要使用引用了,改为使用所有权。我在学异步运行时实现时,看到的教程都是对cx用一次clone一次。所以,你这里只是克隆函数又不上下文cx,问题不大。

方案2

高性能但是低安全。(花费大量自己时间)

使用裸指针,这样就没有了cx的二次借用问题。但是 安全自负!!! 你需要在每一处使用裸指针的地方保证指针有效性。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c65470075352c10dd94083a0fe573fef

方案3

高性能+相对安全。(花微量自己时间)

该方案就是在方案2的基础上封装了一下。限制函数裸指针只会在一处地方使用。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1f82fd96db17e8c2c20930ac589b09cf

zylthinking 2024-08-17 12:00

或者这样:

use std::collections::HashMap;

struct Content {
    pub data: i32,
    pub map_env_fun: HashMap<String, EnvFunction>,
}
impl Content {
    fn new() -> Self {
        Content {
            data: 0,
            map_env_fun: HashMap::new(),
        }
    }
}

type BoxFnMut = Box<dyn FnMut(&mut i32) -> bool>;

enum EnvFunction {
    Func(BoxFnMut),
}

fn __fun(cx: &mut i32) -> bool {
    *cx += 1;
    true
}

fn init_built_in_functions(cx: &mut Content) {
    let fun: BoxFnMut = Box::new(__fun);
    cx.map_env_fun
        .insert("key_1".to_string(), EnvFunction::Func(fun));
    cx.data += 1;
}

#[test]
fn test1() {
    let mut cx = Content::new();
    init_built_in_functions(&mut cx);

    let EnvFunction::Func(target_func) = cx.map_env_fun.get_mut("key_1").unwrap();
    target_func(&mut cx.data);

    println!("Content data: {}", cx.data);
}


zylthinking 2024-08-17 09:51

若这些 builtin method 真的是内置语义, 是不是意味着

  1. 他们是静态的
  2. 他们并没有捕获环境

若是1, 则静态分发就行了, 反正 key_1 也是静态编码的 若是2, 则干脆用函数指针, 这个是 Copy 的, 二次借用自然不存在

两者都否, 再考虑 Rc/Arc 吧

small-white0-0 2024-08-17 08:31

补充:

刚看到下面使用Arc的方案。安全性比裸指针好多了,性能也高。个人建议使用它。unsafe还是能避免就避免好。

small-white0-0 2024-08-17 08:06

首先恢复一下你的信心。rust的借用规则的选择是为了安全性牺牲了部分安全的使用场景。而你的方案涉及了多个借用问题,这个被借用规则拒绝也很正常。方法当然是有的,不过就看你选择是花你的时间还是机器时间了。

解决方法

方案1

高安全但是低性能。(花机器时间)

获取函数的时候,就不要使用引用了,改为使用所有权。我在学异步运行时实现时,看到的教程都是对cx用一次clone一次。所以,你这里只是克隆函数又不上下文cx,问题不大。

方案2

高性能但是低安全。(花费大量自己时间)

使用裸指针,这样就没有了cx的二次借用问题。但是 安全自负!!! 你需要在每一处使用裸指针的地方保证指针有效性。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c65470075352c10dd94083a0fe573fef

方案3

高性能+相对安全。(花微量自己时间)

该方案就是在方案2的基础上封装了一下。限制函数裸指针只会在一处地方使用。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1f82fd96db17e8c2c20930ac589b09cf

elsejj 2024-08-17 08:01
use std::collections::HashMap;
use std::sync::Arc;


struct Content {
    pub data: i32,
    pub map_env_fun: HashMap<String, EnvFunction>,
}
impl Content {
    fn new() -> Self {
        Content {
            data: 0,
            map_env_fun: HashMap::new(),
        }
    }
}

// 因为会有多个共享,所以要 RC/ARC
// FnMut 很麻烦,在这种场景应该可以不用
// https://rustcc.cn/article?id=8b6c5e63-c1e0-4110-8ae8-a3ce1d3e03b9
type BoxFnMut = Arc<dyn Fn(&mut Content) -> bool>;

// 让EnvFunction可以clone
#[derive(Clone)]
enum EnvFunction {
    Func(BoxFnMut),
}

fn __fun(cx: &mut Content) -> bool {
    cx.data += 1;
    true
}

fn init_built_in_functions(cx: &mut Content) {
    let fun: BoxFnMut = Arc::new(__fun);
    cx.map_env_fun
        .insert("key_1".to_string(), EnvFunction::Func(fun));
    cx.data += 1;
}

fn main() {
    let mut cx = Content::new();
    init_built_in_functions(&mut cx);

    // 获取到之后,做一个引用计数的 clone
    let EnvFunction::Func(target_func) = cx.map_env_fun
        .get_mut("key_1")
        .map(|f| f.clone())
        .unwrap();
    target_func(&mut cx);

    println!("Content data: {}", cx.data);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=05ea1ae4e247440add15c413091274bd

作者 祐祐 2024-08-17 01:32

引用一段话 https://course.rs/advance/smart-pointer/cell-refcell.html

RefCell 只是将借用规则从编译期推迟到程序运行期,并不能帮你绕过这个规则 RefCell 适用于编译期误报或者一个引用被在多处代码使用、修改以至于难于管理借用关系时 使用 RefCell 时,违背借用规则会导致运行期的 panic

--
👇
祐祐: 我有试过,也许是我写法问题,但按理来说还是会造成同时可变借用,也就违背 RefCell 规则,导致运行时panic

--
👇
‘static: 单线程的话就看你能不能接受Rc<RefCell>>包起来了,map get 换成 map get+clone

作者 祐祐 2024-08-17 01:28

我有试过,也许是我写法问题,但按理来说还是会造成同时可变借用,也就违背 RefCell 规则,导致运行时panic

--
👇
‘static: 单线程的话就看你能不能接受Rc<RefCell>>包起来了,map get 换成 map get+clone

‘static 2024-08-17 01:16

单线程的话就看你能不能接受Rc<RefCell>>包起来了,map get 换成 map get+clone

作者 祐祐 2024-08-17 00:55

不管怎么想好像似乎无解,真的不行的话可能要全部拆掉? (这很可怕...)...也让我对 rust 的灵活性感到有点失望。

作者 祐祐 2024-08-17 00:52

很有趣,我看到这句 "用了在塞回去" 我整个笑喷。 回归正题。但是这样做性能会损耗太大,我设计解释器是想要让它处理数值密集型的操作。 先不讨论用了后再塞进去的性能消耗,HashMap 的 remove 看着也是很别扭。

--
👇
‘static: 用了在塞回去



fn main() {
    let mut cx = Content::new();
    init_built_in_functions(&mut cx);

    let EnvFunction::Func(mut target_func) = cx.map_env_fun.remove("key_1").unwrap();
    target_func(&mut cx);
      cx.map_env_fun
        .insert("key_1".to_string(), EnvFunction::Func(target_func));
    

    println!("Content data: {}", cx.data);
}

--
👇
祐祐: 因为解释器里面的函数就像是标准程式库一样,所以需要反覆使用直到程式退出。

--
👇
‘static: + 如果不要求map里的函数复用的的话 可以修改成这样

   let EnvFunction::Func(mut target_func) = cx.map_env_fun.remove("key_1").unwrap();
    target_func(&mut cx);
‘static 2024-08-17 00:36

用了在塞回去



fn main() {
    let mut cx = Content::new();
    init_built_in_functions(&mut cx);

    let EnvFunction::Func(mut target_func) = cx.map_env_fun.remove("key_1").unwrap();
    target_func(&mut cx);
      cx.map_env_fun
        .insert("key_1".to_string(), EnvFunction::Func(target_func));
    

    println!("Content data: {}", cx.data);
}

--
👇
祐祐: 因为解释器里面的函数就像是标准程式库一样,所以需要反覆使用直到程式退出。

--
👇
‘static: + 如果不要求map里的函数复用的的话 可以修改成这样

   let EnvFunction::Func(mut target_func) = cx.map_env_fun.remove("key_1").unwrap();
    target_func(&mut cx);
作者 祐祐 2024-08-17 00:33

因为解释器里面的函数就像是标准程式库一样,所以需要反覆使用直到程式退出。

--
👇
‘static: + 如果不要求map里的函数复用的的话 可以修改成这样

   let EnvFunction::Func(mut target_func) = cx.map_env_fun.remove("key_1").unwrap();
    target_func(&mut cx);
1 2 共 21 条评论, 2 页