如何将带有嵌套借用/NLL 的 Rust 代码提取到函数中

问题描述 投票:0回答:1

我有一个很大的枚举,我正在其中修改数据。匹配案例变得难以处理,所以我想将它们提取到一个单独的函数中。然而,由于寿命问题,似乎不可能做到这一点。

例子

这是当前问题的简化示例(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
  • Rust 借用检查器足够聪明,可以识别出如果我停止使用
    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 可能会在未来对此有所帮助?

rust borrow-checker
1个回答
0
投票

您可以将

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()),
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.