Prettier way to work around borrow checker?


#1

I’m wondering if anyone has a nicer pattern for how to work around the borrow checker in the following sort of pattern match (the nested ifs below). The enum I’m actually caring about is not an Option, otherwise I’d just change the outer if to check is_some, which would be “good enough”. But is there a nicer way to check for a pattern, then do something with something inside the pattern, and then use a mutable reference to the thing that is matched?

playground link

fn needs_mut(_: &mut Option<String>) {
}

fn main() {
  let mut v = Some(String::from("start"));
  if let Some(_) = v {
    if let Some(ref old) = v {
      println!("old x is {}", old);
    }
    needs_mut(&mut v);
  }
}

I believe the “real” solution to this is non-lexical lifetimes, so the compiler notice that the reference to “old” is no longer in use when I call needs_mut. But for now, does anyone have a prettier way to structure something like this?


#2

Could you show an example of how would you write in a prettier way in another language, because I’m not too sure which part you don’t like about this or what you are trying to achieve.


#3

@Botev one might hope that this could work:

fn needs_mut(_: &mut Option<String>) {
}

fn main() {
    let mut v = Some(String::from("start"));
    if let Some(ref old) = v {
        println!("old x is {}", old);
        needs_mut(&mut v);
    }
}

I don’t have a better way than the nested ifs.


#4

“Datasort refinement” could improve on this a bit. This is tracked in rust-lang/rfcs#754 and matching is covered in this proposal.

With datasort refinement, we wouldn’t need a reference to old and a mutable reference to v in scope at the same time. Instead there could be a single reference of type &mut Option::Some through which we can access old but also pass it to needs_mut. Something like:

fn needs_mut(_: &mut Option<String>) {
}

fn main() {
    let mut v = Some(String::from("start"));
    if let ref mut s @ Some(_) = v {
        println!("old x is {}", s.0);
        needs_mut(s);
    }
}

#5

You could also not nest them :slight_smile:

fn needs_mut(_: &mut Option<String>) {
}

fn main() {
    let mut v = Some(String::from("start"));

    if let Some(ref old) = v {
      println!("old x is {}", old);
    }

    if v.is_some() {
        needs_mut(&mut v);
    }
}

but it’s basically the same thing.


#6

Good point. I think I prefer the nesting, though, since then the reader of the code can skip over both at once (more easily) if they are thinking of the case where the pattern is not matched.


#7

Another option, if the needs_mut is something you are designing is to make it Option<&mut String>. Than you can do

fn needs_mut(x: Option<&mut String>) {
    let s = x.unwrap();
    *s = s.to_lowercase();
}

fn main() {
  let mut v = Some(String::from("Start"));
  if let Some(ref mut old) = v {
    println!("old x is {}", old);
    needs_mut(Some(old));
  }
  println!("New x is {:?}", v);
}

But all that really depends on what exactly is the real use case for the function.


#8

Alas, I’m not using Option at all, but rather an enum with several variants and fields. I’d rather not write the equivalent of unwrap for this enum, particularly as I only use this pattern a couple of times (so far) for this particular item.


#9

Ugly ugly… Just an idea.

fn main() {
  let mut v = Some(String::from("start"));
  if if let Some(ref old) = v {
      println!("old x is {}", old);
      true
    } else {
      false
    } {
    needs_mut(&mut v);
  }
}

#10

I knew this day would come :fearful:


#11

Hahah I was thinking about that cause I do it when you have more matches, but thought that is considered ugly as well :smiley:


#12

Use Else
https://play.rust-lang.org/?gist=d5019553beff3476dbf80c19a3d9c0b4&version=stable

A better way is probably to not send a mut Option but the inner instead


#13

@viperscape I think your code inverts the logic of when to call needs_mut.


#14

Oh I think I see what you’re trying to achieve, writing out your enum would have been helpful here. Anyways, you could match your enum, move the value out, and on the needs_mut call rewrap said value with an enum again.

https://play.rust-lang.org/?gist=ecd5ca6c7d09cc453a045a025d97f15d&version=stable


#15

I don’t know what I’m doing but this seems to work on stable:

fn needs_mut(_: &mut Option<String>) {
}

fn main() {
  let v = Some(String::from("start"));
  if let mut x @ Some(_) = v {
    x.as_ref().map(|ref old| println!("old x is {}", old));
    needs_mut(&mut x);
  }
}

So you would need to provide as_ref and map for your enum.