在学习 lifetimekata 的 lifetime,学到了最后一个挑战 ( https://tfpk.github.io/lifetimekata/chapter_9.html ) ,需要让下面的代码执行正确,同时文章也给了提示 ( https://github.com/rust-lang/rust/issues/73788 ),但还是不太明白,请教一下各位如何改? 并说明一下推理过程, 感谢!
use std::collections::HashSet;
struct Difference<'first, 'second> {
first_only: Vec<&'first str>,
second_only: Vec<&'second str>
}
fn find_difference<'fst,'snd>(sentence1: &'fst str, sentence2: &'snd str) -> Difference<'fst, 'snd> {
let sentence_1_words: HashSet<&str> = sentence1.split(" ").collect();
let sentence_2_words: HashSet<&str> = sentence2.split(" ").collect();
Difference {
first_only: (&sentence_1_words - &sentence_2_words).into_iter().collect(),
second_only: (&sentence_2_words - &sentence_1_words).into_iter().collect(),
}
}
fn main() {
let first_sentence = String::from("I love the surf and the sand.");
let second_sentence = String::from("I hate the surf and the sand.");
let first_only = {
let third_sentence = String::from("I hate the snow and the sand.");
let diff = find_difference(&first_sentence, &third_sentence);
diff.first_only
};
assert_eq!(first_only, vec!["hate", "surf"]);
let second_only = {
let third_sentence = String::from("I hate the snow and the sand.");
let diff = find_difference(&third_sentence, &second_sentence);
diff.second_only
};
assert_eq!(second_only, vec!["snow"]);
}
playground 链接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d89af7ce5f834f38d6caf0400eea2f3c
Ext Link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d89af7ce5f834f38d6caf0400eea2f3c
1
共 14 条评论, 1 页
评论区
写评论问题是hashset的sub操作
这里的
T:'first+'second
混合了额外的生命周期。 自己单独写一个函数/接口会比较好这主要得益于 covariance,比如这样:例 1
它正常运行,并且与局部作用域无关。
而 例 2,invariance 的加入不会阻止在其之前发生 covariance,但会维持之后的生命周期不变。
对于这个问题里的例子,问题的关键的确在于 difference 的签名约束了 第二个参数,见 例 3,它是对此次问题的最简代码错误复现(注意,它并没有使用到 invariance!),并且包含一个看似可行的解决办法 —— 让 difference 的两个参数有各自的生命周期:
fn difference<'a, 'b>(&'a self, other: &'b HashSet<T, S>) -> Difference<'a, 'b, T, S>
但其实这并不可行,因为这个 API 依然只接受相同的 T,所以没有改变原问题中
T: 'fst
和T: 'snd
两个隐含约束,但我说了,这里问题的关键在于,我们不想要T: 'snd
的限制,所以你根本无法修复这个 API 来解决问题(不信的话自己去用 GAT 和 newtype 修正,你得不到解决方案),从而需要提出新的 API。适用于这个问题的新的 API 候选签名(没有写完整的约束):
fn difference<'a, 'b>(&'a HashSet<T, S>, other: &'b HashSet<U, S>) -> Difference<'a, 'b, T, U, S>
fn difference<'a, 'b, 'c>(&'c HashSet<&'a T, S>, other: &'c HashSet<&'b T, S>) -> Difference<'a, 'b, 'c, T, S>
(这里使用相同的 'c 是因为 covariance 会处理好我们想要的临时引用;一楼的方案和这个在核心上一致,虽然你看到标注生命周期略有不同)我觉得不用专门学,你的代码作风已经很符合Haskell派的。
--
👇
苦瓜小仔: > 看你的代码就知道你肯定是Haskell爱好者
我不是。我没有学过 Haskell :(
我不是。我没有学过 Haskell :(
谢谢,受益匪浅
--
👇
苦瓜小仔: > 所以根本不能收集成 Vec 去断言!
如果有人想知道怎么写一个返回迭代器而不是 Vec 的函数的话...
playground
顺便把结构体的 Vec 修正成了 HashSet :)
大师,看你的代码就知道你肯定是Haskell爱好者😁
对了,原代码不保证vec元素的顺序是因为HashSet用的杂凑函数本来有随机性,不是因为多线程的竞态条件。我原来看到报错消息含thread的时候想歪了。
--
👇
苦瓜小仔:
给那个 issue 留言了
如果有人想知道怎么写一个返回迭代器而不是 Vec 的函数的话...
playground
顺便把结构体的 Vec 修正成了 HashSet :)
一楼给了一个仿照标准库 Difference 结构体的例子。
但这并不是标准库生命周期过度约束的问题,而是需要一个针对 T 为引用时的 API 来让例子中的代码通过。
你完全可以自己写一个函数:
playground
它就能让例子通过。
这个函数的生命周期标注告诉了我们问题出在哪:
我们的返回值只需要元素为
&'a T
的集合的一部分,而不需要元素为&'b T
的集合(的一部分)。这正是这个例子核心的要点。它之所以能做到,是因为
fn contains<Q>(&self, value: &Q) -> bool
方法对&Self
和&Q
有各自的生命周期。标准库的
fn difference<'a>(&'a self, other: &'a HashSet<T, S>) -> Difference<'a, T, S>
做不到,是因为这个 API 只接受相同的 T —— 当 T 为引用时,它们应具有相同的生命周期!而例子中,两个集合有不同的生命周期,从而引用的元素不一样 —— 一个是HashSet<&'fst str>
,另一个是HashSet<'snd str>
,从而让它们类型一致,必须添加编译器建议的'fst: 'snd
和'snd: 'fst
,来保证元素的类型一致。是的生命周期会影响类型,这在大多数情况下我们感受不到,但在这里,就是关键所在。此外,例子其实有误:
估计是吧,不过两个生命周期可能会让代码写起来更方便。不过,这需要你重新实现集合差集的计算。前面的人借鉴github上的贴子已经做好了。
--
👇
wukong: 我理解如果这样改的话,就不用两个生命周期了。 见 ( https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3f74520bbf5120466d2dd34f7b6b0679 )
应该是作者就要这样设计题目,通过题目加强对 lifetime 的理解和实践。
我理解如果这样改的话,就不用两个生命周期了。 见 ( https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3f74520bbf5120466d2dd34f7b6b0679 )
应该是作者就要这样设计题目,通过题目加强对 lifetime 的理解和实践。
--
👇
hax10: 这段代码会通过HashSet的difference方法比较一下两个集合里的单词,计算出哪些单词只出现在第一集合或只出现在第二集合里。
原代码存在几个方面的问题:
difference()返回的Difference值包含两个向量vec,即first_only和second_only。difference()会假设这两个向量拥有同样的生命周期,但代码要求difference()把结果放在两个生命周期不一定相同的Difference结构体里。这也是github的贴子上提到的问题,按理说difference()应该接受两个生命周期不一样的情况,但是一开始没这么做,现在为了保持跟现有代码的兼容性,就不扩生命周期了。为了解决问题可以通过where条款告诉编译器你的两个生命周期是相同的,或者直接改Difference结构体的定义也可以。更复杂的解决办法你可以自己去研究。
third_sentence不应该定义在代码块里头,把它拿出来才能让它不被删掉。
两个assert_eq断言都有问题。
因为竞态条件的原因,first_only里面的两个单词出现的顺序不能保证。
能运行的代码:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=528f409da2fd79c1f0633fcf77528b2a
这段代码会通过HashSet的difference方法比较一下两个集合里的单词,计算出哪些单词只出现在第一集合或只出现在第二集合里。
原代码存在几个方面的问题:
difference()返回的Difference值包含两个向量vec,即first_only和second_only。difference()会假设这两个向量拥有同样的生命周期,但代码要求difference()把结果放在两个生命周期不一定相同的Difference结构体里。这也是github的贴子上提到的问题,按理说difference()应该接受两个生命周期不一样的情况,但是一开始没这么做,现在为了保持跟现有代码的兼容性,就不扩生命周期了。为了解决问题可以通过where条款告诉编译器你的两个生命周期是相同的,或者直接改Difference结构体的定义也可以。更复杂的解决办法你可以自己去研究。
third_sentence不应该定义在代码块里头,把它拿出来才能让它不被删掉。
两个assert_eq断言都有问题。
因为竞态条件的原因,first_only里面的两个单词出现的顺序不能保证。
能运行的代码:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=528f409da2fd79c1f0633fcf77528b2a
谢谢!👍🏻
--
👇
Grainspring: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=95ac4258edd7cddb2a3de3116589348b
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=95ac4258edd7cddb2a3de3116589348b