rust - 我们如何解决有条件返回字符串切片时发生的这个生命周期错误?
问题描述
期望的行为是如果你可以截断 4 个字节并返回 4 个字节,否则如果你可以截断 2 个字节并返回 2 个字节,否则返回错误。
方法cut
,cut_alternative
并cut_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 返回引用会导致借用持续超出其范围?
这个问题在两个方面有所不同:
- 有多个可变借用而不是一个不可变借用和一个可变借用。
- 我现在无法在此问题的答案中应用变通方法。解决方法将不胜感激。
解决方案
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。
推荐阅读
- c++ - '非标准语法;使用 '&' 创建指向成员的指针' 与线程
- node.js - 节点 JS:填充自动增量字段(猫鼬)
- vue.js - 使用 Require JS 将 vuetify 添加到我的项目中?
- asp.net-core - Entity Framework Core 不会创建 Identity 和其他表 MVC Code First
- python - 遍历文件但按创建时间排序
- sql - 根据 Postgres 10 中 JSON 的特定值做 UPSERT
- python - 如何从 for 循环中删除项目?
- javascript - Vue 故事书意外的令牌 (3:14)
- microsoft-graph-api - microsoft graph api 分配许可证给出错误
- react-native - 嵌套 React-Native FlatList 组件并接收这两个项目