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


13

的实现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此处的签名?当将马桶盖放回原处时,它为何与众不同?

Answers:


4

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


我们将以drop泛型函数为典型示例,但所有这些也同样适用。以下是供参考的代码:fn drop<T>(_: T) {}

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

另一方面,类似fn 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);
}

(操场)

在该示例中,生命周期'a'b彼此之间没有关系:两者都不完全包含彼此。因此,这里没有发生任何子类型化的事情。pass实际上,单个实例具有两个不同的不相关的生存期。

这就是为什么drop不满意的原因for<'a> FnOnce(&'a T)。的任何特定实例drop只能覆盖一个生命期(忽略子类型)。如果我们从上面的示例(略微的签名更改并假设编译器允许我们drop进入)进入two_uses,则必须选择一些特定的生存'adrop,并且范围内的实例two_uses将具有Fn(&'a i32)一些具体的生存期'a。由于该功能仅适用于单个生命周期'a,因此无法在两个不相关的生命周期中使用它。

那么,为什么抽水马桶盖会收到HRTB?在推断闭包的类型时,如果期望的类型暗示需要更高等级的特征绑定,则编译器将尝试使其适合。在这种情况下,它会成功。


问题#41078与此密切相关,特别是eddyb的注释本质上给出了上面的解释(尽管在闭包的上下文中,而不是在普通函数中)。问题本身并未解决当前的问题。相反,它解决了如果在使用前将马桶盖分配给变量(尝试一下!)会发生什么情况。

这种情况将来可能会改变,但是这将需要对泛型函数的单态化进行很大的改变。


4

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

我对此错误的解释是,函数&x主体中的foo作用域寿命仅限于该主体,因此f(&x)也具有相同的作用域寿命,它可能无法满足for<'a>特征绑定所要求的通用量化。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.