大佬Potato TooLarge, 这个文章,把我搞懵逼了
https://zhuanlan.zhihu.com/p/194156624
fn foobar<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &&'k str) -> Option<&'b &'a str>
where 'a: 'b, 'b: 'k
{
set.get(key)
}
问题一:
key: &&'k str
为什么有两个&&
, 不应该只有一个&
问题二:
Option<&'b &'a str>
多个生命周期不应该是,
分割的嘛,这里怎么是空格?
问题三:
我还是看不懂为什么去掉&
,它就能跑起来了,我再理解理解。
1
共 5 条评论, 1 页
评论区
写评论推荐两个Rust生命周期相关的中文资料,希望对理解生命周期有所帮助。
LRFRC系列:Rust生命周期基础 https://mp.weixin.qq.com/s?__biz=MzIxMTM0MjM4Mg==&mid=2247483819&idx=1&sn=777ae853e12ed9259f6e0b6de9e9c34c
LRFRC系列:全面理解Rust生命周期 https://mp.weixin.qq.com/s?__biz=MzIxMTM0MjM4Mg==&mid=2247483826&idx=1&sn=5b3fe0e8b51d5cafb01db1f4a441be66
&'k &'a str
是引用的引用啊,描述的是一个在'a
作用域内的共享引用基础上,产生或者说需要一个 `'k 作用域的共享引用。'a: 'k
关系才能通过编译。'k
,那么你传在'a
作用域有效的引用也行。当然,
&'k &'a str
也是一种类型,而且它与&'a str
是不同的类型。这一点在我给的例子里已经验证过了。Rust Book 讲得很清楚
src: Validating References with Lifetimes
所以,一个
&'a str
并不能说明什么,重要的是 函数签名中生命周期定义/指明的各种引用关系 。fn foobar3<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &'k &'a str) -> Option<&'b &'a str>
key: &'k &'a str
是什么意思啊,是表示key要同时满足k, a着两个生命周期的str类型么?Option<&'b &'a str>
也是这个意思么?我晕的原因是看到的所有资料里都是
key: &'a str
,没见过这样写的--
👇
苦瓜小仔: 这要从生命周期、函数签名一起分析:
#![allow(dead_code)] use std::collections::HashSet; fn main() { let hashset: HashSet<&'static str> = "a b c".split(' ').collect(); println!("{:?}", hashset.get("a")); // println!("{:?}", foobar_(&hashset, "a")); println!("{:?}", foobar_(&hashset, &"a")); println!("{:?}", foobar(&hashset, "a")); println!("{:?}", foobar(&hashset, &"a")); } // HashSet 的 get 方法签名为: // pub fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow<Q>, Q: Hash + Eq // 根据生命周期省略规则 3,有: // pub fn get<'q, Q: ?Sized>(&'q self, value: &'q Q) -> Option<&'q T> where T: Borrow<Q>, Q: Hash + Eq // T 在这里用 &'t str 描述;而 T: Borrow<Q> 指明 (&'t T).borrow() -> &'t T -> &'t Q 关系,即 T 的借用必须至少比 Q 的借用长 // 那么不难写出这样的函数: fn foobar_<'t, 'q>(set: &'q HashSet<&'t str>, key: &'q &'t str) -> Option<&'q &'t str> where 't: 'q, { set.get(key) } // 但是我这里写了一个非常具体而特殊的情况:T 为 &'static str // 所以函数 foobar_ 有一个限制,参数 key 不得使用 "a",因为: // key: &'q &'t str,而我们知道,"a" 的类型是 &'static str,显然不满足签名, // 所以必须 foobar_(&hashset, &"a") 才能满足签名 // 可以利用 共享借用具有长的生命周期自动转换成短生命周期的性质 // 见 https://doc.rust-lang.org/rust-by-example/scope/lifetime/lifetime_coercion.html // 这条性质不起眼,但是很重要,因为这表明可以把 &'static str 转换成 &'q str // 从而修改成函数 foobar,让调用方式更简洁 foobar(&hashset, "a") // foobar 也符合 HashSet::get 要求的 T: Borrow<Q> // 因为 &'b &'t str 是 &'t str 的一个借用,所以 &'t str 一定至少比 &'b &'t str 长 fn foobar<'t, 'q>(set: &'q HashSet<&'t str>, key: &'q str) -> Option<&'q &'t str> where 't: 'q, { set.get(key) }
回到你的问题:
问题 1 和 3
key: &&'k str
的形式是不正确的,因为编译器没法推断生命周期时,你需要指明所有生命周期fn foobar3<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &'k &'a str) -> Option<&'b &'a str> where 'a: 'b, 'b: 'k, { set.get(key) }
而且可以只有一个 &,因为共享借用具有长的生命周期自动转换成短生命周期的性质;
此外,你会发现,你传给函数
&&&&&&&&&&&&&& something
和&something
都可以通过编译,因为 Coercion types 第 1-2 条规则:典型的:共享借用具有长的生命周期自动转换成短生命周期的性质
'a: 'b
条件中,'a 是 'b 的 subtype,表示 'a 生命周期至少比 'b 长 ('a outlives 'b);'static 比所有生命周期长,故 'static 是所有生命周期的子类型,故 'static 可以强制转换成各种小作用域里的生命周期。所以,你写各种形式的借用有时都没问题: playground
见:
问题 2
多个共享借用符号 & 无需分隔 &&,多个生命周期(以及独占借用符号
&mut &mut
、&'a mut
)应该空格分隔的,如果逗号分隔,怎么和函数参数区别开来呢?&'b &'a str 写成 &'b, &'a str 吗?
我也晕了,我贴一下我的理解吧,望路过的大大们斧正。
首先是失败的情况:
fn foobar<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &&'k str) -> Option<&'b &'a str> where 'a: 'b { // pub fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow<Q>, ... // Self: &'b HashSet<&'a str> // Q: &'k str // T: &'a str // T: Borrow<Q> 在这里等价于:&'a str: Borrow<&'k str>,这个约束不可能被满足(因为生命周期之间没有子类型关系),标准库中也没有对应的实现。 set.get(key)// 约束得不到满足,编译失败了。 }
然后是成功的情况:
fn foobar<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &'k str) -> Option<&'b &'a str> where 'a: 'b { // pub fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow<Q>, ... // Self: &'b HashSet<&'a str> // Q: str // T: &'a str // T: Borrow<Q> 在这里等价于:&'a str: Borrow<str>,这个约束是合理的,标准库中有对应的实现:https://doc.rust-lang.org/std/borrow/trait.Borrow.html#impl-Borrow%3CT%3E set.get(key) // 约束得到满足,编译成功了。 }
如果只想通过编译的话,下面这种方式也可以。但这种方式在实践中是不合理的,对
key
的生命周期过度约束了。fn foobar<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &&'k str) -> Option<&'b &'a str> where 'a: 'b, 'k: 'a { // pub fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow<Q>, ... // Self: &'b HashSet<&'a str> // Q: &'k str // T: &'a str // T: Borrow<Q> 看起来是 &'a str: Borrow<&'k str>,但因为存在`'k: 'a`的约束,通过型变能够得到:&'a str: Borrow<&'a str>,这个约束在标准库中有对应的实现:https://doc.rust-lang.org/std/borrow/trait.Borrow.html#impl-Borrow%3CT%3E-4 set.get(key)// 约束得到满足,编译通过了。 }
这要从生命周期、函数签名一起分析:
#![allow(dead_code)] use std::collections::HashSet; fn main() { let hashset: HashSet<&'static str> = "a b c".split(' ').collect(); println!("{:?}", hashset.get("a")); // println!("{:?}", foobar_(&hashset, "a")); println!("{:?}", foobar_(&hashset, &"a")); println!("{:?}", foobar(&hashset, "a")); println!("{:?}", foobar(&hashset, &"a")); } // HashSet 的 get 方法签名为: // pub fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow<Q>, Q: Hash + Eq // 根据生命周期省略规则 3,有: // pub fn get<'q, Q: ?Sized>(&'q self, value: &'q Q) -> Option<&'q T> where T: Borrow<Q>, Q: Hash + Eq // T 在这里用 &'t str 描述;而 T: Borrow<Q> 指明 (&'t T).borrow() -> &'t T -> &'t Q 关系,即 T 的借用必须至少比 Q 的借用长 // 那么不难写出这样的函数: fn foobar_<'t, 'q>(set: &'q HashSet<&'t str>, key: &'q &'t str) -> Option<&'q &'t str> where 't: 'q, { set.get(key) } // 但是我这里写了一个非常具体而特殊的情况:T 为 &'static str // 所以函数 foobar_ 有一个限制,参数 key 不得使用 "a",因为: // key: &'q &'t str,而我们知道,"a" 的类型是 &'static str,显然不满足签名, // 所以必须 foobar_(&hashset, &"a") 才能满足签名 // 可以利用 共享借用具有长的生命周期自动转换成短生命周期的性质 // 见 https://doc.rust-lang.org/rust-by-example/scope/lifetime/lifetime_coercion.html // 这条性质不起眼,但是很重要,因为这表明可以把 &'static str 转换成 &'q str // 从而修改成函数 foobar,让调用方式更简洁 foobar(&hashset, "a") // foobar 也符合 HashSet::get 要求的 T: Borrow<Q> // 因为 &'b &'t str 是 &'t str 的一个借用,所以 &'t str 一定至少比 &'b &'t str 长 fn foobar<'t, 'q>(set: &'q HashSet<&'t str>, key: &'q str) -> Option<&'q &'t str> where 't: 'q, { set.get(key) }
回到你的问题:
问题 1 和 3
key: &&'k str
的形式是不正确的,因为编译器没法推断生命周期时,你需要指明所有生命周期fn foobar3<'a, 'b, 'k>(set: &'b HashSet<&'a str>, key: &'k &'a str) -> Option<&'b &'a str> where 'a: 'b, 'b: 'k, { set.get(key) }
而且可以只有一个 &,因为共享借用具有长的生命周期自动转换成短生命周期的性质;
此外,你会发现,你传给函数
&&&&&&&&&&&&&& something
和&something
都可以通过编译,因为 Coercion types 第 1-2 条规则:典型的:共享借用具有长的生命周期自动转换成短生命周期的性质
'a: 'b
条件中,'a 是 'b 的 subtype,表示 'a 生命周期至少比 'b 长 ('a outlives 'b);'static 比所有生命周期长,故 'static 是所有生命周期的子类型,故 'static 可以强制转换成各种小作用域里的生命周期。所以,你写各种形式的借用有时都没问题: playground
见:
问题 2
多个共享借用符号 & 无需分隔 &&,多个生命周期(以及独占借用符号
&mut &mut
、&'a mut
)应该空格分隔的,如果逗号分隔,怎么和函数参数区别开来呢?&'b &'a str 写成 &'b, &'a str 吗?