注释闭包参数可重点使用更高等级的特征边界

Annotating closure parameter foces use of Higher-Rank Trait Bounds

提问人:SaNoy SaKnoi 提问时间:6/18/2023 更新时间:6/18/2023 访问量:64

问:

在这个朴素的代码片段(playground)中,使用闭包的未注释版本不会编译,而使用类型进行注释会:

fn bounded(items: &[&u8]) -> bool {
    items.iter().all(|item| **item <= 10)
}

fn check(check_function: &dyn Fn(&[&u8]) -> bool, items: &[&u8]) -> bool {
    check_function(items)
}

fn main() {
    let a = [1, 45, 7, 2];
    let b = [&a[2], &a[0], &a[0]];
    
    let func = |items| bounded(items); // E0308
    // let func = |items: &[&u8]| bounded(items);
    
    println!("{:?}", check(&func, &b));
}
   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:28:35
   |
28 |     println!("{:?}", Checker::new(&func).check(&b));
   |                                   ^^^^^ one type is more general than the other
   |
   = note: expected trait `for<'a, 'b> Fn<(&'a [&'b u8],)>`
              found trait `Fn<(&[&u8],)>`
note: this closure does not fulfill the lifetime requirements
  --> src/main.rs:25:16
   |
25 |     let func = |items| bounded(items);
   |                ^^^^^^^

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:28:35
   |
28 |     println!("{:?}", Checker::new(&func).check(&b));
   |                                   ^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 [&u8]) -> bool` must implement `FnOnce<(&'1 [&u8],)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 [&u8],)>`, for some specific lifetime `'2`

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:28:35
   |
28 |     println!("{:?}", Checker::new(&func).check(&b));
   |                                   ^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&[&'2 u8]) -> bool` must implement `FnOnce<(&[&'1 u8],)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&[&'2 u8],)>`, for some specific lifetime `'2`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 3 previous errors

我现在知道我可以用来直接存储函数指针(playground),但想调查使用闭包引起的错误。fn

fn check(check_function: fn(&[&u8]) -> bool, items: &[&u8]) -> bool {
    check_function(items)
}

这个问题还涉及闭包编译的显式注释,但是关于借用检查器的,而我在代码中没有借用两次。


我读过《鲁斯托诺米孔》的终生章节,以及这本书的终生章节我大致了解了机制,但需要帮助进行生命周期省略和特定/一般生命周期的概念推理。

  1. 为什么不够?&'c [&'c u8]&'a [&'b u8]

后者来自生命周期省略,但在我看来,如果无效,也是无效的,因此任何接收前者的函数(两者中寿命最短)也应该有效。'a'b

我不认为这是一个过于保守的检查,因为 Rustonomicon 提到引用可以重新初始化。这是我尝试解释为什么(游乐场):

fn print(vec: &[&u8]) {
    println!("{vec:?}")
}

fn main() {
    let x = [1, 2, 3, 4];
    let mut y = vec![&x[1], &x[2], &x[3]];
    print(&y); // &y is &'b [&'a u8, &'a u8, &'a u8]
    y.insert(0, &x[0]); // lifetime of 'b is invalidated and 'c is initialized
    print(&y); // &y is &'d [&'c u8, &'a u8, &'a u8, &'a u8]
}

尽管这确实重新初始化了引用,但似乎我仍然可以通过将所有引用分别绑定到较短的生命周期(和)来运行这两个函数。print&y'b'd

  1. 闭包捕获的值的生命周期是多久?

使用 rust-analyzer,它正确地推断出 的类型是 。查看错误,这也是捕获的类型:items&[&u8]

发现性状Fn<(&[&u8],)>

但是提到 HRTB这个解释,似乎应该对某些人进行脱糖,这里没有这样做。Fnfor<...> Fn(...)func

其他错误也暗示了在某个特定生存期(?)的生存期)内实现了闭包。这就是没有给出明确的生命周期标记的原因吗?main()

相关:生存期省略/注释不能应用于闭包

  1. 为什么用闭包注释有效?&[&u8]

这个我没有太多线索。我猜这允许编译器将捕获的值显式绑定到 HRTB,并且允许闭包足够通用。

防锈 瓶盖 寿命

评论

1赞 isaactfa 6/18/2023
我想这与“编译器将为每个 [闭包的] 参数及其返回值推断一个具体类型”这一事实有关。我不知道“具体类型”在规范中是否定义明确,但它很可能意味着一些具体的.注释闭包会导致编译器推断更通用的 .&'2 [&u8]'2for<'1> &'1 [&u8]
0赞 isaactfa 6/18/2023
“为什么 &'c [&'c u8] 不足以满足 &'a [&'b u8] 的需求?”它是(对于省略的生存期),但生存期省略规则是每个引用都有一个唯一的生存期。这里没有必要特殊情况切片。边界将自动推断。'b: 'a

答:

2赞 PatientPenguin 6/18/2023 #1


我相信 isaactfa 是正确的,因为编译器在没有注释的情况下推断出错误的类型,并且当您对其进行注释时,编译器能够推断出更正确的类型。

也可以通过添加生存期来推断正确的类型,而不是向闭包添加注释,如下所示。添加这些注释有助于编译器意识到您的参数存在足够长的时间以供闭包使用,并计算出闭包的生存期。check

fn check<'b1, 'b2, 'a1: 'b1, 'a2: 'b2>(check_function: &dyn Fn(&'b1 [&'b2 u8]) -> bool, items: &'a1 [&'a2 u8]) -> bool {
    check_function(items)
}

校正


但是,您的帖子中有一些不正确的信息,我确实想快速纠正:
fn main() {
    let x = [1, 2, 3, 4];
    let mut y = vec![&x[1], &x[2], &x[3]];
    print(&y); // &y is &'b [&'a u8, &'a u8, &'a u8]
    y.insert(0, &x[0]); // lifetime of 'b is invalidated and 'c is initialized
    print(&y); // &y is &'d [&'c u8, &'a u8, &'a u8, &'a u8]
}

在此代码片段中,您写道 的生存期为 类型 ,但这并不准确。它仍然是 .推送该值不会引入新的生存期,因为您正在引用 ,就像您在初始化时所做的那样。您可以通过将插入行放入另一个块来验证这一点。为了编译它,生存期必须与生存期一样长(或更长),因为稍后使用。在 playground 上运行它表明,借用与本身无关,而是与被借用对象的生存期相关。&y&'d [&'c u8, &'a u8, &'a u8, &'a u8]&'d [&'a u8]&x[0]xy'c'ay&

let x = [1, 2, 3, 4];
let mut y = vec![&x[1], &x[2], &x[3]];
println!("{:?}", &y);
{
    // If the lifetime was related to this `&` and not `x`,
    // then this wouldn't be able to compile because
    // the `&` is within a shorter scope.
    y.insert(0, &x[0]);
}
println!("{:?}", &y);

如果改为在块内推送项目的引用,则可以缩短其生存期:y

fn main() {
    let x = [1, 2, 3, 4];
    let mut y = vec![&x[1], &x[2], &x[3]];
    println!("{:?}", &y);
    {
        let inner = [1,2,3,4];
        y.insert(0, &inner[0]);
        println!("{:?}", &y);
    }
    // Requires that `y` and all it's elements live as long as `x`, because
    // `y` was initialized with values from `x`.
    println!("{:?}", &y);
}

这失败了,因为寿命不够长。如果注释掉最后一个,那么它将运行,因为 (inside of ) 的借用的生存期可以缩短为 的生存期。innerprintln!xyinner

评论

0赞 SaNoy SaKnoi 6/18/2023
谢谢你的回答。关于“......借用对象的生命周期[相关]“,注释 like so 的生命周期是否正确?我的理解是,由于在我最初的问题是不可变的,因此所有引用都具有相同的生命周期,但如果更改,情况就不再如此,就像两个引用的生命周期不同一样。xxxy