< 返回版块

祐祐 发表于 2024-09-08 13:54

Tags:可变借用,限制,语法,struct,多层struct嵌套结构

救各大神们指教

我有使用了 toml 的库 程式码依赖

[dependencies]
toml = "0.8.19"

目的:我想写一个简单的 TOML 小工具,当写了 a.b.c 且给了 true 应该会呈现出以下。

[a.b]
c = true

完整程式码如下:

use toml::{Table, Value};

fn edit(mut_table: &mut Table, path: Vec<&str>, value: Value) {
    // 简单的一个操作,path 有 n 个元素 keys 的长度会是 n-1
    // ex: path = ["a", "b", "c"] 则 keys = ["a", "b"]
    let keys = path[..path.len() - 1]
        .into_iter()
        .map(|&x| (*x).to_string());

    // 定义 current 让之后可以定位到内层的树状结构
    let mut current = mut_table;

    // 遍历键路径,逐步定位到最内层的表格
    for key in keys {
        current = match current.get_mut(&key) { // -------------------------------------- - Err 可变引用第一次
            // 如果目标表格中已有该键,则获取其对应的值,并定位到其位置
            Some(Value::Table(t)) => t,

            Some(_) => unimplemented!(), // 假设 path[..n-1] 路径一定是 Table 不会是其他的

            // 如果目标键不存在,则创建一个"新表格",并让这个"新表格"作为其位置
            None => {
                // 在此 key 建立表
                current.insert(key.clone(), Value::Table(Table::new())); // -------- Err 可变引用第二次

                // 取到刚刚的 key 指向的表
                target.get_mut(&key).unwrap().as_table_mut() // ------------------- Err 可变引用第三次
            }
        };
    }
    current.insert(path.last().unwrap().to_string(), value);
}

fn main() {
    let mut t = Table::new();
    let p = "a.b.c".split('.').collect::<Vec<&str>>();
    let v = Value::Boolean(true);
    edit(&mut t, p, v);
    println!("{:?}", t.to_string());
}

讨论: 或许觉得不要用mut_table: &mut Table 去修该,直接用mut_table 所有权去改,可是就算是这样target.get_mut(&key) 这行所取得的一样是可变&mut Table。 而且也不可能总是都获取有所有权吧,就这个例子确实只是去修该资料,也没做超出修改范围的行为,所以用 &mut Table合理吧。

目前是查到可以用 Entry 来解决这个问题,但是个人认为不可能每次都依赖 Entry 要是库中没有提供!不就不能实现这样常见的逻辑了吗。 我喜欢 Entry 的解决逻辑,但不想依赖它,好像没它就办不了事了,可怕! !

for key in keys {
    current = {
        current
        .entry(key)
        .or_insert_with(|| Value::Table(Table::new()))
        .as_table_mut()
        .unwrap()
    };
}

评论区

写评论
Foreverhighness 2024-09-10 15:58

https://github.com/rust-lang/rfcs/pull/216

补一下 RFC, 里面可以看到,以前的 HashMap 是有 find_or_insert_with 这种内部修改的方法的。

Foreverhighness 2024-09-10 15:50

目前是查到可以用 Entry 来解决这个问题,但是个人认为不可能每次都依赖 Entry 要是库中没有提供!不就不能实现这样常见的逻辑了吗。 我喜欢 Entry 的解决逻辑,但不想依赖它,好像没它就办不了事了,可怕! !

Entry API 就是设计出来解决你的问题的。

最早的 HashMap 是没有 Entry API 的,然后社区里的人觉得用着不顺手,不符合人体工程学, 于是集思广益设计了 Entry API, 满足了大伙对 Ergonomic 的追求。

所以正确的态度就应该是去用,去喷其他地方怎么没有这种 API, 怎么没有人体工程学的追求。

zylthinking 2024-09-09 10:15

这是当前 borrowck 的限制, 下一代 borrowck 就是瞄着解决这个问题的, 但啥时候能 stable 就不知道了

--
👇
祐祐: 你好~感谢你的回覆,我再添加一点复杂的细节。

我想表达说在这种情况下是否有更好的作法,从这个例子可以看出来,调用一次contains_key 查询又再调用get_mut 并没有特别好,相同的key 做查询需要两次,每当复杂性提一点(多查询一次key),查询就要多一次,我不是有多要求性能,而是想寻找更好的作法,面对这样的规则约束,是否能有更通用的更漂亮的作法呢。

use toml::{Table, Value};
fn edit(mut_table: &mut Table, path: Vec<&str>, value: Value) -> Option<()> {
    let ref keys = path[..path.len() - 1];
    
    let mut target = mut_table;
    for &key in keys {
        target = {
            if target.contains_key(key) {
                let t = target.get_mut(key)?.as_table_mut()?;
                let ref __prefix = "__prefix".to_string();
                if t.contains_key(__prefix) {
                    let prefix_name = t
                        .get(__prefix)
                        .and_then(|x| Some(format!("{}_{}", x.as_str()?, key)))?;

                    if target.contains_key(&prefix_name) {
                        target.get_mut(&prefix_name.to_string())?.as_table_mut()?
                    } else {
                        let _t = Table::new();
                        target.insert(prefix_name.clone(), Value::Table(_t));
                        target.get_mut(&prefix_name.to_string())?.as_table_mut()?
                    }
                } else {
                    target.get_mut(key)?.as_table_mut()?
                }
            } else {
                target.insert(key.to_string(), Value::Table(Table::new()));
                target.get_mut(key)?.as_table_mut()?
            }
        };
    }
    target.insert(path.last()?.to_string(), value);
    Some(())
}

fn main() {
    let mut t = Table::new();
    t.insert("a".to_string(), {
        let mut _t = Table::new();
        _t.insert("b".to_string(), {
            let mut __t = Table::new();
            __t.insert("__prefix".to_string(), Value::String("new".into()));
            Value::Table(__t)
        });
        Value::Table(_t)
    });
    let p: Vec<&str> = "a.b.c.d.e".split('.').collect();
    let v: Value = Value::Boolean(true);
    edit(&mut t, p, v);
    println!("{}", t.to_string());
}

--
👇
jerryshell

作者 祐祐 2024-09-08 22:24

你好~感谢你的回覆,我再添加一点复杂的细节。

我想表达说在这种情况下是否有更好的作法,从这个例子可以看出来,调用一次contains_key 查询又再调用get_mut 并没有特别好,相同的key 做查询需要两次,每当复杂性提一点(多查询一次key),查询就要多一次,我不是有多要求性能,而是想寻找更好的作法,面对这样的规则约束,是否能有更通用的更漂亮的作法呢。

use toml::{Table, Value};
fn edit(mut_table: &mut Table, path: Vec<&str>, value: Value) -> Option<()> {
    let ref keys = path[..path.len() - 1];
    
    let mut target = mut_table;
    for &key in keys {
        target = {
            if target.contains_key(key) {
                let t = target.get_mut(key)?.as_table_mut()?;
                let ref __prefix = "__prefix".to_string();
                if t.contains_key(__prefix) {
                    let prefix_name = t
                        .get(__prefix)
                        .and_then(|x| Some(format!("{}_{}", x.as_str()?, key)))?;

                    if target.contains_key(&prefix_name) {
                        target.get_mut(&prefix_name.to_string())?.as_table_mut()?
                    } else {
                        let _t = Table::new();
                        target.insert(prefix_name.clone(), Value::Table(_t));
                        target.get_mut(&prefix_name.to_string())?.as_table_mut()?
                    }
                } else {
                    target.get_mut(key)?.as_table_mut()?
                }
            } else {
                target.insert(key.to_string(), Value::Table(Table::new()));
                target.get_mut(key)?.as_table_mut()?
            }
        };
    }
    target.insert(path.last()?.to_string(), value);
    Some(())
}

fn main() {
    let mut t = Table::new();
    t.insert("a".to_string(), {
        let mut _t = Table::new();
        _t.insert("b".to_string(), {
            let mut __t = Table::new();
            __t.insert("__prefix".to_string(), Value::String("new".into()));
            Value::Table(__t)
        });
        Value::Table(_t)
    });
    let p: Vec<&str> = "a.b.c.d.e".split('.').collect();
    let v: Value = Value::Boolean(true);
    edit(&mut t, p, v);
    println!("{}", t.to_string());
}

--
👇
jerryshell

jerryshell 2024-09-08 16:42
use toml::{Table, Value};

fn edit(mut_table: &mut Table, path: Vec<&str>, value: Value) {
    let keys = &path[..path.len() - 1];

    let mut current_table = mut_table;

    for &key in keys {
        let is_contains_key = current_table.contains_key(key);
        if is_contains_key {
            let next_table = current_table.get_mut(key).unwrap().as_table_mut().unwrap();
            current_table = next_table;
        } else {
            let new_table = Table::new();
            current_table.insert(key.to_string(), Value::Table(new_table));
            let next_table = current_table.get_mut(key).unwrap().as_table_mut().unwrap();
            current_table = next_table;
        }
    }

    current_table.insert(path.last().unwrap().to_string(), value);
}

fn main() {
    let mut t = Table::new();
    let p = "a".split('.').collect::<Vec<&str>>();
    let v = Value::Boolean(true);
    edit(&mut t, p, v);
    println!("{:?}", t.to_string());

    let mut t = Table::new();
    let p = "a.b".split('.').collect::<Vec<&str>>();
    let v = Value::Boolean(true);
    edit(&mut t, p, v);
    println!("{:?}", t.to_string());

    let mut t = Table::new();
    let p = "a.b.c".split('.').collect::<Vec<&str>>();
    let v = Value::Boolean(true);
    edit(&mut t, p, v);
    println!("{:?}", t.to_string());
}
1 共 5 条评论, 1 页