为什么`std :: mem :: drop`与排名较高的特征范围中的闭包| _ |()不完全相同?

E_n*_*ate 9 closures type-inference traits higher-kinded-types rust

的实现std::mem::drop记录如下:

pub fn drop<T>(_x: T) { }
Run Code Online (Sandbox Code Playgroud)

因此,我希望该封盖|_| ()(俗称马桶封盖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!
}
Run Code Online (Sandbox Code Playgroud)

编译器的错误信息:

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
Run Code Online (Sandbox Code Playgroud)

考虑到drop对于任何大小都应该是通用的,T“更通用”的签名fn(_) -> _与不兼容听起来是不合理的for<'a> fn (&'a _) -> _。为什么编译器不接受drop此处的签名?当将马桶盖放回原处时,它为何与众不同?

SCa*_*lla 9

问题的核心是这drop不是单个函数,而是一组参数化的函数,每个函数都删除某些特定类型。为了满足势必更高的排名性状(以下简称hrtb),你需要一个单一的功能,可以同时进行的引用与任何给定的寿命类型。


我们将使用drop泛型函数作为我们的典型示例,但所有这些也更普遍。下面是代码供参考:fn drop<T>(_: T) {}.

从概念上讲,drop它不是单个函数,而是每个可能类型的函数T。的任何特定实例drop只接受单一类型的参数。这称为单态化。如果T使用drop不同的 ,drop则编译不同版本的。这就是为什么您不能将泛型函数作为参数传递并完全通用地使用该函数的原因(请参阅此问题

另一方面,函数 likefn pass(x: &i32) -> &i32 {x}满足 hrtb for<'a> Fn(&'a i32) -> &'a i32。不像drop,我们有一个单一的功能,可以同时满足Fn(&'a i32) -> &'a i32每一个生命周期'a。这体现在如何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);
}

Run Code Online (Sandbox Code Playgroud)

(操场)

在这个例子中,生命周期'a'b彼此没有关系:两者都不完全包含另一个。所以这里没有发生某种子类型化的事情。的单个实例pass实际上与两个不同的、不相关的生命周期一起使用。

这就是drop不满足的原因for<'a> FnOnce(&'a T)。的任何特定实例drop只能覆盖一个生命周期(忽略子类型)。如果我们通过droptwo_uses从上面的例子(略有签名的变化,并假设编译器让我们),那就要选择一些特定的生命周期'a和实例drop的范围two_usesFn(&'a i32)对一些具体的一生'a。由于该函数仅适用于单个生命周期'a,因此不可能将它与两个不相关的生命周期一起使用。

那么为什么马桶盖上会有一个hrtb呢?在推断闭包的类型时,如果预期的类型暗示需要更高等级的特征边界,编译器将尝试使一个 fit。在这种情况下,它成功了。


问题 #41078与此密切相关,特别是 eddyb在这里的评论基本上给出了上述解释(尽管是在闭包的上下文中,而不是普通函数)。不过,问题本身并没有解决当前的问题。相反,它解决了如果您在使用马桶盖之前将其分配给变量会发生的情况(试试看!)。

未来情况可能会发生变化,但需要对泛型函数的单态化方式进行相当大的改变。


edw*_*rdw 5

简而言之,两条线都应该失败。但是由于处理 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,)>`
Run Code Online (Sandbox Code Playgroud)

我对这个错误的解释是,函数&x体中的infoo仅具有限定在所述体中的作用域生命周期,因此f(&x)也具有相同的作用域生命周期,这不可能满足for<'a>trait bound 所要求的通用量化。

您在此处提出的问题几乎与问题 #57642相同,该问题也有两个相反的部分。

处理 hrtb 生命周期的新方法是使用所谓的Universe。Niko 有一个WIP来处理 Universe 的泄漏检查。在这种新制度下,上面链接的问题 #57642 的两个部分据说都失败了,但诊断更加明确。我想到那时编译器也应该能够正确处理您的示例代码。