我最近开始研究 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 所有权的普通指针将面临与您面临的问题类似的问题。
有人可能会争辩说,这段代码更优雅,但你的代码肯定更有效率,不需要进一步的堆分配。