我们如何解决有条件地返回字符串切片时发生的生命周期错误?

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

期望的行为是,如果你可以切断 4 个字节和 return 4 个字节,否则如果你可以切断 2 个字节和 return 2 个字节,否则 return一个错误。

方法cutcut_alternativecut_alternative_1试图实现这一点。下面列出了编译器错误消息。

为什么所有这些示例方法都无法编译?在cut_alternative_1中,借用彼此分开,而错误消息是相同的。

你能给出解决方法吗?

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 对类似问题评论了 link:

这个问题有两点不同:

HashMap 中的秘诀是 contains_key() 的使用,return 是一个不带终生包袱的布尔值。同样,Vec 解决方案涉及在关键位置使用索引而不是切片。

在您的情况下,等效改编需要将签名 cut4()cut2() 更改为 return Result<Range<usize>, ()>。然后你就可以这样写了:

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

另一种选择是使用内部可变性并使所有函数都接受 &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(我真的很惊讶这个编译,我以前从未尝试过在 Cell 中放置引用。)

最后,如果以上两种选择都不适合你,那总有一种不安全:

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

如果 unsafe 巫术看起来很可怕,那很好 - 它应该。如果你犯了错误,编译器不会帮助你,所以这种事情只能作为最后的手段使用。我 认为 上面的代码是正确的,因为它基本上与范围版本的工作方式相同,它只是在创建切片后将其转换为它们的组成部分。但是,任何时候有人重构代码时,您都会想知道是否崩溃,或者更糟的是,无声的 UB 正潜伏在拐角处。

如果还不是很明显,我的建议是方法 #1。