首页 > 解决方案 > 我们如何解决有条件返回字符串切片时发生的这个生命周期错误?

问题描述

期望的行为是如果你可以截断 4 个字节并返回 4 个字节,否则如果你可以截断 2 个字节并返回 2 个字节,否则返回错误。

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

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

你能给出一个解决方法吗?

游乐场链接

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 评论了一个类似问题的链接:从 HashMap 或 Vec 返回引用会导致借用持续超出其范围?

这个问题在两个方面有所不同:

标签: rustlifetime

解决方案


HashMap 解决方案中的秘诀是使用contains_key(),它返回一个不携带终身包袱的布尔值。同样,该Vec解决方案涉及在战略位置使用索引而不是切片。

在您的情况下,等效的适应需要更改签名cut4()cut2()返回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

操场

另一种选择是使用内部可变性并使所有函数都接受&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(老实说,我对这个编译感到惊讶,我以前从未尝试在 a 中放置参考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(())
        }
    }
}

操场

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

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


推荐阅读