【发布时间】:2021-02-20 05:33:34
【问题描述】:
我正在尝试将文本文件的解析操作结果存储到HashMap 中(使用nom 解析)。结果由Vec 缓冲区和该缓冲区上的一些切片组成。目标是将它们一起存储在一个元组或结构中作为哈希映射中的一个值(使用String 键)。但我无法解决生命周期问题。
上下文
解析本身采用&[u8] 并返回一些包含同一输入上的切片的数据结构,例如:
struct Cmd<'a> {
pub name: &'a str
}
fn parse<'a>(input: &'a [u8]) -> Vec<Cmd<'a>> {
[...]
}
现在,因为解析是在没有存储的切片上进行的,所以我需要首先将输入文本存储在 Vec 中,以便输出切片保持有效,因此类似于:
struct Entry<'a> {
pub input_data: Vec<u8>,
pub parsed_result: Vec<Cmd<'a>>
}
然后我会理想地将这个Entry 存储到HashMap 中。这是出现了麻烦。我尝试了两种不同的方法:
尝试 A:存储然后解析
先用输入创建HashMap条目,直接解析引用HashMap条目,然后更新它。
pub fn store_and_parse(filename: &str, map: &mut HashMap<String, Entry>) {
let buffer: Vec<u8> = load_from_file(filename);
let mut entry = Entry{ input_data: buffer, parsed_result: vec![] };
let cmds = parse(&entry.input_data[..]);
entry.parsed_result = cmds;
map.insert(filename.to_string(), entry);
}
这不起作用,因为借用检查器抱怨 &entry.input_data[..] 借用的生命周期与 entry 相同,因此不能移动到 map,因为有一个有效的借用。
error[E0597]: `entry.input_data` does not live long enough
--> src\main.rs:26:23
|
23 | pub fn store_and_parse(filename: &str, map: &mut HashMap<String, Entry>) {
| --- has type `&mut std::collections::HashMap<std::string::String, Entry<'1>>`
...
26 | let cmds = parse(&entry.input_data[..]);
| ^^^^^^^^^^^^^^^^ borrowed value does not live long enough
27 | entry.parsed_result = cmds;
28 | map.insert(filename.to_string(), entry);
| --------------------------------------- argument requires that `entry.input_data` is borrowed for `'1`
29 | }
| - `entry.input_data` dropped here while still borrowed
error[E0505]: cannot move out of `entry` because it is borrowed
--> src\main.rs:28:38
|
26 | let cmds = parse(&entry.input_data[..]);
| ---------------- borrow of `entry.input_data` occurs here
27 | entry.parsed_result = cmds;
28 | map.insert(filename.to_string(), entry);
| ------ ^^^^^ move out of `entry` occurs here
| |
| borrow later used by call
尝试 B:先解析然后存储
首先解析,然后尝试将Vec 缓冲区和数据切片一起存储到HashMap 中。
pub fn parse_and_store(filename: &str, map: &mut HashMap<String, Entry>) {
let buffer: Vec<u8> = load_from_file(filename);
let cmds = parse(&buffer[..]);
let entry = Entry{ input_data: buffer, parsed_result: cmds };
map.insert(filename.to_string(), entry);
}
这不起作用,因为借用检查器抱怨 cmds 与 &buffer[..] 具有相同的生命周期,但 buffer 将在函数结束时被删除。它忽略了cmds 和buffer 具有相同生命周期的事实,并且都(我希望)移入entry,而entry 本身也移入map,因此这里应该没有生命周期问题。
error[E0597]: `buffer` does not live long enough
--> src\main.rs:33:21
|
31 | pub fn parse_and_store(filename: &str, map: &mut HashMap<String, Entry>) {
| --- has type `&mut std::collections::HashMap<std::string::String, Entry<'1>>`
32 | let buffer: Vec<u8> = load_from_file(filename);
33 | let cmds = parse(&buffer[..]);
| ^^^^^^ borrowed value does not live long enough
34 | let entry = Entry{ input_data: buffer, parsed_result: cmds };
35 | map.insert(filename.to_string(), entry);
| --------------------------------------- argument requires that `buffer` is borrowed for `'1`
36 | }
| - `buffer` dropped here while still borrowed
error[E0505]: cannot move out of `buffer` because it is borrowed
--> src\main.rs:34:34
|
31 | pub fn parse_and_store(filename: &str, map: &mut HashMap<String, Entry>) {
| --- has type `&mut std::collections::HashMap<std::string::String, Entry<'1>>`
32 | let buffer: Vec<u8> = load_from_file(filename);
33 | let cmds = parse(&buffer[..]);
| ------ borrow of `buffer` occurs here
34 | let entry = Entry{ input_data: buffer, parsed_result: cmds };
| ^^^^^^ move out of `buffer` occurs here
35 | map.insert(filename.to_string(), entry);
| --------------------------------------- argument requires that `buffer` is borrowed for `'1`
最小(非)工作示例
use std::collections::HashMap;
#[derive(Debug, PartialEq)]
struct Cmd<'a> {
name: &'a str
}
fn parse<'a>(input: &'a [u8]) -> Vec<Cmd<'a>> {
Vec::new()
}
fn load_from_file(filename: &str) -> Vec<u8> {
Vec::new()
}
#[derive(Debug, PartialEq)]
struct Entry<'a> {
pub input_data: Vec<u8>,
pub parsed_result: Vec<Cmd<'a>>
}
// pub fn store_and_parse(filename: &str, map: &mut HashMap<String, Entry>) {
// let buffer: Vec<u8> = load_from_file(filename);
// let mut entry = Entry{ input_data: buffer, parsed_result: vec![] };
// let cmds = parse(&entry.input_data[..]);
// entry.parsed_result = cmds;
// map.insert(filename.to_string(), entry);
// }
pub fn parse_and_store(filename: &str, map: &mut HashMap<String, Entry>) {
let buffer: Vec<u8> = load_from_file(filename);
let cmds = parse(&buffer[..]);
let entry = Entry{ input_data: buffer, parsed_result: cmds };
map.insert(filename.to_string(), entry);
}
fn main() {
println!("Hello, world!");
}
编辑:尝试使用 2 个地图
正如 Kevin 所指出的,这也是我第一次(以上尝试)让我失望的原因,借用检查器不明白移动 Vec 不会使切片无效,因为 Vec 的堆缓冲区没有被触及。很公平。
旁注:我忽略了 Kevin 的回答中与使用索引相关的部分(Rust 文档 explicitly states slices are a better replacement for indices,所以我觉得这与语言不符)和使用外部 crate(这也是明确的反对语言)。我正在努力学习和理解如何做到这一点“Rust 方式”,而不是不惜一切代价。
所以我对此的直接反应是更改数据结构:首先将存储空间Vec 插入第一个HashMap,然后调用parse() 函数来创建直接指向@987654356 的切片@ 价值。然后将它们存储到第二个HashMap 中,这会自然地将两者分离。但是,一旦我将所有这些都放在一个循环中,这也不起作用,这是这段代码的更广泛目标:
fn two_maps<'a>(
filename: &str,
input_map: &'a mut HashMap<String, Vec<u8>>,
cmds_map: &mut HashMap<String, Vec<Cmd<'a>>>,
queue: &mut Vec<String>) {
{
let buffer: Vec<u8> = load_from_file(filename);
input_map.insert(filename.to_string(), buffer);
}
{
let buffer = input_map.get(filename).unwrap();
let cmds = parse(&buffer[..]);
for cmd in &cmds {
// [...] Find further dependencies to load and parse
queue.push("...".to_string());
}
cmds_map.insert(filename.to_string(), cmds);
}
}
fn main() {
let mut input_map = HashMap::new();
let mut cmds_map = HashMap::new();
let mut queue = Vec::new();
queue.push("file1.txt".to_string());
while let Some(path) = queue.pop() {
println!("Loading file: {}", path);
two_maps(&path[..], &mut input_map, &mut cmds_map, &mut queue);
}
}
这里的问题是,一旦输入缓冲区在第一个映射 input_map 中,引用它会将每个新解析结果的生命周期绑定到该 HashMap 的条目,因此 &'a mut 引用(@ 987654362@ 生命周期已添加)。如果没有这个,编译器会抱怨数据从input_map 流入cmds_map 的生命周期不相关,这很公平。但是这样一来,&'a mut 对 input_map 的引用在第一次循环迭代中被锁定并且永远不会被释放,并且借用检查器在第二次迭代中阻塞,这是理所当然的。
所以我又卡住了。我在 Rust 中尝试做的事情是完全不合理和不可能的吗?我如何解决问题(算法、数据结构)以使事情终生有效?我真的看不出这里有什么“Rust 方式”来存储缓冲区集合和这些缓冲区上的切片。是唯一的解决方案(我想避免)首先加载所有文件,然后解析它们吗?在我的情况下,这 非常 不切实际,因为大多数文件都包含对其他文件的引用,并且我想加载最小的依赖链(可能
问题的核心似乎是,将输入缓冲区存储到任何类型的数据结构中都需要在插入操作期间对所述数据结构进行可变引用,这与对每个数据结构的长期不可变引用不兼容单个缓冲区(用于切片),因为这些引用需要具有与 HashMap 定义相同的生命周期。是否有任何其他数据结构(可能是不可变的)可以解决这个问题?还是我完全走错了路?
【问题讨论】: