8

I have a Context struct:

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> &str {
        &self.name
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    let name = context.get_name();
    if name == "foo" {
        context.set_foo(4);
    }
}

In a function, I need to first get the name of the context and update foo according to the name I have:

let name = context.get_name();
if (name == "foo") {
    context.set_foo(4);
}

The code won't compile because get_name() takes &self and set_foo() takes &mut self. In other words, I have an immutable borrow for get_name() but I also have mutable borrow for set_foo() within the same scope, which is against the rules of references.

At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.

The error looks like:

error[E0502]: cannot borrow `context` as mutable because it is also borrowed as immutable
  --> src/main.rs:22:9
   |
20 |     let name = context.get_name();
   |                ------- immutable borrow occurs here
21 |     if name == "foo" {
22 |         context.set_foo(4);
   |         ^^^^^^^ mutable borrow occurs here
23 |     }
24 | }
   | - immutable borrow ends here

I'm wondering how can I workaround this situation?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xxks-kkk
  • 2,336
  • 3
  • 28
  • 48
  • 2
    Just as a note, I corrected your `&String` -> `&str` which is the borrow type for `String`. I'm not going to change your method names, but just know that in general the Rust style for getters is just the name of the variable rather than `get_variable`, so `Context::name(&self)` rather than `Context::get_name(&self)`. NBD, just thought you may want to know. – Linear Oct 08 '18 at 20:38
  • @LinearZoetrope Thanks for the comment. Just curious is there any convention in Rust for the setter as well? – xxks-kkk Oct 08 '18 at 20:41
  • I **think** `set_x` is the standard, but I'm not sure. Personally I see `x_mut` which returns a mutable reference to the variable more often. (But obviously there are reasons you may want a setter and not to expose the raw variable). – Linear Oct 08 '18 at 20:42

3 Answers3

13

This is a very broad question. The borrow checker is perhaps one of the most helpful features of Rust, but also the most prickly to deal with. Improvements to ergonomics are being made regularly, but sometimes situations like this happen.

There are several ways to handle this and I'll try and go over the pros and cons of each:

I. Convert to a form that only requires a limited borrow

As you learn Rust, you slowly learn when borrows expire and how quickly. In this case, for instance, you could convert to

if context.get_name() == "foo" {
    context.set_foo(4);
}

The borrow expires in the if statement. This usually is the way you want to go, and as features such as non-lexical lifetimes get better, this option gets more palatable. For instance, the way you've currently written it will work when NLLs are available due to this construction being properly detected as a "limited borrow"! Reformulation will sometimes fail for strange reasons (especially if a statement requires a conjunction of mutable and immutable calls), but should be your first choice.

II. Use scoping hacks with expressions-as-statements

let name_is_foo = {
    let name = context.get_name();
    name == "foo"
};

if name_is_foo {
    context.set_foo(4);
}

Rust's ability to use arbitrarily scoped statements that return values is incredibly powerful. If everything else fails, you can almost always use blocks to scope off your borrows, and only return a non-borrow flag value that you then use for your mutable calls. It's usually clearer to do method I. when available, but this one is useful, clear, and idiomatic Rust.

III. Create a "fused method" on the type

   impl Context {
      fn set_when_eq(&mut self, name: &str, new_foo: i32) {
          if self.name == name {
              self.foo = new_foo;
          }
      }
   }

There are, of course, endless variations of this. The most general being a function that takes an fn(&Self) -> Option<i32>, and sets based on the return value of that closure (None for "don't set", Some(val) to set that val).

Sometimes it's best to allow the struct to modify itself without doing the logic "outside". This is especially true of trees, but can lead to method explosion in the worst case, and of course isn't possible if operating on a foreign type you don't have control of.

IV. Clone

let name = context.get_name().clone();
if name == "foo" {
    context.set_foo(4);
}

Sometimes you have to do a quick clone. Avoid this when possible, but sometimes it's worth it to just throw in a clone() somewhere instead of spending 20 minutes trying to figure out how the hell to make your borrows work. Depends on your deadline, how expensive the clone is, how often you call that code, and so on.

For instance, arguably excessive cloning of PathBufs in CLI applications isn't horribly uncommon.

V. Use unsafe (NOT RECOMMENDED)

let name: *const str = context.get_name();
unsafe{
    if &*name == "foo" {
        context.set_foo(4);
    }
}

This should almost never be used, but may be necessary in extreme cases, or for performance in cases where you're essentially forced to clone (this can happen with graphs or some wonky data structures). Always, always try your hardest to avoid this, but keep it in your toolbox in case you absolutely have to.

Keep in mind that the compiler expects that the unsafe code you write upholds all the guarantees required of safe Rust code. An unsafe block indicates that while the compiler cannot verify the code is safe, the programmer has. If the programmer hasn't correctly verified it, the compiler is likely to produce code containing undefined behavior, which can lead to memory unsafety, crashes, etc., many of the things that Rust strives to avoid.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Linear
  • 21,074
  • 4
  • 59
  • 70
  • With non-lexical lifetimes, you won't need to convert the code to a form that only requires a limited borrow. The OP's code compiles without modification with NLLs enabled, since the minimum time a borrow is required is figured out automatically. – Sven Marnach Oct 08 '18 at 21:07
  • *Rust can have some subtle bugs* — No, *Rust* doesn't have these bugs — your code has **undefined behavior** and every guarantee is thrown out the window. – Shepmaster Oct 08 '18 at 21:20
  • @Shepmaster I mean, IMO it's semantics. The Rust team is trying to work on intuiting when it's not allowed to pass `noalias` which could be considered an ergonomics feature or a bug fix depending on perspective. – Linear Oct 08 '18 at 21:21
  • Maybe a better phrasing would be "the compiler can introduce unexpected subtle bugs into the compiled code" – Linear Oct 08 '18 at 21:23
  • It's certainly not "undefined behavior" in that it's *deliberately* left undefined by a spec writer. It's merely undefined-by-difficulty-of-solving, and they're trying to solve it as much as possible. I consider it a different situation than, say, panicking over an FFI boundary which is undefined because of differences in OSs and such. – Linear Oct 08 '18 at 21:25
  • Thanks for this answer. I still fight with the borrow checker. In my actual program, there is one more layer of indirection between get and set: I get the name of the context and feed it into a function and I update `foo` based on the function output. None of the methods you mentioned seem working for me :( I think I simplify my program a little bit too much. – xxks-kkk Oct 08 '18 at 21:33
  • 1
    My point is that if your code uses raw pointers to introduce mutable aliasing, that is *your code's fault*. There is no bug in the compiler, there is a bug in the code fed into the compiler. This code would erroneously claim "this code upholds the guarantees required of a valid Rust program, just trust me". The compiler does not *introduce* anything, it trusts the code as it is instructed. – Shepmaster Oct 08 '18 at 21:37
  • @zack Well, updated your question with a better [mcve] ! – Stargateur Oct 08 '18 at 21:47
  • @Shepmaster Are you happier with the new wording? – Linear Oct 08 '18 at 22:05
  • @zack These methods should work, it's a matter of exact structure and will require modification. it's going to be hard to help without a playground with an example. From the sounds of it, you can probably get around it with `if my_fn(context.get_name()) { ... }` – Linear Oct 08 '18 at 22:07
  • @Shepmaster I read a few times that raw pointers are allowed to alias, and that Rust does not have the same kind of strict aliasing rules that C++ compilers have. Of course this may lead to data races, but my understanding has been that mutable pointer aliasing isn't by itself undefined behaviour. I may be wrong, though. – Sven Marnach Oct 08 '18 at 22:07
  • Here's a quote from the [old version of the book](https://doc.rust-lang.org/book/first-edition/raw-pointers.html): "For example, * pointers are allowed to alias, allowing them to be used to write shared-ownership types." I found similar statements in blog posts by lang-team members and other places. – Sven Marnach Oct 08 '18 at 22:11
  • @SvenMarnach So, mutable pointer aliasing isn't strictly disallowed, it's when you convert a mutable pointer to a reference when passing into something like an `fn(a: &mut T, b: &T)` where `a` and `b` are the same address space. When compiling the function, it assumes that the references follow normal Rust rules, which means no aliasing. Basically, if you're dealing with functions that take pointers, or using the pointers within the same code it's allowed, but you can easily footgun the second you start converting back to references (which you need to for a lot of Rust). – Linear Oct 08 '18 at 22:11
  • @LinearZoetrope It's clear that you need to keep up Rust's rules for references, but you are allowed to have multiple `*mut` pointers to the same memory. Wouldn't that also be "mutable pointer aliasing"? – Sven Marnach Oct 08 '18 at 22:16
  • Well, I saw your update to your answer – I guess we are on the same page. Thanks for replying! – Sven Marnach Oct 08 '18 at 22:18
  • @SvenMarnach yes, *pointers* are allowed to mutably alias each other, *references* are not. Notably, you convert a pointer to a reference whenever you try to write to it (e.g. `unsafe { *foo = 0 }` or when you explicitly convert (`let x: &mut _ = &mut *foo;`). This answer talks about "pointer aliasing" which isn't a concern of Rust's, then suggests that the compiler introduces bugs, which is just not valid. That's like saying that a C compiler "introduces bugs" when you write code that dereferences NULL. – Shepmaster Oct 08 '18 at 22:48
  • I'm not sure how you want me to word this, @Shepmaster. It's incredibly subtle behavior that a proper understanding of just takes more explanation than is appropriate within the answer. It's not strictly correct, no, but my wording gets the gist across for someone new enough to the language that they need 101 tips on appeasing the borrow checker. – Linear Oct 08 '18 at 23:05
  • I've made edits which I believe appropriately cover the problem with unsafe code. – Shepmaster Oct 08 '18 at 23:14
  • @LinearZoetrope Thanks for the comment. I'll try to experiment with your suggestion later today. I'm using the `#![feature(nll)]` suggested in the other answer. I have a playground setup but it seems working. However, I'm curious whether I need to add `#![feature(nll)]` for every `.rc` may involve (my actual implementations are in the separate crate)? Also, does `#![feature(nll)]` work fine with FFI functions as well (i.e. rust definitions from bindgen)? – xxks-kkk Oct 09 '18 at 05:30
4

There is problably some answer that will already answer you, but there is a lot of case that trigger this error message so I will answer your specific case.

The easier solution is to use #![feature(nll)], this will compile without problem.

But you could fix the problem without nll, by using a simple match like this:

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    match context.get_name() {
        "foo" => context.set_foo(4),
        // you could add more case as you like
        _ => (),
    }
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 1
    I'm not sure I'd bring in feature gates for something this simple unless you're already working in nightly. Also, purely IMO, due to the `_ => (),` case this really is more of an `if` situation than a `match` one though that's a preference thing. – Linear Oct 08 '18 at 20:32
  • @LinearZoetrope Well, that really opinion-based, nll require nightly but that not "unsafe". I just said that it was the simplest solution (and also to said that the code itself is ok just that compiler without nll don't allow it). And yes if... only one test is require a if is more fast to write than a match. There is always a lot of solution to one problem. I didn't claim to have list all of them, primary because I don't know all of them ;) – Stargateur Oct 08 '18 at 20:39
  • @LinearZoetrope no feature gates needed, just use Beta and edition 2018. NLL is on by default there and will be the default come December and edition 2018. – Shepmaster Oct 08 '18 at 23:17
2

Prior to seeing @Stargateur's comment, I came up with the below which compiles fine, but does clone the name string:

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> String {
        self.name.clone()
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: String::from("bill"),
        foo: 5,
    };

    let name = context.get_name();
    if name == "foo" {
        context.set_foo(4);
    }
    println!("Hello World!");
}

Working with @Stargateur's sample, it turns out there is a surprisingly simple solution to this particular problem - combine with the get_name with the if, e.g.

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> &String {
        &self.name
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    if context.get_name() == "foo" {
        context.set_foo(4);
    }
}

I believe that this is because the variable for the get_name part has its lifetime clearly delineated, whereas when the name variable was separate, it essentially could have its value suddenly changed without an explicit mutation.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jarak
  • 972
  • 9
  • 16