How can we solve this lifetime error occuring when conditionally returning a string slice?

Viewed 104

The desired behaviour is if you can cut off 4 bytes cut and return 4 bytes, else if you can cut off 2 bytes cut and return 2 bytes, else return an error.

The methods cut, cut_alternative and cut_alternative_1 try to achieve this. The compiler error messages are listed below.

Why do all of these example methods fail to compile? In cut_alternative_1 the borrows are separated from each other while the error message is the same.

Can you give a workaround?

Playground link

struct StrWrapper<'s> {
    content: &'s str,
}

impl<'s> StrWrapper<'s> {
    fn cut(&mut self) -> Result<&str, ()> {
        self.cut4()
            .or(self.cut2())
    }

    fn cut_alternative(&mut self) -> Result<&str, ()> {
        if let Ok(part) = self.cut4() {
            Ok(part)
        } else if let Ok(part) = self.cut2() {
            Ok(part)
        } else {
            Err(())
        }
    }

    fn cut_alternative_1(&'s mut self) -> Result<&'s str, ()> {
        {
            let part = self.cut4();
            if part.is_ok() {
                return part;
            }
        }
        {
            let part = self.cut2();
            if part.is_ok() {
                return part;
            }
        }
        Err(())
    }

    fn cut2(&mut self) -> Result<&str, ()> {
        if self.content.len() >= 2 {
            let part = &self.content[..2];
            self.content = &self.content[2..];
            Ok(part)
        } else {
            Err(())
        }
    }

    fn cut4(&mut self) -> Result<&str, ()> {
        if self.content.len() >= 4 {
            let part = &self.content[..4];
            self.content = &self.content[4..];
            Ok(part)
        } else {
            Err(())
        }
    }
}
error[E0499]: cannot borrow `*self` as mutable more than once at a time
   --> src/parser.rs:210:17
    |
208 |       fn cut(&mut self) -> Result<&str, ()> {
    |              - let's call the lifetime of this reference `'1`
209 |           self.cut4()
    |           ----
    |           |
    |  _________first mutable borrow occurs here
    | |
210 | |             .or(self.cut2())
    | |_________________^^^^_______- returning this value requires that `*self` is borrowed for `'1`
    |                   |
    |                   second mutable borrow occurs here

error[E0499]: cannot borrow `*self` as mutable more than once at a time
   --> src/parser.rs:216:34
    |
213 |     fn cut_alternative(&mut self) -> Result<&str, ()> {
    |                        - let's call the lifetime of this reference `'1`
214 |         if let Ok(part) = self.cut4() {
    |                           ---- first mutable borrow occurs here
215 |             Ok(part)
    |             -------- returning this value requires that `*self` is borrowed for `'1`
216 |         } else if let Ok(part) = self.cut2() {
    |                                  ^^^^ second mutable borrow occurs here

error[E0499]: cannot borrow `*self` as mutable more than once at a time
   --> src/parser.rs:230:24
    |
207 | impl<'s> StrWrapper<'s> {
    |      -- lifetime `'s` defined here
...
224 |             let part = self.cut4();
    |                        ---- first mutable borrow occurs here
225 |             if part.is_ok() {
226 |                 return part;
    |                        ---- returning this value requires that `*self` is borrowed for `'s`
...
230 |             let part = self.cut2();
    |                        ^^^^ second mutable borrow occurs here

@Shepmaster commented a link to a similar question: Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?

This question differs in two ways:

  • There are multiple mutable borrows instead of one unmutable and one mutable borrow.
  • I am unable to apply the workarounds in the answer to this problem right now. A workaround would be much appreciated.
1 Answers

The secret sauce in the HashMap solution is the use of contains_key(), which returns a bool that just doesn't carry the lifetime baggage. Likewise, the Vec solution involves using indices instead of slices in strategic places.

In your case the equivalent adaptation requires changing the signatures cut4() and cut2() to return Result<Range<usize>, ()>. Then you'd be able to write something like this:

fn cut(&mut self) -> Result<&str, ()> {
    if let Ok(part) = self.cut4() {
        Ok(&self.content[part])
    } else if let Ok(part) = self.cut2() {
        Ok(&self.content[part])
    } else {
        Err(())
    }
}

fn cut2(&mut self) -> Result<Range<usize>, ()> {
    if self.content.len() >= 2 {
        let part = 0..2;
        self.content = &self.content[2..];
        Ok(part)
    } else {
        Err(())
    }
}

// likewise for cut4

Playground

Another option would be to use interior mutability and make all of the functions accept &self:

use std::cell::Cell;

struct StrWrapper<'s> {
    content: Cell<&'s str>,
}

impl<'s> StrWrapper<'s> {
    fn cut(&self) -> Result<&str, ()> {
        if let Ok(part) = self.cut4() {
            Ok(part)
        } else if let Ok(part) = self.cut2() {
            Ok(part)
        } else {
            Err(())
        }
    }

    fn cut2(&self) -> Result<&str, ()> {
        if self.content.get().len() >= 2 {
            let part = &self.content.get()[..2];
            self.content.set(&self.content.get()[2..]);
            Ok(part)
        } else {
            Err(())
        }
    }

    // likewise for cut4
}

Playground (I was honestly surprised that this compiled, I've never tried to put a reference inside a Cell before.)

Finally, if neither of the above options is palatable for you, there is always unsafe:

fn cut(&mut self) -> Result<&str, ()> {
    // Safety: slices returned by cut4() and cut2() must live at least as
    // long as `self` (because the lifetime of `&str` returned is tied to the
    // lifetime of `&self`). This is the case because cut2() and cut4() are
    // bound by their signatures to return sub-slices of `self`, and we don't
    // modify `self` between a successful call to cut*() and the return.
    unsafe {
        if let Ok((ptr, len)) = self.cut4().map(|s| (s.as_ptr(), s.len())) {
            Ok(std::str::from_utf8(std::slice::from_raw_parts(ptr, len)).unwrap())
        } else if let Ok((ptr, len)) = self.cut2().map(|s| (s.as_ptr(), s.len())) {
            Ok(std::str::from_utf8(std::slice::from_raw_parts(ptr, len)).unwrap())
        } else {
            Err(())
        }
    }
}

Playground

If the unsafe voodoo looks scary, good - it should. The compiler won't help you if you slip up and make a mistake, so that kind of thing should be used only as a last resort. I think the above code is correct because it basically works the same way as the range version, it just converts the slices to their constituent parts after they've been created. But any time someone refactors the code you'll be wondering if a crash or, worse, a silent UB is lurking around the corner.

In case it's not already obvious, my recommendation would be approach #1.

Related