在 for 循环中处理 "borrowed value does not live long enough"

Dealing with "borrowed value does not live long enough" within for loops

我正在抓取的网站要求我查询 HTML 页面的标题标签以及一些其他元素,以查看我是否可以辨别文章的标题。

我创建了一个 HashMap<&str, u8> 并立即 .insert(title_tag_text, 1),查询 header 元素,然后我希望将 header 标签的文本插入哈希映射同样,但我收到错误 borrowed value does not live long enough.

我不确定我是否理解,因为我认为我正确地将 std::string::String 取消引用为应该实现 Copy 特征的 &str?不幸的是,我怀疑我计划实施的下一个代码有类似的问题。

let mut title_candidates: HashMap<&str, u8> = HashMap::new();

let title_tag_text: String = Selector::parse("title")
    .ok()
    .and_then(|selector| html_document.select(&selector).next())
    .map(|elem| elem.inner_html())?;

title_candidates.insert(&*title_tag_text, 1);

Selector::parse("h1, h2, h3, .title")
    .ok()
    .as_ref()
    .map(|selector| html_document.select(selector))?
    .map(|elem| elem.inner_html()) // std::string::String
    .for_each(|title| {
        *title_candidates.entry(&*title).or_insert(0) += 1;
        // if title_tag_text.contains(&*title.as_str()) {
        //     *title_candidates.entry(&*title_tag_text) += 1;
        // }
    });

error[E0597]: `title` does not live long enough
   --> src/main.rs:140:39
    |
125 |     let mut title_candidates: HashMap<&str, u8> = HashMap::new();
    |         -------------------- lifetime `'1` appears in the type of `title_candidates`
...
140 |             *title_candidates.entry(&*title).or_insert(0) += 1;
    |              -------------------------^^^^^-
    |              |                        |
    |              |                        borrowed value does not live long enough
    |              argument requires that `title` is borrowed for `'1`
...
144 |         });
    |         - `title` dropped here while still borrowed

您的 HashMap 的键是 &str 类型。这意味着 HashMap 仅持有对 str 引用 而不是 str 本身。因此,要使 HashMap 中的数据有效,对 str 的引用至少应与 HashMap 一样长。现在的问题是,String 是在 .map(|elem| elem.inner_html()) 中创建的,因此在该语句完成后它会被删除。

相反,创建一个 HashMap,它使用 owned Strings 而不是引用。以下是您可以根据自己的情况进行调整的简化示例:

fn main() {
    let mut data: HashMap<String, i32> = HashMap::new();

    (0..20)
        .map(|i| (i % 10).to_string())
        .for_each(|text| {
            *data.entry(text).or_insert(0) += 1;
        });
}

在这里,.map(|i| (i % 10).to_string()) 创建了一个 String,然后将其所有权传递给 data.entry(text) 中的 HashMap,从而避免了引用生命周期中的任何不匹配。

Rust Playground

您案例的问题很常见,我曾多次偶然发现。 Rust 没有垃圾收集器,这是众所周知的,但我们通常很难理解它的实际含义。

在您的例子中,您试图在地图中存储对字符串的引用,该字符串仅存在 for_each 函数。

问题来了,那个函数returns时会发生什么? 当该函数 returns 在该函数中创建的对象将是 freed 并且如果您 &str 指向的字符串被释放,您的 &str 将指向一个位置不再属于你

如果您想使用引用,您需要确保只要引用还在使用,它们引用的内容就会存在。

在你上面的例子中,简单地使用一个拥有的字符串将解决这个问题,在这种情况下,这个问题将由 hashmap 拥有,并且只要 hashmap 存在,就会存在。

所以你应该将你的 hashmap 签名编辑为 HashMap<String,u8> 并在插入时使用 .to_string().to_owned()

简单地传递 &str 的拥有副本