【问题标题】:In-place updating of a Rust enum is awkward [duplicate]Rust 枚举的就地更新很尴尬 [重复]
【发布时间】:2018-01-12 01:40:44
【问题描述】:

我有一个两种情况的枚举:

#[derive(Debug)]
pub enum Enum {
    Str(String),
    Fields { len: u32, opt: Option<String> },
}

use Enum::*;

我想就地更新我的枚举,具体取决于它的值。这首先有效:

pub fn update(x: &mut Enum) {
    match x {
        &mut Str(ref mut s) => { s.push('k'); }
        &mut Fields { ref mut len, ref mut opt } => { *len += 1; }
    }
}

在某些情况下我想切换枚举类型:

pub fn update(x: &mut Enum) {
    match x {
        &mut Str(ref mut s) => { s.push('k'); }
        &mut Fields { ref mut len, ref mut opt } => { 
            if *len < 5 {
                *x = Str(String::from("default"));
            } else {
                *len += 1;
            }
        }
    }
}

现在借用检查器很不高兴:

error[E0506]: cannot assign to `*x` because it is borrowed
  --> src/main.rs:14:15
   |
12 |         &mut Fields { ref mut len, ref mut opt } => { 
   |                       ----------- borrow of `*x` occurs here
13 |           if *len < 5 {
14 |               *x = Str(String::from("default"));
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `*x` occurs here

在这种情况下,我们可以通过分配一个临时变量来解决这个问题:

pub fn update(x: &mut Enum) {
    let mut update_hack: Option<Enum> = None;

    match x {
        &mut Str(ref mut s) => { s.push('k'); }
        &mut Fields { ref mut len, ref mut opt } => { 
            if *len < 5 {
                update_hack = Some(Str(String::from("default")));
            } else {
                *len += 1;
            }
        }
    }

    match update_hack {
        None => {},
        Some(to_return) => { *x = to_return; },
    }
}

但我想使用我在update_hack 中的一些数据。

match x {
    &mut Str(ref mut s) => { s.push('k'); }
    &mut Fields { ref mut len, ref mut opt } => { 
        match opt {
            &mut Some(ref s) => { update_hack = Some(Str(*s)) },
            &mut None => { *len += 1 },
        }
    }
}

现在我们遇到了更严重的麻烦:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:16:62
   |
16 |                 &mut Some(ref s) => { update_hack = Some(Str(*s)) },
   |                                                              ^^ cannot move out of borrowed content

这次我不知道如何解决这个问题。当应该有一个干净的方法时,感觉就像我们正在堆积黑客。什么是惯用的解决方案?

playground

【问题讨论】:

  • 您的情况是否可以使用比赛后卫? play.rust-lang.org/…
  • 这是个好主意。我的实际代码有几个分支,其中一些使用子字段,因此它不容易适用于该解决方案。不过也许是可能的。

标签: rust borrow-checker


【解决方案1】:

从 Rust 1.23.0 开始,借用总是词法的。从此匹配块创建的对 x 的可变引用:

match x {
    &mut Str(ref mut s) => { s.push('k'); }
    &mut Fields { ref mut len, ref mut opt } => { 
        if *len < 5 {
            *x = Str(String::from("default"));
        } else {
            *len += 1;
        }
    }
}

直到代码中的最后一个}

通常,另一个{} 用于避免此问题,例如:

let mut x = String::from("a");
{
    let y = &mut x;
    *y = String::from("b");
}
// & to be explicit
println!("borrows x: {}", &x);

这个技巧有效,因为它限制了词法范围。你可以这样做

pub fn update(x: &mut Enum) {
    let s = match *x {
        Str(ref mut s) => {
            s.push('k');
            return;
        }
        Fields {
            ref mut len,
            ref opt,
        } if *len >= 5 || opt.is_none() =>
        {
            *len += 1;
            return;
        }
        // opt is Some(str)
        // Option.take() is used to get String out of
        // &mut Option<String>
        Fields {
            len: _,
            ref mut opt,
        } => opt.take().unwrap(),
    };

    *x = Str(s);
}

playground


如果您使用 nightly 工具链和 #![feature(nll)],您的代码几乎可以正常工作。唯一的区别是必须使用Option::take()

match *x {
    Str(ref mut s) => {
        s.push('k');
    }
    Fields {
        ref mut len,
        ref mut opt,
    } => {
        if *len < 5 {
            // Use Option::take() if you want to move owned value
            // out of &mut Option<String>
            match opt.take() {
                Some(s) => *x = Str(s),
                None => *len += 1,
            }
        } else {
            *len += 1;
        }
    }
}

https://play.rust-lang.org/?gist=235580d4be1242fc9dd3a7e4ee5fe186&version=nightly

【讨论】:

  • 如果您认为这是一个独特的答案,我建议您将此答案移至副本。
  • 你也don't need the unwrap。在 nightly 领域,我为 match_default_bindings feature 感到兴奋,这让这更加干净!
  • 我该怎么做?我不认为它是独一无二的。似乎 SO 有关于重复答案的规则?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-03
  • 2014-02-26
相关资源
最近更新 更多