这个问题突然出现在我的脑海中(当我没有编程时),它实际上让我对很多关于编程的事情产生了疑问(尤其是在C++、C#、Rust中)。
我想指出的是,我知道在这个问题上也有类似的问题:不能作为可变借用,因为它也是作为不可变借用的。但我认为,这个问题是针对某一特定情况提出的;一个子问题。我想更好地了解如何在Rust中解决这样的问题。
";事物;我最近意识到:;如果我有一个指向动态数组中某个元素的指针/引用,然后我添加了一个元素,导致数组扩展和重新分配,这将破坏指针因此,我需要一个特殊引用即使它重新分配了"。这让我开始以不同的方式思考很多事情。但除此之外,我意识到这个问题对于有经验的c++程序员来说是微不足道的。不幸的是,在我的经历中,我根本没有遇到过这种情况。
所以我想看看Rust是否有针对这类问题的现有"特殊类型",如果没有,如果我自己制作(用于测试)会发生什么。其思想是;特殊指针";将只是指向Vector(List)本身的指针,但也有一个用于索引的i32字段;因此,它都绑定在一个变量下,可以在需要时"取消引用"。
注意:";VecPtr";是指一个不可变的引用。
struct VecPtr<'a, T> {
vec: &'a Vec<T>,
index: usize
}
impl<T: Copy> VecPtr<'_, T> {
pub fn value(&self) -> T {
return self.vec[self.index];
}
}
fn main() {
let mut v = Vec::<i32>::with_capacity(6);
v.push(3);
v.push(1);
v.push(4);
v.push(1);
let r = VecPtr {vec: &v,index: 2};
let n = r.value();
println!("{}",n);
v.push(5); // error!
v.push(9); // error!
v.push(6); // re-allocation triggered // also error!
let n2 = r.value();
println!("{}",n2);
return;
}
因此,上面的示例代码表明,在尝试同时拥有可变引用的同时,不能拥有现有的不可变引用。好的
根据我从另一个StackOverflow问题中读到的内容,编译器错误的原因之一是Vector可以在调用"时随时重新分配其内部数组;"推";。这将使对内部数组的所有引用失效。这是100%合理的。因此,作为一名程序员,您可能希望仍然有对数组的引用,但它们的设计更安全一些。你只需要一个指向有问题向量本身的指针,而不是直接指向内部数组的指针,并包含一个i32索引,这样你就可以知道你正在查看的元素。这意味着将发生在v.push(6);
的悬挂指针问题不应该再发生了。但是编译器仍然抱怨同样的问题。我理解。
我想它仍然关注向量本身的引用,而不是内部。这让事情有点混乱。因为编译器正在查看并试图保护这里的不同指针。但老实说,在示例代码中,指向vec本身的指针看起来完全不错。这个引用根本没有改变(据我所知,它不应该改变)。
所以我的问题是,有没有一种实践可以通过某些引用告诉编译器你的意图?所以编译器知道没有问题(除了unsafe关键字)。
或者,有没有更好的方法来完成我在示例代码中要做的事情?
经过更多的研究
看起来这里的一个解决方案是使用参考计数Rc<T>
,但我不确定这是100%
由于存在类似的现有问题,我通常不会问这个问题,但这个问题(我认为)正在调查一个略有不同的情况,有人(或我)会试图解决不安全的引用情况,但编译器仍然坚持存在问题。
我想问题归结为:你觉得这可以接受吗?
fn main() {
let mut v = Vec::<i32>::with_capacity(6);
v.push(3);
v.push(1);
v.push(4);
v.push(1);
let r = VecPtr { vec: &v, index: 2 };
let n = r.value();
println!("{}",n);
v[2] = -1;
let n2 = r.value(); // This returned 4 just three lines ago and I was
// promised it wouldn't change! Now it's -1.
println!("{}",n2);
}
或者这个
fn main() {
let mut v = Vec::<i32>::with_capacity(6);
v.push(3);
v.push(1);
v.push(4);
v.push(1);
let r = VecPtr { vec: &v, index: 2 };
let n = r.value();
println!("{}",n);
v.clear();
let n2 = r.value(); // This exact same thing that worked three lines ago will now panic.
println!("{}",n2);
}
或者,最糟糕的是:
fn main() {
let mut v = Vec::<i32>::with_capacity(6);
v.push(3);
v.push(1);
v.push(4);
v.push(1);
let r = VecPtr { vec: &v, index: 2 };
let n = r.value();
println!("{}",n);
drop(v);
let n2 = r.value(); // Now you do actually have a dangling pointer.
println!("{}",n2);
}
Rust的回答是一个强调的";否";并且这是在类型系统中强制执行的。这不仅仅是关于取消引用悬挂指针的不确定性,这也是一个核心设计决策。
你能用某些参考资料告诉编译器你的意图吗?对你可以告诉编译器你是否想共享你的引用,或者你是否想通过它进行变异。在你的情况下,你已经告诉编译器你想共享它。这意味着你不允许再对它进行变异了。正如上面的例子所表明的那样,这是有充分理由的。
因此,借用检查器没有堆栈或堆的概念,它不知道哪些类型分配,哪些不分配,也不知道Vec
何时调整大小。它只知道并关心移动值和借用引用:它们是共享的还是可变的,以及它们的寿命。
现在,如果你想让你的结构发挥作用,Rust为你提供了一些可能性:其中之一是RefCell
。RefCell
允许您从不可变引用借用可变引用,而代价是运行时检查是否没有错误的别名。这与Rc
一起可以使您的VecPtr
:
use std::cell::RefCell;
use std::rc::Rc;
struct VecPtr<T> {
vec: Rc<RefCell<Vec<T>>>,
index: usize,
}
impl<T: Copy> VecPtr<T> {
pub fn value(&self) -> T {
return self.vec.borrow()[self.index];
}
}
fn main() {
let v = Rc::new(RefCell::new(Vec::<i32>::with_capacity(6)));
{
let mut v = v.borrow_mut();
v.push(3);
v.push(1);
v.push(4);
v.push(1);
}
let r = VecPtr {
vec: Rc::clone(&v),
index: 2,
};
let n = r.value();
println!("{}", n);
{
let mut v = v.borrow_mut();
v.push(5);
v.push(9);
v.push(6);
}
let n2 = r.value();
println!("{}", n2);
}
我将让您研究RefCell
是如何工作的。