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 PathBuf
s 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.