鉴于以下struct
和impl
:
use std::slice::Iter;
use std::cell::RefCell;
struct Foo {
bar: RefCell<Vec<u32>>,
}
impl Foo {
pub fn iter(&self) -> Iter<u32> {
self.bar.borrow().iter()
}
}
fn main() {}
我收到有关终身问题的错误消息:
error: borrowed value does not live long enough
--> src/main.rs:9:9
|
9 | self.bar.borrow().iter()
| ^^^^^^^^^^^^^^^^^ does not live long enough
10 | }
| - temporary value only lives until here
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the body at 8:36...
--> src/main.rs:8:37
|
8 | pub fn iter(&self) -> Iter<u32> {
| _____________________________________^ starting here...
9 | | self.bar.borrow().iter()
10 | | }
| |_____^ ...ending here
我如何返回并使用bar
迭代器?
答案 0 :(得分:12)
您不能这样做,因为它允许您绕过运行时检查唯一性违规。
RefCell
为您提供了一种将可变性排他性检查“推迟”到运行时的方法,以换取允许通过共享引用对其保留的数据进行变异。这是使用RAII保护完成的:您可以使用对RefCell
的共享引用获取保护对象,然后使用此保护对象访问RefCell
内的数据:
&'a RefCell<T> -> Ref<'a, T> (with borrow) or RefMut<'a, T> (with borrow_mut)
&'b Ref<'a, T> -> &'b T
&'b mut RefMut<'a, T> -> &'b mut T
这里的关键点是'b
与'a
不同,&mut T
允许人们在没有&mut
RefCell
Ref
引用的情况下获得RefMut
引用。但是,这些参考文献将与警卫相关联,并且不能比警卫更长寿。这是故意执行的:RefCell
和borrow()
析构函数在borrow_mut()
内切换各种标记以强制进行可变性检查,并在这些检查失败时强制Ref
和IntoIterator
恐慌
您可以做的最简单的事情是返回use std::cell::Ref;
struct VecRefWrapper<'a, T: 'a> {
r: Ref<'a, Vec<T>>
}
impl<'a, 'b: 'a, T: 'a> IntoIterator for &'b VecRefWrapper<'a, T> {
type IntoIter = Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Iter<'a, T> {
self.r.iter()
}
}
周围的包装器,该引用将实现IntoIterator
:
VecRefWrapper
(试试on playground)
您无法直接为Ref
实施into_iter()
,因为function get_last_chat_time(steamid){
if(!db_connect) {
return 0;
}
chat_db.aggregate(
[
{"$match":{"name":"chat-msg",steamid: steamid}},
{ "$sort" : { date : -1 } },
{"$limit" : 1}
]
).toArray(function(err, list){
if (err) {
console.log("Error loading history from DB: " + err);
} else {
var i = list.length-1;
var lasttime = Math.floor((new Date() - list[i].date)/1000);
console.log(Math.floor((new Date() - lasttime)/1000));//this works perfect
return lasttime;
}
});
}
console.log(get_last_chat_time(msg.steamid));// this is undefined
会消耗内部{{1}},因为您现在处于基本相同的状态
答案 1 :(得分:0)
这是一个替代解决方案,它按预期使用内部可变性。我们应该为 &T
值创建一个迭代器,而不是为 Ref<T>
值创建迭代器,它会自动遵循。
struct Iter<'a, T> {
inner: Option<Ref<'a, [T]>>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = Ref<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
match self.inner.take() {
Some(borrow) => match *borrow {
[] => None,
[_, ..] => {
let (head, tail) = Ref::map_split(borrow, |slice| {
(&slice[0], &slice[1..])
});
self.inner.replace(tail);
Some(head)
}
},
None => None,
}
}
}
接受的答案有一些明显的缺点,可能会使 Rust 的新手感到困惑。我将解释根据我的个人经验,接受的答案实际上如何对初学者有害,以及为什么我相信这个替代方案会按预期使用内部可变性和迭代器。
正如前面的答案重要强调的那样,使用 RefCell
创建了一个不同的类型层次结构,它将对共享值的可变和不可变访问隔离开来,但您不必担心要解决的生命周期迭代问题:
RefCell<T> .borrow() -> Ref<T> .deref() -> &T
RefCell<T> .borrow_mut() -> RefMut<T> .deref_mut() -> &mut T
在没有生命周期的情况下解决这个问题的关键是 Ref::map
方法,它在 book 中被严重遗漏。 Ref::map
“对借用数据的组件进行新引用”,或者换句话说,将外部类型的 Ref<T>
转换为某个内部值的 Ref<U>
:
Ref::map(Ref<T>, ...) -> Ref<U>
Ref::map
及其对应的 RefMut::map
是内部可变性模式的真正明星,不是 borrow()
和 borrow_mut()
。
为什么?因为与 borrow()
和 borrow_mut()
不同,Ref::mut
和 RefMut::map
允许您创建对可以“返回”的内部值的引用。
考虑向问题中描述的 first()
结构添加一个 Foo
方法:
fn first(&self) -> &u32 {
&self.bar.borrow()[0]
}
不,.borrow()
会创建一个临时的 Ref
,它只在方法返回之前有效:
error[E0515]: cannot return value referencing temporary value
--> src/main.rs:9:11
|
9 | &self.bar.borrow()[0]
| ^-----------------^^^
| ||
| |temporary value created here
| returns a value referencing data owned by the current function
error: aborting due to previous error; 1 warning emitted
如果我们将其分解并明确隐含的尊重,我们可以使正在发生的事情变得更加明显:
fn first(&self) -> &u32 {
let borrow: Ref<_> = self.bar.borrow();
let bar: &Vec<u32> = borrow.deref();
&bar[0]
}
现在我们可以看到 .borrow()
创建了一个 Ref<T>
,它由方法的作用域拥有,并且在它提供的引用可以使用之前不会返回,因此被删除。所以,我们真正需要的是返回一个拥有的类型而不是一个引用。我们想返回一个 Ref<T>
,因为它为我们实现了 Deref
!
Ref::map
将帮助我们为组件(内部)值做到这一点:
fn first(&self) -> Ref<u32> {
Ref::map(self.bar.borrow(), |bar| &bar[0])
}
当然,.deref()
仍会自动发生,而 Ref<u32>
将主要作为 &u32
引用透明。
问题。使用 Ref::map
时容易犯的一个错误是尝试在闭包中创建一个拥有的值,这在我们尝试使用 {{1} }.考虑第二个参数的类型签名,函数:borrow()
。它返回一个引用,而不是一个拥有的类型!
这就是为什么我们在答案 FnOnce(&T) -> &U,
中使用切片而不是尝试使用向量的 &v[..]
方法,该方法返回一个拥有的 .iter()
。切片是一种引用类型。
好的,现在我将尝试证明为什么这个解决方案比接受的答案更好。
首先,std::slice::Iter<'a, T>
的使用与 Rust 标准库不一致,可以说是 trait 的目的和意图。 trait 方法消耗 IntoIterator
: self
。
fn into_iter(self) -> ...
间接将 let v = vec![1,2,3,4];
let i = v.into_iter();
// v is no longer valid, it was moved into the iterator
用于包装器是不一致的,因为您使用的是包装器而不是集合。根据我的经验,初学者将受益于遵守约定。我们应该使用常规的 IntoIterator
。
接下来,为引用 Iterator
而不是拥有的类型 IntoIterator
实现了 &VecRefWrapper
特征。
假设您正在实现一个库。您的 API 的使用者将不得不使用引用操作符随意修饰拥有的值,如 Playground 上的示例所示:
VecRefWrapper
如果您不熟悉 Rust,这是一个微妙且令人困惑的区别。当它被匿名拥有并且应该只存在于循环的范围时,为什么我们必须引用该值?
最后,上面的解决方案展示了如何通过内部可变性深入到您的数据中,并使实现 mutable iterator 的前进道路也变得清晰。使用 for &i in &foo.iter() {
println!("{}", i);
}
。