一次不能多次借用可变 + 变量是可变队列

cannot borrow as mutable more than once at a time + the variable is a mutable queue

提问人:Pouneh Bahrami 提问时间:4/25/2023 更新时间:4/26/2023 访问量:180

问:

我正在尝试遍历一个图形,并根据我的代码中定义的条件从中提取一个子树。图表或标准的细节并不重要。然而,由于引用的复杂性和锈蚀中的借用,创建树的过程非常具有挑战性。我不是生锈专家。

为了简单起见,我有 2 个功能:

  1. 第一个获取图,要遍历的图的深度,并返回一个 ScriptTree 类型的子树,该子树是一个结构。
fn run_subsequent_scripts(&self, graph: &PageGraph, depth: usize) ->  ScriptTree {
    let mut level: usize;
    let mut visited: HashMap<NodeId, bool> = HashMap::new();
    // create a queue for BFS
    let mut queue = vec![];
    // Starting vertex maked as visited and added to queueu
    let script_info = get_script_info(graph, self.query.id);
    let mut root = ScriptTree::new(script_info.0 ,script_info.1, script_info.2);
    
    visited.insert(self.query.id, true);
    queue.push(&mut root);
    
    let mut level_meter: Vec<NodeId> = Vec::new();
    
    // continue until queue is empty
    while queue.len() != 0 && level_meter.len() < depth {
        level = queue.len();

        while level != 0 {
          // Get the front of the queue and remove it
          let mut child_node: ScriptTree;
          let parent_node = queue.remove(0);
          level -= 1;
          // Get all adjacent vertices from that vertex
          // neighbors of the parent
          let neighbors: Vec<NodeId> = get_injected_scripts(&graph,    parent_node.script_info.script_node_id, &Action::script());
          let mut neighbor_nodes: Vec<ScriptTree> = Vec::new();
          for child_id in neighbors {
              if !visited.contains_key(&child_id) {
              //mark as visited
              visited.insert(child_id, true);
              // push back to check this vertex's vertices
              let child_script_info = get_script_info(graph, child_id);
              child_node = ScriptTree::new(child_script_info.0, child_script_info.1, child_script_info.2);
              queue.push(&mut child_node);
              neighbor_nodes.push(child_node);
              level_meter.push(child_id);
              }
          }
        self.update_tree(&mut root, &parent_node, neighbor_nodes);
        }
        level += 1;
    } 
  return root
  }
  1. 第二个函数通过添加在上一个函数中向量的node_neighbors中捕获的parent_node子项来更新树:
fn update_tree(&self, subtree: &mut ScriptTree, parent_node: &ScriptTree, node_neighbors: Vec<ScriptTree>) {
    if subtree.node_id == parent_node.node_id {
      // Return a reference to this node if it has the target ID
      for child in node_neighbors{
        subtree.add_child(child);
      }
    }
    else {

      // Recursively search the children of this node
      for mut child in &subtree.children {
          self.update_tree(&mut child, parent_node, node_neighbors);
      }
    }    
  }

这段代码在多行中给了我错误:

在函数 1 中,

  • 在行中,它说“不能分配给,因为它是借来的,分配到借来的发生在这里”。child_node = ScriptTree::new(child_script_info.0, child_script_info.1, child_script_info.2);child_nodechild_node
  • 在行中,它说“不能借用,因为一次可变多次,在循环的上一次迭代中,这里可变借用”queue.push(&mut child_node);child_nodechild_node
  • 在行中,它说“不能搬出,因为它是借来的,搬出这里发生”neighbor_nodes.push(child_node);child_nodechild_node
  • 并且它说“不能一次多次借用为可变借用,第二次可变借用在这里发生”self.update_tree(&mut root, &parent_node, neighbor_nodes);root

在函数 2 中:

  • 在行中,它说:self.update_tree( &mut child, parent_node, node_neighbors);
  1. 不能将引用中的数据借用为可变 不能借用为可变&
  2. 移动值的使用:在循环的上一次迭代中,将值移动到此处node_neighbors

我感谢任何帮助解决问题!

参考 借用检查器 可变

评论

0赞 CoronA 4/25/2023
您应该考虑重写。在函数 2 中: - 你期望一个可变引用是一个不可变引用的别名,它被借用检查器阻止 - 你不可变地借用它,将其分配给一个你期望可变的变量 - 你有一个拥有的 var,你递归地传递它,期望在之后重用它(放弃所有权可以防止之后的任何更改)&subtree.childrenmut childnode_neighbours
1赞 cafce25 4/25/2023
正如 CoronA 所指出的,您的代码有几个问题,您可能需要退后一步,更多地熟悉这门语言,在 Rust 中编写正确的图形、树甚至链表都非常困难,并且不应该在没有一些指导的情况下由相对不熟悉该语言的人尝试。你可能想看看 完全太多的链表 它涵盖了你在实现它们时遇到的许多陷阱,它极大地帮助了我理解借用检查器。
0赞 Pouneh Bahrami 4/25/2023
@CoronA 感谢您的指导!我解决了函数 2 中的错误。现在,只有一个问题:在函数 1 中,我需要将child_node分配给两个向量。queue.push(child_node);neighbor_nodes.push(child_node);但关键是队列是预期的,当我更改它时,第二个向量抱怨“无法移出,因为它是借来的”。有什么办法可以解决这个问题吗?&mut ScriptTreechild_node
0赞 Pouneh Bahrami 4/25/2023
@cafce25 感谢您发送链接。真的很感激。我要努力了。
0赞 CoronA 4/25/2023
试着把你的代码放到一个 rust-playground 中。很难对仅在片段中可见的代码给出解决方案。

答:

0赞 Jishan Shaikh 4/26/2023 #1

在:run_subsequent_scripts

  • 更改为 .let mut child_node: ScriptTreelet child_node: ScriptTree
    • 声明而不初始化它,这允许稍后分配给它而无需可变借用它。child_node
  • 更改为 .queue.push(&mut root)queue.push(root)
    • 将对 root 的不可变引用传递给队列,这应该允许我们稍后可变地借用它。
  • 更改为 .let mut neighbor_nodes: Vec<ScriptTree> = Vec::new()let mut neighbor_nodes = Vec::with_capacity(neighbors.len())
    • 预先分配具有足够容量的neighbor_nodes向量以容纳所有邻居,这应该避免任何可能导致借贷问题的重新分配。
  • 更改循环访问要改用的向量。neighbor_nodesfor child_node in node_neighbors.into_iter()
    • 使用向量,并允许在不借用它们的情况下将值移出其中。node_neighborsScriptTree

在:update_tree

  • 更改为 .for mut child in &subtree.childrenfor child in &mut subtree.children
    • 允许可变地借用子树的子级,这是更新子树所必需的。

更新了包含示例输入的代码 (Rust Playground):

use std::collections::HashMap;

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct NodeId(u32);

#[derive(Debug)]
struct PageGraph;

#[derive(Debug, Clone)]
struct ScriptInfo(u32, u32, u32);

#[derive(Debug, Clone)]
struct ScriptTree {
    script_info: ScriptInfo,
    children: Vec<ScriptTree>,
}

impl ScriptTree {
    fn new(a: u32, b: u32, c: u32) -> Self {
        ScriptTree {
            script_info: ScriptInfo(a, b, c),
            children: Vec::new(),
        }
    }

    fn add_child(&mut self, child: ScriptTree) {
        self.children.push(child);
    }
}

enum Action {
    Script,
}

fn get_injected_scripts(
    _graph: &PageGraph,
    _node_id: u32,
    _action: &Action,
) -> Vec<NodeId> {
    vec![NodeId(1), NodeId(2), NodeId(3)]
}

fn get_script_info(_graph: &PageGraph, _node_id: NodeId) -> ScriptInfo {
    ScriptInfo(1, 2, 3)
}

struct Query {
    id: NodeId,
}

struct Example {
    query: Query,
}

impl Example {
    fn run_subsequent_scripts(&self, graph: &PageGraph, depth: usize) -> ScriptTree {
        let mut level: usize = 0;
        let mut visited: HashMap<NodeId, bool> = HashMap::new();
        let mut queue = vec![];
        let script_info = get_script_info(graph, self.query.id.clone());
        let mut root = ScriptTree::new(script_info.0, script_info.1, script_info.2);

        visited.insert(self.query.id.clone(), true);
        queue.push(root.clone());

        let mut level_meter: Vec<NodeId> = Vec::new();

        while !queue.is_empty() && level < depth {
            let level_size = queue.len();

            for _ in 0..level_size {
                let mut neighbor_nodes = Vec::new();
                let parent_node = queue.remove(0);
                let neighbors = get_injected_scripts(&graph, parent_node.script_info.0, &Action::Script);

                for child_id in neighbors {
                    if !visited.contains_key(&child_id) {
                        visited.insert(child_id.clone(), true);
                        let child_script_info = get_script_info(graph, child_id.clone());
                        let child_node = ScriptTree::new(
                            child_script_info.0,
                            child_script_info.1,
                            child_script_info.2,
                        );
                        queue.push(child_node.clone());
                        neighbor_nodes.push(child_node);
                        level_meter.push(child_id);
                    }
                }
                self.update_tree(&mut root, &parent_node, neighbor_nodes);
            }
            level += 1;
        }
        root
    }

    fn update_tree(
        &self,
        subtree: &mut ScriptTree,
        parent_node: &ScriptTree,
        node_neighbors: Vec<ScriptTree>,
    ) {
        if subtree.script_info.0 == parent_node.script_info.0 {
            for child_node in node_neighbors {
                subtree.add_child(child_node);
            }
        } else {
            for child in &mut subtree.children {
                self.update_tree(child, parent_node, node_neighbors.clone());
            }
        }
    }
}

fn main() {
    let graph = PageGraph;
    let example = Example {
        query: Query { id: NodeId(1) },
    };
    let depth = 1;
    let script_tree = example.run_subsequent_scripts(&graph, depth);
    println!("{:#?}", script_tree);
}

最佳实践:提供可由其他人执行的最小可行示例代码(有问题)。

评论

0赞 Pouneh Bahrami 4/26/2023
嗨,@jishan-shaikh,感谢您的详细回答!我做了你建议的更改。它提示其他错误:现在在 update_tree 函数中,在说“for child in neighbor_nodes { subtree.add_child(child); }”的行中,child 是不匹配类型,并说 expected struct ,找到。当我尝试更改为 struct ScriptTree 实现的 add_child 方法时,它会导致其他错误。比如,我必须将子项更改为 Vec<&Scripts>这要求我包含“预期的命名生存期参数”ScriptTree&&ScriptTree
0赞 Jishan Shaikh 4/26/2023
@PounehBahrami 编辑:添加了 playground 以供参考和示例
0赞 Pouneh Bahrami 4/28/2023
谢谢@jishan-shaikh!!真的很感激它♡解决了这个问题。我真的为此苦苦挣扎了一个星期:)),似乎我不能投票给你的答案,因为我在 stackoverflow 上没有足够的声誉!对不起......
0赞 Jishan Shaikh 4/28/2023
谢谢,很乐意帮忙。不用担心点赞,我想你仍然可以接受答案。