【发布时间】:2020-01-11 18:44:38
【问题描述】:
考虑一下,为了简单起见,我想实现一个具有 n 个连续元素 0,1,...,n-1 的可索引向量 v,即 v[i] = i。这个向量应该是按需填充的,也就是说,如果使用 v[i] 并且当前向量包含 n
下面的代码可以正常工作。
struct LazyVector {
data: Vec<usize>
}
impl LazyVector {
fn new() -> LazyVector {
LazyVector{
data: vec![]
}
}
fn get(&mut self, i:usize) -> &usize {
for x in self.data.len()..=i {
self.data.push(i);
}
&self.data[i]
}
}
pub fn main() {
let mut v = LazyVector::new();
println!("v[5]={}",v.get(5)); // prints v[5]=5
}
但是,上面的代码只是我尝试实现的实际结构的模型。除此之外,(1)我希望能够使用索引运算符,(2)尽管在访问位置时实际上可能会修改向量,但我希望这对用户是透明的,即也就是说,即使我对 v 有不可变引用,我也希望能够索引任何位置。不可变引用是首选,以防止其他不需要的修改。
要求 (1) 可以通过实现 Index trait 来实现,就像这样
impl std::ops::Index<usize> for LazyVector {
type Output = usize;
fn index(&self, i: usize) -> &Self::Output {
self.get(i)
}
}
但是,这不会编译,因为我们需要一个可变引用才能调用 LazyVector::get。由于要求 (2),我们不想让这个引用可变,即使我们这样做了,我们也不能这样做,因为它会违反 Index trait 的接口。我认为这可以通过 RefCell 智能指针实现内部可变性模式(如 The Rust Book 的第 15 章)。所以我想出了类似的东西
struct LazyVector {
data: std::cell::RefCell<Vec<usize>>
}
impl LazyVector {
fn new() -> LazyVector {
LazyVector{
data: std::cell::RefCell::new(vec![])
}
}
fn get(&self, i:usize) -> &usize {
let mut mutref = self.data.borrow_mut();
for x in mutref.len()..=i {
mutref.push(x)
}
&self.data.borrow()[i] // error: cannot return value referencing a temporary value
}
}
但这不起作用,因为它试图返回一个引用借用()返回的 Ref 结构的值,该结构在 LazyVector::get 的末尾超出范围。最后,为了避免这种情况,我做了类似的事情
struct LazyVector {
data: std::cell::RefCell<Vec<usize>>
}
impl LazyVector {
fn new() -> LazyVector {
LazyVector{
data: std::cell::RefCell::new(vec![])
}
}
fn get(&self, i:usize) -> &usize {
let mut mutref = self.data.borrow_mut();
for x in mutref.len()..=i {
mutref.push(x)
}
unsafe { // Argh!
let ptr = self.data.as_ptr();
&std::ops::Deref::deref(&*ptr)[i]
}
}
}
impl std::ops::Index<usize> for LazyVector {
type Output = usize;
fn index(&self, i: usize) -> &Self::Output {
self.get(i)
}
}
pub fn main() {
let v = LazyVector::new(); // Unmutable!
println!("v[5]={}",v.get(5)); // prints v[5]=5
}
现在它可以按要求工作,但作为新手,我不太确定 unsafe 块!我想我正在用一个安全的界面有效地包装它,但我不确定。所以我的问题是这是否可行,或者是否有更好、完全安全的方法来实现这一目标。
感谢您的帮助。
【问题讨论】:
-
由于您返回对
usize的引用,如果您的代码按原样工作,它将扩展向量并在对usize的引用存在时重新分配向量中的内存,这会导致无效的内存访问。如果你想这样做,你需要返回usize而不是引用,这意味着你不能使用Index特征。 -
不安全块不健全。添加到向量可能会导致它重新分配,因此引用可能最终成为悬空指针。这是当发生变异的方法采用
&mut self时,Rust 保护你的事情之一。 -
无论您在这里做什么,都会变得非常复杂。这应该暗示你正在尝试一些奇怪的东西,你应该重新考虑为什么你甚至需要这个。
-
天哪!呸!现在很明显,你指出来了。我非常专注于应该在真实场景中使用它的方式,以至于我错过了这个明显的问题。 (见 cmets 到下一个答案)
标签: rust