4.5.2 高阶类型
译文 · 基于 Learning Rust
高阶类型
原文链接: https://quinedot.github.io/rust-learning/dyn-hr.html
trait 对象的另一个特性是它们可以对 trait 的生命周期参数高阶:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 带生命周期参数的 trait
trait Look<'s> {
fn method(&self, s: &'s str);
}
// 对任意生命周期都有效的实现
impl<'s> Look<'s> for () {
fn method(&self, s: &'s str) {
println!("Hi there, {s}!");
}
}
fn main() {
// 高阶 trait 对象
// vvvvvvvvvvvvvvvvvvvvvvvv
let _bx: Box<dyn for<'any> Look<'any>> = Box::new(());
}
|
for<'x> 部分是生命周期绑定器,引入高阶生命周期。可以有多个生命周期,可像函数、结构体等上的生命周期参数一样任意命名。
只有对所有生命周期都实现该 trait 时,才能强制转换为高阶 trait 对象。例如以下不行:
1
2
3
4
5
6
7
8
9
10
| # trait Look<'s> { fn method(&self, s: &'s str); }
impl<'s> Look<'s> for &'s i32 {
fn method(&self, s: &'s str) {
println!("Hi there, {s}!");
}
}
fn main() {
let _bx: Box<dyn for<'any> Look<'any>> = Box::new(&0);
}
|
&'s i32 只实现 Look<'s>,而非对所有生命周期 'a 实现 Look<'a>。
类似地,这也不行:
1
2
3
4
5
6
7
8
9
10
| # trait Look<'s> { fn method(&self, s: &'s str); }
impl Look<'static> for i32 {
fn method(&self, s: &'static str) {
println!("Hi there, {s}!");
}
}
fn main() {
let _bx: Box<dyn for<'any> Look<'any>> = Box::new(0);
}
|
用 'static 作为生命周期参数实现 trait,与对任意生命周期参数实现 trait 不是一回事。trait 和 trait 实现没有类似型变的东西;trait 的参数总是不变的,因此实现总是仅针对显式生命周期。
子类型
高阶类型如 dyn for<'any> Look<'any> 与非高阶类型如 dyn Look<'x>(单个生命周期 'x)之间有关系:高阶类型是非高阶类型的子类型。因此可将高阶类型强制转换为任意具体生命周期的非高阶类型:
1
2
3
4
5
6
7
8
| # trait Look<'s> { fn method(&self, s: &'s str); }
fn as_static(bx: Box<dyn for<'any> Look<'any>>) -> Box<dyn Look<'static>> {
bx
}
fn as_whatever<'w>(bx: Box<dyn for<'any> Look<'any>>) -> Box<dyn Look<'w>> {
bx
}
|
注意这仍不是 trait 生命周期参数的型变形式。例如以下失败,因为不能从 dyn Look<'static> 强制转换为 dyn Look<'w>:
1
2
3
4
5
| # trait Look<'s> { fn method(&self, s: &'s str); }
# fn as_static(bx: Box<dyn for<'any> Look<'any>>) -> Box<dyn Look<'static>> { bx }
fn as_whatever<'w>(bx: Box<dyn for<'any> Look<'any>>) -> Box<dyn Look<'w>> {
as_static(bx)
}
|
作为超类型强制转换,从高阶到非高阶即使在协变嵌套上下文中也可应用,与非高阶超类型强制转换一样:
1
2
3
4
5
6
7
| # trait Look<'s> {}
fn foo<'l: 's, 's, 'p>(
v: Vec<Box<dyn for<'any> Look<'any> + 'l>>
) -> Vec<Box<dyn Look<'p> + 's>>
{
v
}
|
Fn trait 和 fn 指针
Fn trait(FnOnce、FnMut 和 Fn)有特殊语法。一方面,写法更像函数,用 (TypeOne, TypeTwo) 列出输入参数,用 -> ResultType 列出关联类型。另一方面,省略的输入生命周期是引入高阶绑定的语法糖。
例如,这两个 trait 对象类型相同:
1
2
3
| fn identity(bx: Box<dyn Fn(&str)>) -> Box<dyn for<'any> Fn(&'any str)> {
bx
}
|
这与函数声明中省略生命周期的做法类似,确实,相同的输出生命周期省略规则也适用:
1
2
3
4
5
6
| // 省略的输入生命周期变为高阶生命周期
// 省略的输出生命周期与单个输入生命周期相同
// (在绑定器之下)
fn identity(bx: Box<dyn Fn(&str) -> &str>) -> Box<dyn for<'any> Fn(&'any str) -> &'any str> {
bx
}
|
1
2
3
4
5
| // 不能编译,因为输出生命周期应是什么被认为有歧义
fn ambiguous(bx: Box<dyn Fn(&str, &str) -> &str>) {}
// 一种可能的修复,也是绑定器中多个生命周期的例子
fn first(bx: Box<dyn for<'a, 'b> Fn(&'a str, &'b str) -> &'a str>) {}
|
函数指针是 Rust 中可高阶的另一类类型。它们与函数声明和 Fn trait 有类似语法和糖。
1
2
3
| fn identity(fp: fn(&str) -> &str) -> for<'any> fn(&'any str) -> &'any str {
fp
}
|
语法不一致
函数声明、函数指针类型和 Fn trait 在输入参数「名称」方面存在一些不一致。
首先,只有函数(方法)声明可以使用 self 等接收者简写:
1
2
3
4
5
| # struct S;
impl S {
fn foo(&self) {}
// ^^^^^
}
|
此例外相当 unsurprising,因为 Self 别名仅存在于这些实现块内。
函数声明中每个非 self 参数是不可反驳模式后跟类型注解。省略模式是错误;若不使用参数(无需命名),至少要用通配符模式。
1
2
| fn this_works(_: i32) {}
fn this_fails(i32) {}
|
对此规则有意外例外,但在 Edition 2018 中移除,因此仅在 Edition 2015 可用。
相比之下,函数指针中每个参数可以是
- 标识符后跟类型注解(
i: i32) _ 后跟类型注解(_: i32)- 仅类型名(
i32)
因此这些都有效:
1
2
3
| let _: fn(i32) = |_| {};
let _: fn(i: i32) = |_| {};
let _: fn(_: i32) = |_| {};
|
但实际模式不允许:
1
| let _: fn(&i: &i32) = |_| {};
|
惯用形式是仅使用类型名。
函数指针参数中允许重名, 但这是没有函数体的属性——因此在 trait 方法声明等中也可能。也与上面 Edition 2015 匿名函数参数例外相关,可能最终弃用。
1
2
3
4
5
| trait Trait {
fn silly(a: u32, a: i32);
}
let _: fn(a: u32, a: i32) = |_, _| {};
|
最后,Fn trait 中每个参数只能是类型名:不允许标识符、_ 或模式。
1
2
3
4
| // 都不能编译
let _: Box<dyn Fn(i: i32)> = Box::new(|_| {});
let _: Box<dyn Fn(_: i32)> = Box::new(|_| {});
let _: Box<dyn Fn(&_: &i32)> = Box::new(|_| {});
|
为何有差异?一个原因是模式在语法上与匿名参数不兼容,显然。 我不确定为何函数指针接受标识符,或更一般为何 Fn 糖与函数指针类型不一致。最简单的解释是函数指针先有可命名参数,Fn 糖用于也没有名称的 trait 输入类型参数。
高阶 trait 约束
也可对泛型类型参数应用高阶 trait 约束(HRTB),语法相同:
1
2
3
4
5
6
7
| # trait Look<'s> { fn method(&self, s: &'s str); }
fn box_it_up<'t, T>(t: T) -> Box<dyn for<'any> Look<'any> + 't>
where
T: for<'any> Look<'any> + 't,
{
Box::new(t)
}
|
Fn 类 trait 的糖在此也适用。你可能已在接受闭包的方法上见过此类约束:
1
2
3
4
5
6
7
8
9
10
| # struct S;
# impl S {
fn map<'s, F, R>(&'s self, mut f: F) -> impl Iterator<Item = R> + 's
where
F: FnMut(&[i32]) -> R + 's
{
// 这部分不是重点 ;-)
[].into_iter().map(f)
}
# }
|
该约束实际是 F: for<'x> FnMut(&'x [i32]) -> R + 's。
高阶类型就讲到这里
希望这能让你对高阶类型、HRTB 及其与 Fn trait 的关系有 decent 概览。可以想象,闭包等相关概念还有更多细节和细微差别。不过,这些主题值得独立指南,因此在本 dyn Trait 概览中不会过多涉及高阶类型。