是否需要使用"let"绑定来创建生存期更长的值?



我最近开始研究 Rust,在开发测试程序时,我编写了以下方法:

pub fn add_transition(&mut self, start_state: u32, end_state: u32) -> Result<bool, std::io::Error> {
let mut m: Vec<Page>;
let pages: &mut Vec<Page> = match self.page_cache.get_mut(&start_state) {
Some(p) => p,
None    => {
m = self.index.get_pages(start_state, &self.file)?;
&mut m
}
};
// omitted code that mutates pages 
// ...
Ok(true)
}

它确实按预期工作,但我不相信m变量。如果我删除它,代码看起来更优雅:

pub fn add_transition(&mut self, start_state: u32, end_state: u32) -> Result<bool, std::io::Error> {
let pages: &mut Vec<Page> = match self.page_cache.get_mut(&start_state) {
Some(p) => p,
None    => &mut self.index.get_pages(start_state, &self.file)?
};
// omitted code that mutates pages
// ...
Ok(true)
}

但我得到:

error[E0716]: temporary value dropped while borrowed
--> srcmodule1mod.rs:28:29
|
26 |           let pages: &mut Vec<Page> = match self.page_cache.get_mut(&start_state) {
|  _____________________________________-
27 | |             Some(p) => p,
28 | |             None    => &mut self.index.get_pages(start_state, &self.file)?
| |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
| |                             |                                            |
| |                             |                                            temporary value is freed at the end of this statement
| |                             creates a temporary which is freed while still in use
29 | |         };
| |_________- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value

我完全理解该错误,该错误将我定向到工作片段,但我想知道是否有更优雅和/或惯用的方式来编写此代码。我在函数开始时声明m,只是为了防止临时变量过早释放。有没有办法告诉编译器self.index.get_pages返回值的生存期应该是整个add_transition函数?

更多详情:

  • Page是一个相对较大的结构,所以我宁愿不实现Copy特征,也不愿克隆它。
  • page_cache属于HashMap<u32, Vec<Page>>
  • self.index.get_pages相对较慢,我正在使用page_cache来缓存结果
  • self.index.get_pages的返回类型为Result<Vec<Page>, std::io::Error>

这是正常的,你的"干净"代码基本上可以执行以下操作:

let y = {
let x = 42;
&x
};

在这里应该很明显,您无法返回对x的引用,因为x在块的末尾被删除。使用临时值时,这些规则不会更改:self.index.get_pages(start_state, &self.file)?创建一个临时值,该值在块的末尾(第 29 行(删除,因此您无法返回对它的引用。

通过m的解决方法现在将其临时移动到m绑定一个块中,该块将存活足够长的时间,以便pages使用它。

现在对于替代方案,我想page_cache是一个HashMap?然后,您也可以执行类似let pages = self.page_cache.entry(start_state).or_insert_with(||self.index.get_pages(...))?;.这种方法的唯一问题是get_pages返回结果,而当前缓存存储Vec<Page>(仅限Ok分支(。您可以调整缓存以实际存储Result,我认为这在语义上也更好,因为您想缓存该函数调用的结果,那么为什么不为Err这样做呢?但是,如果您有充分的理由不缓存Err,那么您的方法应该可以正常工作。

你的可能是最有效的方法,但理论上没有必要,一个可以更优雅。

另一种方法是在这种情况下使用trait 对象— 让变量的类型为dyn DerefMut<Vec<Page>>。这基本上意味着这个变量可以容纳任何实现特征DerefMut<Vec<Page>>>的类型,这样做的两种类型是&mut Vec<Page>Vec<Page>,在这种情况下,变量可以容纳其中任何一个,但内容只能通过DerefMut引用。

因此,以下代码用作说明:

struct Foo {
inner : Option<Vec<i32>>,
}
impl Foo {
fn new () -> Self {
Foo { inner : None }
}
fn init (&mut self) {
self.inner = Some(Vec::new())
}
fn get_mut_ref (&mut self) -> Option<&mut Vec<i32>> {
self.inner.as_mut()
}
}
fn main () {
let mut foo : Foo = Foo::new();
let mut m   : Box<dyn AsMut<Vec<i32>>> = match foo.get_mut_ref() {
Some(r) => Box::new(r),
None    => Box::new(vec![1,2,3]),
};
m.as_mut().as_mut().push(4);
}

这里的关键是类型Box<dyn AsMut<Vec<i32>>;这意味着它可以是一个容纳任何类型的盒子,只要类型实现AsMut<Vec<i32>>,因为它是装箱的,我们还需要.as_mut().as_mut()才能从中获取实际&mut <Vec<i32>>

因为不同的类型可以有不同的大小;它们也不能在堆栈上分配,所以它们必须位于某个指针后面,因此通常会选择Box,在这种情况下,一个没有其 pointee 所有权的普通指针将面临与您面临的问题类似的问题。

有人可能会争辩说,这段代码更优雅,但你的代码肯定更有效率,不需要进一步的堆分配。

最新更新