注解闭包参数需要使用高阶特质边界。

huangapple go评论79阅读模式
英文:

Annotating closure parameter foces use of Higher-Rank Trait Bounds

问题

在这个简单的代码片段中,如果使用未注释的闭包版本,不会编译通过,而如果使用类型注释,就会编译通过:

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

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

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

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

我已经阅读了Rustonomicon的生命周期章节,以及书中的生命周期章节。我大致了解了机制,但需要帮助理解生命周期省略的概念推理,以及特定/一般生命周期。

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

后者来自于生命周期省略,但在我看来,如果'a被无效,'b也被无效,因此接收前者的任何函数(它获取两者中的最短生命周期)也应该能够工作。

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

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]
}

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

  1. 闭包捕获的值的生命周期是什么?

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

found trait Fn(&[&u8],)

但参考HRTB这个解释,似乎Fn应该被解糖为一些for<...> Fn(...),但这在func中并没有这样做。

其他错误也暗示着闭包是为某个特定生命周期(main()的生命周期?)而实现的。这是没有给出显式生命周期标记的原因吗?

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

  1. 为什么用&[&u8]注释闭包可以工作?

这个问题我没有太多线索。我猜想这允许编译器将捕获的值明确绑定到HRTB,从而使闭包足够通用。

英文:

In this naive snippet (playground), using the unannotated version of the closure does not compile, while annotating with the type does:

fn bounded(items: &amp;[&amp;u8]) -&gt; bool {
    items.iter().all(|item| **item &lt;= 10)
}

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

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

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

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

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

I am now aware that I could use fn to store a function pointer directly (playground), but want to investigate the error caused from using closures.

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

This question also deals with the explicit annotation of the closure compiling, but is about the borrow checker, whereas I do not borrow twice in my code.


I have read the Rustonomicon's lifetime chapters, as well as the Book's lifetime chapters. I roughly understand the mechanics, but need help with the conceptual reasoning for lifetime elision, and specific/general lifetimes.

  1. Why isn't &amp;&#39;c [&amp;&#39;c u8] sufficient for &amp;&#39;a [&amp;&#39;b u8]?

The latter is from lifetime elision, but it seems to me like if &#39;a is invalidated, &#39;b is also invalidated, so any function receiving the former (which gets the shortest lifetime of the two) should also work.

I don't think this is a over-conservative check, since the Rustonomicon mentions that references can be re-initialized. Here is my attempt explaining why (playground):

fn print(vec: &amp;[&amp;u8]) {
    println!(&quot;{vec:?}&quot;)
}

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

Although this does re-initialize the reference, it seems like I could still run both print functions by binding the all references from &amp;y to the shorter lifetime(&#39;b and &#39;d respectively).

  1. What are the lifetimes of the value captured by the closure?

Using rust-analyzer, it correctly deduces that the type of items is &amp;[&amp;u8]. Looking at the errors, this is also the type captured:

> found trait Fn&lt;(&amp;[&amp;u8],)&gt;

But referring to HRTB and this explanation, it seems like Fn should be desugared to some for&lt;...&gt; Fn(...), this isn't done to func here.

The other errors also hint at the closure being implemented for some specific lifetime (the lifetime of main()?). Is this the reason why no explicit lifetime marker is given?

Related: Lifetime elision/annotation cannot be applied to closures.

  1. Why does annotating the closure with &amp;[&amp;u8] work?

This one I don't have many clues about. I'm guessing that this allows the compiler to explicitly bind the captured value to a HRTB, and that allows the closure to be general enough.

答案1

得分: 2

修正


在您的帖子中有一些不正确的信息,我想快速进行修正:

fn main() {
    let x = [1, 2, 3, 4];
    let mut y = vec![&x[1], &x[2], &x[3]];
    print(&y); // &y 是 &'b [&'a u8, &'a u8, &'a u8]
    y.insert(0, &x[0]); // 'b 的生命周期被使无效,并初始化了 'c
    print(&y); // &y 是 &'d [&'c u8, &'a u8, &'a u8, &'a u8]
}

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

let x = [1, 2, 3, 4];
let mut y = vec![&x[1], &x[2], &x[3]];
println!("{:?}", &y);
{
    // 如果生命周期与此`&`而不是`x`相关,那么这将无法编译,因为`&`位于更短的作用域内。
    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);
    }
    // 需要'y'及其所有元素的寿命与'x'一样长,因为'y'是从'x'初始化的。
    println!("{:?}", &y);
}

这将失败,因为'inner'的生命周期不够长。如果您将最后的println!注释掉,它将运行,因为对'x'的借用的生命周期(在'y'中)可以缩短到'inner'的寿命。

英文:

Answer

<hr>

I believe that isaactfa is correct in that the compiler is inferring the wrong type without the annotation, and when you annotate it, the compiler is able to infer a more correct type.

It is also possible to get it to infer the correct types by adding lifetimes to check instead of adding an annotation to the closure as seen here. Adding these annotations helps the compiler to realize that your arguments live long enough to be used by the the closure, and to figure out the closure's lifetime.

fn check&lt;&#39;b1, &#39;b2, &#39;a1: &#39;b1, &#39;a2: &#39;b2&gt;(check_function: &amp;dyn Fn(&amp;&#39;b1 [&amp;&#39;b2 u8]) -&gt; bool, items: &amp;&#39;a1 [&amp;&#39;a2 u8]) -&gt; bool {
    check_function(items)
}

Correction

<hr>
There is however some information that isn't correct in your post that I do want to correct real quick:

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

In this snippet, you write that the lifetime of &amp;y is of type &amp;&#39;d [&amp;&#39;c u8, &amp;&#39;a u8, &amp;&#39;a u8, &amp;&#39;a u8], however this isn't accurate. It is still of the type &amp;&#39;d [&amp;&#39;a u8]. Pushing the value &amp;x[0] does not introduce a new lifetime, because you are referencing x, just like you did when you initialized y. You can verify this by putting the insert line into another block. In order for it to compile, the lifetime &#39;c has to be as long (or longer) than the lifetime &#39;a, because y is used later. Running this on the playground shows that the borrow is not related to the &amp; itself, but to the lifetime of the object that is being borrowed from.

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

If you were to instead push a reference of an item inside the block, then the lifetime of y could be shortened:

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

This fails because inner doesn't live long enough. If you were to comment out the last println! then it would run, because the lifetime of the borrows of x (inside of y) can be shorted to the lifetime of inner.

huangapple
  • 本文由 发表于 2023年6月18日 18:40:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76500115.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定