可变结构中的生命周期与 HashSet

4

我不太明白为什么Rust不喜欢我的remove_str方法:

use std::cell::RefCell;
use std::collections::HashSet;


#[derive(Hash, Eq, PartialEq)]
struct StringWrap<'a>{
    s: &'a String,
}

struct Container<'a>{
    m: HashSet<StringWrap<'a>>
}

impl<'a> Container<'a>{
    fn remove_str(&mut self, s: &str){
        let string = String::from(s);
        let to_remove = StringWrap{s: &string};
        self.m.remove(&to_remove);
    }
}

它会出现以下问题:

error[E0597]: `string` does not live long enough
  --> tests/worksheet.rs:17:39
   |
14 | impl<'a> Container<'a>{
   |      -- lifetime `'a` defined here
...
17 |         let to_remove = StringWrap{s: &string};
   |                                       ^^^^^^^ borrowed value does not live long enough
18 |         self.m.remove(&to_remove);
   |         ------------------------- argument requires that `string` is borrowed for `'a`
19 |     }
   |     - `string` dropped here while still borrowed

据我所见,我的stringto_remove的寿命足够长,可以允许.remove调用完成其工作。这是因为remove可能是异步的还是其他什么原因呢?
感谢任何帮助或见解!

5
为什么你一开始要使用生命周期?似乎是因为你在StringWrap结构体中使用了s: &String,但这并不必要--只需使用s: String,就不需要生命周期了。 - Herohtar
1
我认为这是一个非常好的问题。我不知道为什么它会被踩和被关闭投票。很明显HashSet可能包含拥有的字符串,但是OP请求帮助更好地理解为什么这行不通。 - at54321
2个回答

4
据我所见,我的字符串和to_remove的寿命足够长,以允许.remove调用完成其工作。这是因为remove可能是异步的或类似于此类的东西吗?
不是,这是因为HashSet::remove必须使用某些东西调用,该项在借用时变为该东西:
pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool
where
    T: Borrow<Q>,
    Q: Hash + Eq, 

然而,除非您手动实现 Borrow 以供 StringWrap 使用,否则只会应用通用的自反实现,因此 remove 只能使用类型为 &StringWrap<'a>value。请注意生命周期要求。
要使其正常工作,您需要为 StringWrap 实现 Borrow。例如,您可以执行以下操作:
impl Borrow<str> for StringWrap<'_> {
    fn borrow(&self) -> &str {
        self.s
    }
}

然后Container::remove_str只需将其参数转发到HashMap::remove

impl Container<'_> {
    fn remove_str(&mut self, s: &str) {
        self.m.remove(s);
    }
}

在游乐场上查看

尽管如此,在 HashSet 中存储引用是相当不寻常的:通常情况下,您会将存储的 String 的所有权移动到集合中,这将使这个问题无关紧要,因为没有生命周期参与。


1
很有趣。这让我想到为什么remove需要一个&Q而不是&T,但我决定发布一个单独的问题 - at54321
谢谢!我使用引用的原因是因为我有大量的原始数据(基本上是字符串),以及几个包含不同集合的哈希集,所以我不想一直复制这些字符串。 - jeje
在这种情况下,您可能会发现存储Rc<String>几乎同样有效,同时仍然避免生命周期的麻烦。 - eggyal
1
此外,&String 几乎总是应该用 &str 代替(Rc<str> 也是一个不错的选择)。 - Chayim Friedman

-1
在 HashSet::remove/take/get(arg1: &Q) 的上下文中:
这些内部调用将尝试从 HashSet 内部获取“T的借用版本Q”,并通过 Borrow trait 为 T 将其与 arg1 进行比较。因此,使用适当生命周期的自定义 impl Borrow for T 是一个好主意。

这篇回答有什么超过优秀现有回答的地方吗? - Chayim Friedman

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