我有一个很大的枚举,我正在其中修改数据。匹配案例变得难以处理,所以我想将它们提取到一个单独的函数中。然而,由于寿命问题,似乎不可能做到这一点。
这是当前问题的简化示例(Rust Playground 链接):
#[derive(Debug)]
enum Variants {
WithString(String),
WithNum(usize),
}
fn manipulate(v: &mut Variants) {
match v {
Variants::WithString(s) => {
if s.is_empty() {
s.push_str("abc");
} else {
*v = Variants::WithNum(123);
}
}
Variants::WithNum(n) => *v = Variants::WithString(n.to_string()),
}
}
我有一个不是克隆或默认的枚举。让我们详细看看
WithString()
案例:
v: &mut Variants
变量。s: &mut String
变量指向 v
。当 s
借用处于活动状态时,我无法使用外部对象 v
。s
,那么我可以再次访问 v
。我在 else
分支中执行此操作。我认为这是非词汇生命周期(NLL)的一个例子?
我的实际用例更糟糕,因为数据结构是递归的,但我认为这与 MCVE 无关。
当我尝试将
WithString(..)
情况下的代码提取到单独的函数中时,这总是会失败,因为函数生命周期似乎没有表达“当 v
处于活动状态时不能使用 s
”关系:
match v {
// rejected by borrow checker:
// cannot borrow `*v` as mutable more than once at a time
Variants::WithString(s) => manipulate_with_string(v, s),
...
}
// this function doesn't know that "s" points into "v"
fn manipulate_with_string(v: &mut Variants, s: &mut String) {
if s.is_empty() {
s.push_str("abc");
} else {
*v = Variants::WithNum(123);
}
}
有没有办法在不大幅改变数据模型的情况下解决这个问题?
我知道一些解决方案,但都不能令人满意:
就住一个非常大的
match
块。
将整个数据结构传递到提取的函数中,如果它是错误的变体,则会出现恐慌 - 听起来是最合适的解决方案,因为这很容易测试。大致如下:
fn manipulate_with_string(v: &mut Variants) {
let Variants::WithString(s) = v else { unreachable!() };
...
}
放弃尝试在类型系统级别解决此问题,而是通过
RefCell
动态检查此问题。然而,这需要对数据模型进行重大更改,并引入恐慌的可能性。
使用拥有的数据,即使用签名
fn manipulate(v: Variants) -> Variants
。然而,这需要重新架构大量数据流。
制作数据
Default
,以便我可以将其std::mem::take()
放在函数的开头,并在返回之前将其放回原处。例如,这可以通过传递 v: &mut Option<Variants>
来实现。然而,这放弃了一些类型安全性。
使用
unsafe
编写一些智能指针或保护对象,可以安全地表达这种嵌套借用关系。然而,我不相信我能正确地做到这一点。
那么,有没有办法提取这个
manipulate_with_string()
函数,或者我是否坚持使用其中一种解决方法? Rust 是否有任何功能/RFC 可能会在未来对此有所帮助?
您可以将
manipulate_with_string
设为宏:
macro_rules! manipulate_with_string {
($v:expr, $s:expr) => {{
let s = $s; // don't evaluate $s twice.
if s.is_empty() {
s.push_str("abc");
} else {
*$v = Variants::WithNum(123);
}
}};
}
fn manipulate(v: &mut Variants) {
match v {
Variants::WithString(s) => manipulate_with_string!(v, s),
Variants::WithNum(n) => *v = Variants::WithString(n.to_string()),
}
}