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?
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:
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.
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.
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.
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.
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.
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
_ => (),
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With