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 概览中不会过多涉及高阶类型。

最后修改 August 23, 2026: 更新 (499855b16)