1

我正在尝试将HashMap文本文件的解析操作的结果存储到一个文本文件中(使用nom解析)。结果由一个Vec缓冲区和该缓冲区上的一些切片组成。目标是将它们一起存储在一个元组或结构中,作为哈希映射中的一个值(带有String键)。但我无法解决终身问题。

语境

解析本身采用&[u8]并返回一些包含同一输入上的切片的数据结构,例如

struct Cmd<'a> {
  pub name: &'a str
}

fn parse<'a>(input: &'a [u8]) -> Vec<Cmd<'a>> {
  [...]
}

现在,因为解析在没有存储的切片上运行,所以我需要首先将输入文本存储在 aVec中,以便输出切片保持有效,例如:

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将在函数结束时被删除。它忽略了cmdsandbuffer具有相同生命周期的事实,并且都(我希望)移入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 张地图

正如凯文指出的那样,这就是我第一次(以上尝试)让我失望的原因,借用检查器不明白移动 aVec不会使切片无效,因为Vec没有触及 的堆缓冲区。很公平。

旁注:我忽略了凯文的回答中与使用索引相关的部分(Rust 文档明确指出 slices 是 index 的更好替代品,所以我觉得这对语言不利)和使用外部板条箱(这也是明确的反对语言)。我正在努力学习和理解如何做到这一点“Rust 方式”,而不是不惜一切代价。

所以我对此的直接反应是改变数据结构:首先将存储Vec插入到 firstHashMap中,一旦它在那里调用parse()函数来创建直接指向HashMap值的切片。然后将它们存储到 secondHashMap中,这自然会使两者分离。但是,一旦我将所有这些都放在一个循环中,这也不起作用,这是这段代码的更广泛目标:

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);
    }
}

这里的问题是,一旦输入缓冲区在第一个 mapinput_map中,引用它会将每个新解析结果的生命周期绑定到 that 的条目,HashMap因此是&'a mut引用('a添加的生命周期)。如果没有这个,编译器会抱怨数据从不相关的生命周期流入input_mapcmds_map这很公平。但是这样一来,对的&'a mut引用input_map在第一次循环迭代中被锁定并且永远不会被释放,并且借用检查器在第二次迭代中阻塞,这是理所当然的。

所以我又被困住了。我在 Rust 中尝试做的事情是完全不合理和不可能的吗?我如何解决问题(算法、数据结构)以使事情终生有效?我真的看不出这里有什么“Rust 方式”来存储缓冲区集合和这些缓冲区上的切片。是唯一的解决方案(我想避免)首先加载所有文件,然后解析它们吗?这在我的情况下是非常不切实际的,因为大多数文件都包含对其他文件的引用,我想加载最小的依赖链(可能 < 10 个文件),而不是整个集合(类似于 3000 多个文件),我可以仅通过解析每个文件来访问依赖项。

问题的核心似乎是,将输入缓冲区存储到任何类型的数据结构中都需要在插入操作期间对所述数据结构进行可变引用,这与对每个单个缓冲区的长期不可变引用不兼容(对于切片),因为这些引用需要具有与定义相同的生命周期HashMap。是否有任何其他数据结构(可能是不可变的)可以解决这个问题?还是我完全走错了路?

4

1 回答 1

1
于 2020-11-08T01:51:37.887 回答