为什么 VecDeque is_empty 会阻止进一步借用?

如何解决为什么 VecDeque is_empty 会阻止进一步借用?

我有以下功能可以正常工作。我想稍微优化一下,将两个相似的代码块转换成一个闭包或一个函数。几次尝试后,我仍然看到一些借用检查器错误。看起来 VecDeque is_empty 阻止了更多代码。我不想改变队列和访问数据结构只是为了满足借用检查器。可以这样做吗?

use std::collections::{VecDeque,HashSet,HashMap};

fn day_7() -> i32 {
    let lines: Vec<String> = r"light red bags contain 1 bright white bag,2 muted yellow bags.
dark orange bags contain 3 bright white bags,4 muted yellow bags.
bright white bags contain 1 shiny gold bag.
muted yellow bags contain 2 shiny gold bags,9 faded blue bags.
shiny gold bags contain 1 dark olive bag,2 vibrant plum bags.
dark olive bags contain 3 faded blue bags,4 dotted black bags.
vibrant plum bags contain 5 faded blue bags,6 dotted black bags.
faded blue bags contain no other bags.
dotted black bags contain no other bags.".split('\n').map(|s| s.to_owned()).collect();
    let root_name = String::from("shiny gold");

    let mut connections: HashMap<String,Vec<String>> = HashMap::new();

    for line in &lines {
        let arr: Vec<&str> = line.split(" bags contain ").collect();
        let parent = arr[0];
        let values: Vec<&str> = arr[1].split(",").collect();
        for val in values {
            if val.starts_with("no other bags") { continue; }
            let parts: Vec<&str> = val.split_whitespace().collect();
            let child = format!("{} {}",parts[1],parts[2]);
            let child_list = connections.entry(child).or_insert(vec![]);
            if !child_list.iter().any(|s| s == parent) {
                child_list.push(parent.to_owned());
            }
        }
    }

    let mut queue: VecDeque<&str> = VecDeque::new();
    let mut visited: HashSet<&str> = HashSet::new();

    // a closure or a function should start here
    visited.insert(&root_name);
    if let Some(list) = connections.get(&root_name) {
        for n in list { queue.push_back(n); }
    }
    // convert above 4 lines to a closure or a function

    // let visit = |name| {
    //     visited.insert(name);
    //     if let Some(list) = connections.get(name) {
    //         for n in list { queue.push_back(n); }
    //     }
    // }
    // visit(&root_name);
    
    let mut result = 0;
    while !queue.is_empty() {
        match queue.pop_front() {
            None => break,Some(cur) => {
                if visited.contains(cur) { continue; };
                result += 1;

                // the second use of the code block
                visited.insert(cur);
                if let Some(list) = connections.get(cur) {
                    for n in list { queue.push_back(n); }
                }
                // visit(cur);
            }
        }
    }

    result
}

fn main() {
    println!("day 7: {}",day_7());
}

Playground link

更新:这是正确答案的正确功能。请注意,visitedqueue 没有明确的生命周期参数。

    fn visit<'a>(name: &'a str,visited: &mut HashSet<&'a str>,queue: &mut VecDeque<&'a str>,connections: &'a HashMap<String,Vec<String>>) {
        visited.insert(name);
        if let Some(list) = connections.get(name) {
            for n in list { queue.push_back(n); }
        }
    }

解决方法

不幸的是,将代码分解为 lambda 表达式与借用检查器有些不兼容。原因是 lambda 本质上是一个结构,其中包含对它关闭的所有变量的引用。因此,如果 lambda 需要引用一个变量,它必须在 lambda 存在的整个时间里都被冻结,如果它需要改变它,它必须是唯一这样做的。

这是借用规则的不幸交互。我不知道有任何 RFC 可以帮助解决这个问题,但未来的语言扩展将使这样的代码工作并非不可能。

至于变通方法,您可以定义函数局部宏。宏也可以关闭局部变量,所以效果很好:

    macro_rules! visit {
        ($name:expr) => {
            let name = $name;
            visited.insert(name);
            if let Some(list) = connections.get(name) {
                for n in list { queue.push_back(n); }
            }
        }
    }
    
    visit!(&root_name);

Playground link

注意我是如何立即将宏参数绑定到变量的。这是为了避免踩枪:when you repeat a macro parameter name,the expression passed in as argument is evaluated twice。这对于一些宏来说是可取的,但是当你想像 lambda 一样使用它们时——不是那么多。

如果您宁愿避免为此目的使用宏,您也可以考虑将所需的变量作为函数的显式参数。确保您拥有所有变量的一个好方法是使用内部 fn 函数,因为这些函数无法捕获变量:

    fn visit<'a>(name: &'a str,visited: &mut HashSet<&'a str>,queue: &mut VecDeque<&'a str>,connections: &'a HashMap<String,Vec<String>>) {
        visited.insert(name);
        if let Some(list) = connections.get(name) {
            for n in list { queue.push_back(n); }
        }
    }

    visit(root_name.as_str(),&mut visited,&mut queue,&connections);

在这种情况下你需要注意生命周期注解:'a 是所有涉及到的字符串的生命周期,如果你还说 visited: &'a mut,那么 visited 将保持借用只要字符串存在。

顺便说一句:您可以使用 while let 更好地编写循环:

    while let Some(cur) = queue.pop_front() {
        if visited.contains(cur) { continue; };
        result += 1;
        visit!(cur);
    }
,

阻塞借用的不是 queue.is_empty(),而是持有 queue 的可变借用的闭包阻塞了对 queue 的所有其他访问。这包括对 queue.is_empty() 的调用,还包括 queue.pop_front(),或者在保持它的闭包处于活动状态时您想对 queue 执行的任何其他操作。

此问题的标准解决方法是避免捕获,并将 &mut queue 作为显式参数传递给闭包。但不幸的是,在您的情况下它不起作用,因为您还需要将 &connections&mut visited 传递给闭包,并且它们的生命周期之间的关系不是编译器目前能够推断的。

然而,还有另一种出路,虽然不清楚你是否会喜欢它。问题的核心是闭包试图捕获对您也需要在其外部访问的内容的可变引用。 Rust 确实支持共享的可变访问——通过内部可变性。您可以将 queueconnections 包裹在 RefCell 中,如下所示:

let queue: RefCell<VecDeque<&str>> = Default::default();
let visited: RefCell<HashSet<&str>> = Default::default();

现在闭包将通过共享引用捕获它们,并且与环境共享它们没有问题:

let visit = |name| {
    visited.borrow_mut().insert(name);
    if let Some(list) = connections.get(name) {
        let mut queue = queue.borrow_mut();
        for n in list {
            queue.push_back(n);
        }
    }
};

let mut result = 0;
while !queue.borrow().is_empty() {
    let cur = match queue.borrow_mut().pop_front() {
        None => break,Some(cur) => cur,};
    if visited.borrow().contains(cur) {
        continue;
    };
    result += 1;
    visit(cur);
}

Playground

虽然使用 RefCell 感觉有点像作弊,因为它将借用检查从编译推到运行时,但它非常适合我们知道我们在做什么的情况,但我们不能向当前借用检查器证明它。此外,RefCell 并不昂贵,例如它不会产生分配,而 borrow()borrow_mut() 归结为检查/更新标志。最好针对您的特定用例衡量其效果,但由于所有检查都仅限于单个代码块,我希望编译器能够完全省略它们。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-