I got a borrow checking problem when writing code like this:
struct Foo {
v: Vec<i32>,
}
impl Foo {
// the logic in this code makes no sense, just an illustration.
fn f1(&self) -> Option<&[i32]> {
if self.v.len() % 2 == 0 {
Some(self.v.as_slice())
} else {
None
}
}
fn f2(&mut self) {
println!("do some thing with mut self")
}
fn f3(&mut self) -> Option<&[i32]> {
if let Some(result) = self.f1() {
return Some(result);
}
self.f2();
None
}
}
And the error message is:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:22:9
|
16 | fn f3(&mut self) -> Option<&[i32]> {
| - let's call the lifetime of this reference `'1`
17 | let result = self.f1();
| --------- immutable borrow occurs here
18 | if let Some(result) = result {
19 | return Some(result);
| ------------ returning this value requires that `*self` is borrowed for `'1`
...
22 | self.f2();
| ^^^^^^^^^ mutable borrow occurs here
It looks weird to me that when I call self.f2(), this means the result is useless to me, so I don't hold a write reference to self anymore, but apparently the compiler don't think so.
Looks like the typical only-solved-by-polonius kind of problem. I. e. the borrow checker is somewhat limited and rejects code here that is not actually problematic in a memory safety sense. I'm on mobile right now, you can probably easily find recent topics that mention "polonius" and explain more.
For workarounds, this (click the "click to show") is an interesting collection of less or more advanced workarounds, and also an interesting crate in and by itself. Don't use unsafe; if nothing else works well/better, try using that crate itself instead.
error: `mut` must be followed by a named binding
--> src/main.rs:19:9
|
19 | / polonius!(|self| -> Option<&'polonius[i32]> {
20 | | if let Some(result) = self.f1() {
21 | | polonius_return!(Some(result));
22 | | }
23 | | });
| |__________^ help: remove the `mut` prefix: `self`
|
= note: `mut` may be followed by `variable` and `variable @ pattern`
= note: this error originates in the macro `polonius` (in Nightly builds, run with -Z macro-backtrace for more info)
fn f3(&mut self) -> Option<&[i32]> {
let result = self.f1();
polonius!(|result| -> Option<&'polonius[i32]> {
if let Some(result) = result {
polonius_return!(Some(result));
}
});
self.f2();
None
}
The error is:
error[E0277]: the size for values of type `[i32]` cannot be known at compilation time
--> src/main.rs:21:20
|
21 | if let Some(result) = result {
| ^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `[i32]`
note: required by a bound in `Some`
--> /Users/liuyafei/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/option.rs:562:17
|
562 | pub enum Option<T> {
| ^ required by this bound in `Some`
Try let mut this = self; in the beginning of the function, and put the call to f1inside the macro call as well, with a |this| -> signature (and not using self anywhere else other then the definition of this, only use this).
fn f3(&mut self) -> Option<&[i32]> {
let mut this = self;
polonius!(|this| -> Option<&'polonius[i32]> {
let result = this.f();
if let Some(result) = result {
polonius_return!(Some(result));
}
});
this.f2();
None
}
By the way, I'm a little confused:
I found that in one release note, Rust use NLL for borrow checking, what's the relationship between this NLL feature and project polonius ? If they are totally unrelated, does this mean polonius is trying to solve problems which polonius_the_crab tries to do?
NLL is a refinement of the original borrow checker which allowed some things that were previously rejected. Polonius is similarly a prototype borrow checking system which aims to fix even more of the known-to-be-too-strict issues like this one.
That crate uses some well placed unsafe code to workaround some of those known issues in the current borrow checker soundly. "Polonius the crab" is referencing that the crate allows safe[1] code to do some of the things polonius would allow without actually having polonius.
ignoring the unsafe code in the crate obviously âŠī¸
So the Polonius Project is experimental, and it is not in Rust now, but if it is succeeded one day, Rust will probably switch the original borrow checker to Polonius, right?
I mean it is in the compiler, accessible with an unstable command line flag on nightly. But if "Rust" is to be understood as "stable Rust", then - no - it's not in Rust, indeed.
Yeah, it's part of the normal nightly rustc. Technically it's also inside the stable one, but stable Rust deliberately makes unstable features (almost) unusable.