3

Consider the following code (also available on the playground)

pub trait TextStream {
    fn next_str(&mut self) -> Option<&str>;
}

pub struct FilterTextStream<T: TextStream> {
    inner: T,
    maxlen: usize,
}

impl<T: TextStream> TextStream for FilterTextStream<T> {
    fn next_str(&mut self) -> Option<&str> {
        match self.inner.next_str() {
            None => None,
            Some(txt) if txt.len() <= self.maxlen => {
                Some(txt) // <-- this does not compile
                //Some(unsafe {&*(txt as *const str)}) // <-- this fixes the problem
            }
            _ => self.next_str()
        }
    }
}

The TextStream trait works like an iterator, except that the &str's it yields do not have overlapping lifetimes. FilterTextStream works like a (specialized) filter iterator.

However, this code fails to compile, with the following error:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:18:18
   |
11 |     fn next_str(&mut self) -> Option<&str> {
   |                 - let's call the lifetime of this reference `'1`
12 |         match self.inner.next_str() {
   |               ---------- first mutable borrow occurs here
...
15 |                 Some(txt)
   |                 --------- returning this value requires that `self.inner` is borrowed for `'1`
...
18 |             _ => self.next_str()
   |                  ^^^^ second mutable borrow occurs here

What surprises me is that I don't see how this is borrowing self twice. Either we return in line 15, or we return in line 18, so only one of the two will ever happen. My assumption is that this is nor a real problem, but one of these cases were the borrow-checker is not smart enough. Am I missing something?

Assuming I am right, I managed to circumvent this error using an unsafe block (see the commented-out line 16) by artificially "breaking" the lifetime dependency. Can anyone see a better way (i.e. without any unsafe block) to make this code compile?

Pierre-Antoine
  • 1,915
  • 16
  • 25
  • 1
    I believe this is a [known issue](https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions) with the current implementation of the borrow checker. Unfortunately, I don't know the right workaround for this case. – kmdreko Apr 19 '21 at 17:29
  • @kmdreko thanks for your comment. However, as far as I know, this RFC has been merged in the stable Rust with version [1.36](https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html). So the problem described in your link is probably solved now. But granted, it is similar to the problem I have... – Pierre-Antoine Apr 19 '21 at 20:24
  • @Pierre-Antoine I submitted bug report https://github.com/rust-lang/rust/issues/84361 – Inline Apr 20 '21 at 09:00

1 Answers1

1

According to The Polonius Talk rustc currently treats lifetimes in function signatures as if they are live for the entirety of the function body. So, if I understand currectly, it doesn't matter to rustc that the borrows are on different match arms.

The code type-checks with cargo +nightly rustc -- -Zpolonius which means that when Polonius features are fully integrated the code should type-check without any changes.

I couldn't find a better workaround than using unsafe like in the example in question and adding a comment saying the unsafe can be removed at some point.

Max Heiber
  • 14,346
  • 12
  • 59
  • 97