改变嵌套枚举的最佳方法是什么?

时间:2022-01-14 02:49:14

I have an enum which roughly looks like this simplified example:

我有一个枚举,大致看起来像这个简化的例子:

use std::collections::BTreeMap;

enum Value {
    Null,
    Object(BTreeMap<String, Value>)
}

Now I would like to, based on a list of field names, drill down into a Value::Object and initialize it with the given fields. The leaf field should always receive the Value::Null variant.

现在,我希望根据字段名称列表深入查看Value :: Object并使用给定字段对其进行初始化。叶字段应始终接收Value :: Null变量。

What I would like to, and am used to, do is the following:

我想要和以前做的是以下内容:

fn set_null_to_how_i_want_it(fields: &[&str], mut v: &mut Value) {
    debug_assert!(fields.len() > 0);

    for (fid, field) in fields.iter().enumerate() {
        v = 
            match *v {
                Value::Object(ref mut map) => {
                    let val_to_insert = 
                        if fid == fields.len() - 1 {
                            Value::Null
                        } else {
                            Value::Object(Default::default())
                        };
                    map.entry(field.to_string()).or_insert(val_to_insert)
                }
                _ => unreachable!(),
            }
    }
}

However, the borrow checker will not allow it.

但是,借阅检查员不允许这样做。

Interestingly, what worked is the following, more complex and probably less performant implementation:

有趣的是,有效的是以下,更复杂,可能性能更差的实现:

fn set_null_how_borrow_chk_allows_it(fields: &[&str], v: &mut Value) {
    let next = 
        match *v {
            Value::Object(ref mut map) => {
                let val_to_insert = 
                    if fields.len() == 1 {
                        Value::Null
                    } else {
                        Value::Object(Default::default())
                    };
                map.entry(fields[0].to_string()).or_insert(val_to_insert)
            },
            _ => unreachable!()
        };
    if fields.len() > 1 {
        set_null_how_borrow_chk_allows_it(&fields[1..], next)
    }
}

With the version above, all the following code runs fine:

使用上面的版本,以下所有代码运行正常:

let mut v = Value::Object(Default::default());
let fields = ["foo", "bar", "baz"];
set_null_how_borrow_chk_allows_it(&fields, &mut v);


let mut map_count = 0;
for (fid, field) in fields.iter().enumerate() {
    let next = 
        match v {
            Value::Object(mut map) => {
                map_count += 1;
                map.remove(&field.to_string()).unwrap()
            },
            _ => unreachable!()
        };
    v = next;
}
assert_eq!(map_count, fields.len());
match v {
    Value::Null => (),
    Value::Object(_) => unreachable!(),
}

Is there a better way to initialize a nested enumeration or to get set_null_to_how_i_want_it(...) to work ?

有没有更好的方法来初始化嵌套枚举或让set_null_to_how_i_want_it(...)工作?

How to reproduce

You can have a look at the complete example on github, and play around with it as follows:

您可以查看github上的完整示例,并按如下方式使用它:

git clone https://github.com/Byron/depot
cd depot/src/rust/hello
cargo test --test lang
# edit src/rust/hello/tests/lang.rs for testing

Meta

➜  hello git:(master) ✗ rustc --version --verbose
rustc 1.1.0-nightly (4b88e8f63 2015-05-11) (built 2015-05-12)
binary: rustc
commit-hash: 4b88e8f63eeaf557c916a0a1e73150b028c44c52
commit-date: 2015-05-11
build-date: 2015-05-12
host: x86_64-apple-darwin
release: 1.1.0-nightly

1 个解决方案

#1


The borrow checker works on names, and so some examples like your first approach don’t work; this can be worked around by moving the value v to a different name first and then assigning the new value to v:

借用检查器对名称起作用,所以像你的第一种方法这样的例子不起作用;这可以通过先将值v移动到另一个名称然后将新值分配给v来解决:

fn set_null_to_how_i_want_it(fields: &[&str], mut v: &mut Value) {
    debug_assert!(fields.len() > 0);

    for (fid, field) in fields.iter().enumerate() {
        let tmp = v;
        v = match *tmp {
            Value::Object(ref mut map) => {
                let val_to_insert = 
                    if fid == fields.len() - 1 {
                        Value::Null
                    } else {
                        Value::Object(Default::default())
                    };
                map.entry(field.to_string()).or_insert(val_to_insert)
            }
            _ => unreachable!(),
        };
    }
}

#1


The borrow checker works on names, and so some examples like your first approach don’t work; this can be worked around by moving the value v to a different name first and then assigning the new value to v:

借用检查器对名称起作用,所以像你的第一种方法这样的例子不起作用;这可以通过先将值v移动到另一个名称然后将新值分配给v来解决:

fn set_null_to_how_i_want_it(fields: &[&str], mut v: &mut Value) {
    debug_assert!(fields.len() > 0);

    for (fid, field) in fields.iter().enumerate() {
        let tmp = v;
        v = match *tmp {
            Value::Object(ref mut map) => {
                let val_to_insert = 
                    if fid == fields.len() - 1 {
                        Value::Null
                    } else {
                        Value::Object(Default::default())
                    };
                map.entry(field.to_string()).or_insert(val_to_insert)
            }
            _ => unreachable!(),
        };
    }
}