1

I have a two-case enum:

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

use Enum::*;

I want to update my enum in-place, depending on its value. This works at first:

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; }
    }
}

I would like to switch the enum type in some cases:

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;
            }
        }
    }
}

Now the borrow checker is unhappy:

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

In this case, we can work around the problem by assigning to a temporary variable:

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; },
    }
}

But I want to use some of my data in 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 },
        }
    }
}

And now we're in more serious trouble:

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

This time I'm not sure how to fix the problem. It feels like we're piling on hacks when there should be a clean way. What's the idiomatic solution?

playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Joel Burget
  • 1,328
  • 8
  • 17
  • 1
    Is your case one where you could use a match guard? https://play.rust-lang.org/?gist=c0a7a49f5572c4bf441f1554b04e49b0&version=stable – loganfsmyth Jan 12 '18 at 01:57
  • That's a great idea. My actual code has several branches, some of which use subfields, so it doesn't lend itself easily to that solution. Might be possible though. – Joel Burget Jan 12 '18 at 02:07

1 Answers1

4

As of Rust 1.23.0, borrows are always lexical. The mutable reference to x created from this match block:

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;
        }
    }
}

lives until the last } in the code.

Typically, another {} is used to avoid this issue, like:

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

This trick works because it limits the lexical scope. You can do

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


If you use the nightly toolchain and #![feature(nll)], your code almost works. The only difference is that Option::take() must be used.

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
kdy
  • 370
  • 3
  • 14
  • I'd encourage you to move this answer to the duplicate, if you think it's a unique answer. – Shepmaster Jan 12 '18 at 03:39
  • You also [don't need the `unwrap`](https://play.rust-lang.org/?gist=e9dc6d0679789186069d44c8a08d0fc2&version=stable). In the realm of nightly, I'm excited for the [`match_default_bindings` feature](https://play.rust-lang.org/?gist=e0627cbb49aa18665d2953727e2c1925&version=nightly), which makes this even cleaner! – Shepmaster Jan 12 '18 at 04:10
  • How can I do that? I didn't think it's unique. Seems like SO has a rule about duplicated answer? – kdy Jan 13 '18 at 10:49