我很难让借阅检查程序为一个简单的迭代链表构建器工作。
C
我尝试编译时收到的错误:
true
我想要的是相当简单的。我们遍历Vec,在每次迭代时创建一个新节点。如果prev为None,则必须是start,因此我们使root变量取得第一个节点的所有权。如果不是,我们会更新上一个节点的下一个值以指向此节点。
我是Rust的新手,所以我不确定自己哪里出错了。我的解释是,借用检查员并没有很好地处理这个问题。它无法推断出匹配中的None分支,包含' root'赋值,只会被调用一次,导致有关root的两个错误被借用两次。我是对的吗?
这种方法在Rust中可行吗?是否有更惯用的方法来做这类事情?
(递归方法可能更容易,但我想完成一个迭代的方法作为学习练习。)
答案 0 :(得分:6)
首先,您应该确保已阅读并理解Ownership和References and Borrowing上的Rust Book章节。你当前的问题是,你正在借用任何东西拥有的东西,因此就会消失。您还有其他问题,例如尝试通过不可变指针进行变异。
让我们得到的东西至少可以起作用:
fn main() {
let v = vec![1,5,3,8,12,56,1230,2,1];
let mut root: Option<Box<Node>> = None;
for i in v.into_iter().rev() {
root = Some(Box::new(Node { value: i, next: root }));
}
println!("root: {}",
root.map(|n| n.to_string()).unwrap_or(String::from("None")));
}
struct Node {
value: i32,
next: Option<Box<Node>>,
}
impl std::fmt::Display for Node {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let mut cur = Some(self);
let mut first = true;
try!(write!(fmt, "["));
while let Some(node) = cur {
if !first { try!(write!(fmt, ", ")); }
first = false;
try!(write!(fmt, "{}", node.value));
cur = node.next.as_ref().map(|n| &**n);
}
try!(write!(fmt, "]"));
Ok(())
}
}
这构造了一个列表并显示了如何迭代显示它。请注意构造代码完全没有借用。
我有点作弊,因为我向后迭代了向量来构建列表。
原始代码的问题在于,即使您删除了所有不必要的内容,也可以使用以下内容:
let v = vec![1,5,3,8,12,56,1230,2,1];
let mut v = v.into_iter();
let mut root: Option<Box<Node>> = None;
if let Some(i) = v.next() {
root = Some(Box::new(Node { value: i, next: None }));
let mut prev: &mut Box<Node> = root.as_mut().unwrap();
for i in v {
let curr = Some(Box::new(Node { value: i, next: None }));
prev.next = curr;
prev = prev.next.as_mut().unwrap();
}
}
你仍然会遇到这样一种情况:编译器看到你改变了你用第二条路径借来的东西。理解重新分配prev
并不实际创建任何别名并不够智能。另一方面,如果您将循环分解为等效的递归:
if let Some(i) = v.next() {
root = Some(Box::new(Node { value: i, next: None }));
fn step<It>(prev: &mut Box<Node>, mut v: It) where It: Iterator<Item=i32> {
if let Some(i) = v.next() {
let curr = Some(Box::new(Node { value: i, next: None }));
prev.next = curr;
step(prev.next.as_mut().unwrap(), v)
}
}
step(root.as_mut().unwrap(), v);
}
然后它完全没问题。遗憾的是,即使启用了优化,在这种情况下Rust也不会执行尾调用。因此,在借用检查器限制和缺乏保证尾部调用消除之间,这种设计在安全代码中可能是不可能的。
我自己遇到了这个问题;循环和&mut
指针并不总是很好地相互配合。您可以通过切换到RefCell
及其相关的运行时成本来解决此问题,尽管这会使在循环中迭代这样的列表变得复杂。另一个替代方法是使用usize
而不是指针,并将所有节点分配到某个地方的Vec
,尽管这会引入边界检查开销。
如果没有这一切,那就是unsafe
代码,它可以让你或多或少地准确写出你用其他语言写的东西,比如C或C ++,但是没有Rust通常的安全保证。
在一天结束时,编写不的数据结构只是包装在安全Rust中的现有数据结构而没有开销,这是不可能的。这就是为什么Rust中的基本数据结构都是使用一些不安全的代码编写的。