2

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.
nuiun
  • 764
  • 8
  • 19
  • 1
    It looks like your question might be answered by the answers of [Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?](https://stackoverflow.com/q/38023871/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Jul 06 '21 at 14:09
  • @Shepmaster ok thanks. I will look into that one =) – nuiun Jul 06 '21 at 14:11
  • 1
    Why not `fn cut(&mut self) -> Result<&str, ()> { self.cut4()?; self.cut2() }`? – Shepmaster Jul 06 '21 at 14:11
  • 1
    `fn cut(&mut self) -> Result<&str, ()> { self.cut4()?; self.cut2() }` will return `Err(())` before `self.cut2()` is run. Also it will only return the result of `self.cut2()`. The behaviour desired 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. This is a simplified version of the other code I have. I am writing a recursive parser. – nuiun Jul 06 '21 at 14:16
  • 1
    The question should not be closed as dup because the OP responded to the query and edited the answer to clarify that the answers to the previous question do not apply to their situation. – user4815162342 Jul 06 '21 at 20:44

1 Answers1

1

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.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • wow... I am astonished by your marvelous solution. Thankyou very much! Thankyou also for the extra explanations of the other possibilities. I am eager to adapt the solution for the project. Also I hope someone else will find this QA useful. – nuiun Jul 06 '21 at 23:05
  • What is a "silent UB"? – nuiun Jul 06 '21 at 23:06
  • @nuiun "UB" is short for [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior), a situation where an unsafe program does something it is not allowed to, that the compiler rightfully assumes will never happen. The best case is when the UB results in a crash, such as when you try to dereference a null pointer. A silent UB is one that _appears_ to work, but causes corruption or a subtle miscompilation that will cause problems later in a seemingly unrelated part of the program. A program with UB can also work fine with one version of the compiler and break with a different one. – user4815162342 Jul 07 '21 at 07:13