在Rust2024
中,impl Trait
在中
位置的默认工作方式
有了变化.是为了简化impl Trait
,以更好地匹配
人们一般的需求
.
还添加了一个灵活的语法
,让你需要
时可完全控制
.
从Rust2024
开始,一直在更改,何时可在返回位置impl Trait
的隐藏
类型中使用泛型参数
的规则:
1,即对返回位置impl Trait
的隐藏类型
,的新的默认值
,可在域中用任意泛型参数
,而不仅
是(仅适合Rust2024
)类型
;
2,用来显式声明
可用哪些类型
的一个语法
(可在任意版本
中使用).
新的显式语法
叫"用约束"
:如,impl Trait+use<'x,T>
表示允许隐藏类型
使用'x
和T
(但不能使用域内
的其他泛型参数
).
背景:返回位置impl Trait
本篇涉及返回位置impl Trait
,如以下示例:
cpp
fn process_data(
data: &[Datum]
) -> impl Iterator<Item = ProcessedDatum> {
data
.iter()
.map(|datum| datum.process())
}
在此返回位置``使用->impl Iterator
,表明该函数
返回实际类型
由编译器根据函数体确定
的"某种迭代器
".它叫做"隐藏类型
",因为调用者
无法确切
地知道它是什么
;
他们必须针对Iterator
特征编码.但是,在生成代码
时,编译器根据实际的精确类型
生成代码,从而确保充分优化调用者
.
尽管调用者
不知道确切的类型
,但确实需要知道它继续借用数据
参数,这样可确保在迭代
时数据引用保持有效
.
此外,调用者必须可无需查看函数体
,仅根据类型签名
来弄清楚它.
Rust
的当前规则
是,返回位置impl Trait
值只有在impl Trait
自身中有引用的生命期
时,才能使用引用.此例中,impl Iterator<Item = ProcessedDatum>
不引用生命期
,因此抓数据
是非法
的.
你可在玩耍地
上亲眼看到它.
此时收到的错误消息
("隐藏类型抓生命期
")不是很直观,但它确实附带了如何修复它的有用建议
:
帮助
:要声明impl Iterator<Item = ProcessedDatum>
抓''_'
时,你可添加显式''_'
生命期约束
cpp
impl Iterator<Item = ProcessedDatum> + '_ {
按此建议
的显式版本,函数签名
变为:
cpp
fn process_data<'d>(
data: &'d [Datum]
) -> impl Iterator<Item = ProcessedDatum> + 'd {
data
.iter()
.map(|datum| datum.process())
}
在此版本中,在impl Trait
类型中显式引用
,'d
数据的生命期,因此允许使用
.即只要正在使用迭代器
,就必须持续借用
数据,即它(正确地)此例中标记错误:
cpp
let mut data: Vec<Datum> = vec![Datum::default()];
let iter = process_data(&data);
data.push(Datum::default()); //<-错误!
iter.next();
此设计的可用性问题
在impl Trait
中可用哪些泛型参数
的规则
是在早期根据一组有限的示例
确定的.随着时间,会注意
到它们有许多问题
.
1,不是正确的默认值
对主要代码基
(编译器和crates.io
上的箱
)的调查发现,绝大多数
返回位置impl Trait
值都要用生命期
,因此默认不抓
没用.
2,不够灵活
当前规则
是返回位置impl Trait
总是允许使用类型参数
,有时如果它们出现在约束
中,允许使用生命期
参数.
如上,该默认值
是错误的,因为大多数函数
实际上确实想允许返回类型
使用生命期
参数:这至少有变通
.
但默认值
也是错误
的,因为某些函数
想要显式声明
它们不在返回类型
中使用类型参数
,且现在无法覆盖它
.
最初意图是类型别名impl Trait
可解决该用例,但很麻烦.
3,难以解释
因为默认值
是错误
的,所以用户经常遇见这些错误
!添加编译器提示
以建议+'_
有用,但用户必须遵守不完全理解的提示
并不是很好.
4,错误的建议
给impl Trait
添加+'_
参数可能会很怪
,但并不是很难
.可惜,它一般是错误的注解
,导致不必要的编译器错误
,正确的修复
或很复杂
,有时甚至不可能
.考虑如下示例
:
cpp
fn process<'c, T> {
context: &'c Context,
data: Vec<T>,
) -> impl Iterator<Item = ()> + 'c {
data
.into_iter()
.map(|datum| context.process(datum))
}
此处,处理
函数用context.process
来处理数据
中的(T
类型)每个元素
.因为返回值
使用环境
,因此按+'c
声明它.
在此真正目标
是允许中
类型中,使用'c
;写+'c
可实现该目标
,因为现在在约束列表
中有'c
.但是,虽然编写+'c
是使'c
出现在约束中的方便方法,但也表明隐藏类型
必须比'c
更久.
该要求是不必要
的,实际上会导致本例
中编译错误
,在玩耍地
上试一下.
5,与Rust
的其他部分不一致
当前设计
还与Rust
其他部分不一致
.
异步FN
去糖
Rust
定义异步fn
来去糖返回->impl Future
的普通fn
.因此,你想像处理
此函数:
cpp
async fn process(data: &Data) { .. }
...将(大致)去糖
为:
cpp
fn process(
data: &Data
) -> impl Future<Output = ()> {
async move {
..
}
}
实际上,因为可用生命期的规则
有问题,这不是实际的去糖
.实际的去糖
是针对
一个特殊的允许使用所有生命期
的impl Trait
.
但是该形式的impl Trait
并未向终端用户
公开.
impl Trait
中的特征
Rust2024
设计
上述问题促使在Rust2024
中结合了两件事
来用新的方法
:
1,一个新的默认值
,即返回位置impl Trait
的隐藏类型
可用域中
而不仅是类型
(仅适合Rust2024
)的泛型参数
;
2,一个显式声明
可用哪些类型
的语法.
新的显式语法
叫"用约束"
:如,impl Trait+use<'x,T>
表示允许隐藏类型
使用'x
和T
(但不能使用域内
的其他泛型参数
).
现在默认可使用生命期
在Rust2024
中,默认是返回位置impl Trait
值的隐藏类型
使用域内
无论是类型还是生命期
的泛型参数
.
即在Rust2024
中可很好编译这篇博文
的初始示例
,设置玩耍地
中的版本
为2024
来试试:
cpp
fn process_data(
data: &[Datum]
) -> impl Iterator<Item = ProcessedDatum> {
data
.iter()
.map(|datum| datum.process())
}
好!
impl Trait
可包含一个use<>bound
来精确指定使用的泛型类型和生命期
例外
是,当函数仅接受读取值
且不包含在返回值
中的引用参数
时.一个示例是下面的indices()
函数:它接受一个&[T]
类型的切片
,但它唯一做的是读取长度
,用它来创建一个迭代器
.
返回值
中不需要切片
自身:
cpp
fn indices<'s, T>(
slice: &'s [T],
) -> impl Iterator<Item = usize> {
0 .. slice.len()
}
在Rust2021
中,该声明隐式
地表示切片
没有返回类型
.但在Rust2024
中,默认值
恰恰相反.即像此调用者
停止在Rust2024
中编译,因为他们现在假设直到迭代
完成,数据是借用的
:
cpp
fn main() {
let mut data = vec![1, 2, 3];
let i = indices(&data);
data.push(4); //<-错误!<--假设访问`'&data'`
i.next(); //
}
这实际上可能就是你想要的
!即你可稍后修改indices()
的定义,这样它实际
上在结果中包含切片
.即,新的默认值
延续了impl Trait
的传统,即保留在不中断调用者
时,函数
更改其实现
的灵活性
.
但是,如果它不是你想要的
呢?如果想保证indices()
不会在其返回值
中,保存参数切片
的引用,你现在在返回类型
中包含use<>
约束来显式
表示返回类型
中可包含哪些泛型参数
来完成.
在indices()
时,返回类型
实际上不使用泛型
,因此最好写为use<>
:
cpp
fn indices<'s, T>(
slice: &'s [T],
) -> impl Iterator<Item = usize> + use<> {
//-----返回类型不使用`''s'或'T'`
0 .. slice.len()
}
这与早期版本中impl Trait
的限制相应,它总是必须抓类型参数
.此时,可如下,避免了编译错误
,但仍比必要的更保守
:
cpp
fn indices<T>(
slice: &[T],
) -> impl Iterator<Item = usize> + use<T> {
0 .. slice.len()
}
或:'静态约束
.对完全不抓引用
的特例
,也可用'静态绑定
,如下(自己试一下):
cpp
fn indices<'s, T>(
slice: &'s [T],
) -> impl Iterator<Item = usize> + 'static {
//-------返回类型不会抓引用.
0 .. slice.len()
}
"此时,静态约束
很方便,特别是考虑到当前use<>
约束的实现限制,但use<>
约束总体上更灵活
.
如,编译器
有一个返回newtype
的索引I
而不是usize
值索引
的变量,因此它包含
一个use<I>
声明.