在for循环中处理“borrowed value does not live long enough”问题

4

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

我创建了一个 HashMap<&str, u8> 并立即 .insert(title_tag_text, 1),查询标题元素,然后希望以类似的方式将标题标签的文本插入哈希映射表中,但是我收到了错误消息 borrowed value does not live long enough

我不确定我是否理解正确,因为我认为我已经正确地将 std::string::String 解引用为 &str,这应该实现了 Copy 特性?不幸的是,我怀疑我计划实现的下一段代码也有类似的问题。

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

欢迎来到Stack Overflow!很难回答你的问题,因为它没有包含[MRE]。我们无法确定代码中存在哪些crate(及其版本)、类型、特性、字段等。如果可能的话,您可以尝试在Rust Playground上重现错误,否则可以在全新的Cargo项目中进行,然后[编辑]您的问题以包含额外的信息。这里有Rust-specific MRE tips,您可以使用它们来缩小发布在此处的原始代码。谢谢! - Shepmaster
2个回答

6
你的 HashMap 的键是 &str 类型。这意味着 HashMap 仅保存对 str 的引用,而不是 str 本身。因此,为了使 HashMap 中的数据有效,对 str 的引用应该至少和 HashMap 一样长。现在问题是,String 是在 .map(|elem| elem.inner_html()) 中创建的,因此在完成该语句后会被删除。
相反,可以创建一个使用拥有所有权的 StringHashMap。下面是一个简化的示例,你可以根据你的情况进行调整:
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 ,其所有权随后传递给 HashMap 中的 data.entry(text),从而避免了引用的生命周期不匹配。 Rust Playground

0
你的情况存在一个非常常见的问题,我多次遇到过。 Rust没有垃圾回收器,这是众所周知的,但我们经常很难理解这个问题的实际影响。
在你的情况下,你试图在map中存储一个字符串的引用,该引用仅存在于for_each函数中。
问题来了,当该函数返回时会发生什么? 当该函数返回时,在该函数中创建的对象将被释放,如果你的&str指向的字符串被释放,那么你的&str将指向一个不再属于你的位置。
如果你想使用引用,你需要确保它们所引用的内容在引用使用期间一直存在。
在你上面的情况下,简单地使用一个拥有的字符串将解决这个问题,该字符串将由哈希映射拥有,并且只要哈希映射存在,它就会一直存在。
所以你应该编辑你的哈希映射签名为HashMap,并在插入时使用.to_string()或.to_owned()传递一个拥有的副本的&str。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接