std::mem::drop
的实现记录如下:
pub fn drop<T>(_x: T) { }
因此,我希望闭合|_| ()
(俗称马桶封盖(在两个方向上都是drop
的潜在 1:1 替代品。但是,下面的代码表明,drop
与函数参数上绑定的更高等级特征不兼容,而马桶关闭则不兼容。
fn foo<F, T>(f: F, x: T)
where
for<'a> F: FnOnce(&'a T),
{
dbg!(f(&x));
}
fn main() {
foo(|_| (), "toilet closure"); // this compiles
foo(drop, "drop"); // this does not!
}
编译器的错误消息:
error[E0631]: type mismatch in function arguments
--> src/main.rs:10:5
|
1 | fn foo<F, T>(f: F, x: T)
| ---
2 | where
3 | for<'a> F: FnOnce(&'a T),
| ------------- required by this bound in `foo`
...
10 | foo(drop, "drop"); // this does not!
| ^^^
| |
| expected signature of `for<'a> fn(&'a _) -> _`
| found signature of `fn(_) -> _`
error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
--> src/main.rs:10:5
|
1 | fn foo<F, T>(f: F, x: T)
| ---
2 | where
3 | for<'a> F: FnOnce(&'a T),
| ------------- required by this bound in `foo`
...
10 | foo(drop, "drop"); // this does not!
| ^^^ expected bound lifetime parameter 'a, found concrete lifetime
考虑到drop
对于任何大小的T
都是通用的,"更通用"的签名fn(_) -> _
与for<'a> fn (&'a _) -> _
不兼容听起来是不合理的。为什么编译器在这里不承认drop
的签名,当厕所关闭代替它时,有什么不同?
问题的核心是drop
不是一个单一的函数,而是一组参数化的函数,每个函数都删除了一些特定的类型。为了满足更高等级的 trait 绑定(以下简称 hrtb(,您需要一个函数,该函数可以同时引用具有任何给定生存期的类型。
我们将使用drop
作为泛型函数的典型示例,但所有这些也更普遍地适用。以下是供参考的代码:fn drop<T>(_: T) {}
。
从概念上讲,drop
不是单个函数,而是每种可能类型的T
的一个函数。drop
的任何特定实例仅采用单一类型的参数。这称为单态化。如果与drop
一起使用不同的T
,则会编译不同版本的drop
。这就是为什么你不能将泛型函数作为参数传递并完全通用地使用该函数的原因(请参阅此问题(
另一方面,像fn pass(x: &i32) -> &i32 {x}
这样的函数满足 hrtbfor<'a> Fn(&'a i32) -> &'a i32
.与drop
不同,我们有一个单一的函数,可以同时满足每个生命周期'a
的Fn(&'a i32) -> &'a i32
。这反映在如何使用pass
上。
fn pass(x: &i32) -> &i32 {
x
}
fn two_uses<F>(f: F)
where
for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
// F: Fn(&i32) -> &i32 due to lifetime elision rules.
// That applies to your original example too.
{
{
// x has some lifetime 'a
let x = &22;
println!("{}", f(x));
// 'a ends around here
}
{
// y has some lifetime 'b
let y = &23;
println!("{}", f(y));
// 'b ends around here
}
// 'a and 'b are unrelated since they have no overlap
}
fn main() {
two_uses(pass);
}
(游乐场(
在示例中,'a
和'b
的生命周期彼此没有关系:两者都不完全包含另一个。所以这里没有某种子类型的事情发生。pass
的单个实例实际上被用于两个不同的、不相关的生命周期。
这就是为什么drop
不满足for<'a> FnOnce(&'a T)
。任何特定的drop
实例只能涵盖一个生命周期(忽略子类型(。如果我们从上面的示例中将drop
传递到two_uses
(略微更改签名并假设编译器允许我们(,它将不得不选择一些特定的生命周期'a
并且two_uses
范围内的drop
实例将Fn(&'a i32)
一些具体的生命周期'a
。由于该函数只适用于单个生命周期'a
,因此不可能在两个不相关的生命周期中使用它。
那么,为什么厕所关闭会得到HRTB?在推断闭包的类型时,如果预期类型暗示需要更高等级的特征绑定,编译器将尝试使一个绑定适合。在这种情况下,它成功了。
问题 #41078 与此密切相关,特别是,eddyb 在这里的评论基本上给出了上面的解释(尽管是在闭包的上下文中,而不是普通函数(。不过,问题本身并不能解决当前的问题。相反,它解决了如果您在使用马桶封盖之前将马桶封盖分配给变量会发生什么(试试吧!
将来情况可能会发生变化,但这需要对泛型函数的单态化方式进行相当大的改变。
简而言之,两条线都应该失败。但是,由于处理hrtb生命周期的旧方法中的一个步骤,即泄漏检查,目前存在一些健全性问题,rustc
最终(错误地(接受一个并留下另一个非常糟糕的错误消息。
如果使用rustc +nightly -Zno-leak-check
禁用泄漏检查,您将能够看到更明智的错误消息:
error[E0308]: mismatched types
--> src/main.rs:10:5
|
10 | foo(drop, "drop");
| ^^^ one type is more general than the other
|
= note: expected type `std::ops::FnOnce<(&'a &str,)>`
found type `std::ops::FnOnce<(&&str,)>`
我对这个错误的解释是,foo
函数主体中的&x
只有一个范围生存期,仅限于所述主体,因此f(&x)
也具有相同的作用域生存期,不可能满足特征边界所需的for<'a>
通用量化。
您在此处提出的问题几乎与问题#57642相同,后者也有两个对比部分。
处理 hrtb 生命周期的新方法是使用所谓的宇宙。Niko有一个WIP来解决宇宙的泄漏检查。在这种新制度下,据说上面链接的问题#57642的两个部分都失败了,诊断更加清晰。我想编译器到那时也应该能够正确处理您的示例代码。