改变嵌套枚举的最佳方法是什么?
What's the best way to mutate a nested enum?
我有一个大致类似于这个简化示例的枚举:
use std::collections::BTreeMap;
enum Value {
Null,
Object(BTreeMap<String, Value>)
}
现在我想根据字段名称列表,深入到 Value::Object
并使用给定的字段对其进行初始化。叶字段应始终接收 Value::Null
变体。
我愿意并习惯于做的是:
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!(),
}
}
}
但是,借阅检查器不允许。
有趣的是,有效的是以下更复杂且性能可能较低的实现:
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)
}
}
使用上面的版本,以下所有代码都可以正常运行:
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!(),
}
是否有更好的方法来初始化嵌套枚举或让 set_null_to_how_i_want_it(...)
工作?
如何重现
您可以查看完整示例 on 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
元
➜ 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
借用检查器对名称起作用,因此像您的第一种方法这样的示例不起作用;这可以通过先将值 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!(),
};
}
}
我有一个大致类似于这个简化示例的枚举:
use std::collections::BTreeMap;
enum Value {
Null,
Object(BTreeMap<String, Value>)
}
现在我想根据字段名称列表,深入到 Value::Object
并使用给定的字段对其进行初始化。叶字段应始终接收 Value::Null
变体。
我愿意并习惯于做的是:
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!(),
}
}
}
但是,借阅检查器不允许。
有趣的是,有效的是以下更复杂且性能可能较低的实现:
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)
}
}
使用上面的版本,以下所有代码都可以正常运行:
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!(),
}
是否有更好的方法来初始化嵌套枚举或让 set_null_to_how_i_want_it(...)
工作?
如何重现
您可以查看完整示例 on 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
元
➜ 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
借用检查器对名称起作用,因此像您的第一种方法这样的示例不起作用;这可以通过先将值 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!(),
};
}
}