返回捕获引用的高级闭包

Lam*_*dba 4 rust

我正在尝试在返回闭包的结构上编写一个方法。此闭包应将&[u8]具有任意生命周期的 a'inner作为参数并返回相同的类型&'inner [u8]。为了执行它的功能,闭包还需要一个对 struct 成员的(共享)引用&self。这是我的代码:

#![warn(clippy::pedantic)]

// placeholder for a large type that I can't afford to clone
struct Opaque(usize);

struct MyStruct<'a>(Vec<&'a Opaque>);

impl<'a> MyStruct<'a> {
    pub fn get_func_for_index(
        &'a self,
        n: usize,
    ) -> Option<impl for<'inner> Fn(&'inner [u8]) -> &'inner [u8] + 'a> {
        // the reference to the struct member, captured by the closure
        let opaque: &'a Opaque = *self.0.get(n)?;

        Some(move |i: &[u8]| {
            // placeholder: do something with the input using the &Opaque
            &i[opaque.0..]
        })
    }
}

fn main() {
    let o1 = Opaque(1);
    let o5 = Opaque(5);
    let o7 = Opaque(7);

    let x = MyStruct(vec![&o1, &o5, &o7]);

    let drop_five = x.get_func_for_index(1 /*Opaque(5)*/).unwrap();

    let data: Vec<u8> = Vec::from(&b"testing"[..]);

    assert_eq!(drop_five(&data[..]), b"ng");
}
Run Code Online (Sandbox Code Playgroud)

如果我尝试用 编译它rustc 1.54.0 (a178d0322 2021-07-26),我会收到以下错误:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> /tmp/lifetimes.rs:16:14
   |
16 |             &i[opaque.0..]
   |              ^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 14:14...
  --> /tmp/lifetimes.rs:14:14
   |
14 |           Some(move |i: &[u8]| {
   |  ______________^
15 | |             // placeholder: do something with the input using the &Opaque
16 | |             &i[opaque.0..]
17 | |         })
   | |_________^
note: ...so that reference does not outlive borrowed content
  --> /tmp/lifetimes.rs:16:14
   |
16 |             &i[opaque.0..]
   |              ^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 6:6...
  --> /tmp/lifetimes.rs:6:6
   |
6  | impl<'a> MyStruct<'a> {
   |      ^^
note: ...so that return value is valid for the call
  --> /tmp/lifetimes.rs:10:17
   |
10 |     ) -> Option<impl for<'inner> Fn(&'inner [u8]) -> &'inner [u8] + 'a> {
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: higher-ranked subtype error
  --> /tmp/lifetimes.rs:7:5
   |
7  | /     pub fn get_func_for_index(
8  | |         &'a self,
9  | |         n: usize,
10 | |     ) -> Option<impl for<'inner> Fn(&'inner [u8]) -> &'inner [u8] + 'a> {
   | |_______________________________________________________________________^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0495`.
Run Code Online (Sandbox Code Playgroud)

这是相当一口,我真的不明白它想告诉我什么。第一部分 ( first, the lifetime...) 对我来说很有意义,返回的切片不能超过闭包参数。然而,第二部分 ( but, the lifetime...) 对我来说似乎很奇怪 -+ 'a方法签名中的注释指的是闭包本身(因为它捕获了&'a self.foo),而不是闭包返回的值。

是否可以更改代码以在 rust 中正确建模,或者目前不支持此构造?

tre*_*tcl 9

问题在这里:

Some(move |i: &[u8]| {
Run Code Online (Sandbox Code Playgroud)

每个人&都有一生,无论是否明确。的寿命是&[u8]多少?很明显,它应该是“闭包调用者选择的生命周期”(即更高等级的生命周期)。但是当编译器遇到一个带有空闲生命周期参数的引用类型时,即使在一个闭包的参数列表中,它也不会假设生命周期是排名更高的。您收到的关于“匿名生命周期 #1”的错误消息是编译器困惑地试图使非排名更高的生命周期工作。

理论上,编译器可以从impl Fn返回类型中“向后”工作,并认识到闭包的类型需要具有更高等级的生命周期。在这种情况下这样做还不够聪明,但有一种方法可以说服它:使用带有有界类型参数的局部函数将闭包的类型限制为您想要的范围。

pub fn get_func_for_index(
    &self, // note 1
    n: usize,
) -> Option<impl 'a + for<'inner> Fn(&'inner [u8]) -> &'inner [u8]> { // note 2
    // the reference to the struct member, captured by the closure
    let opaque: &'a Opaque = *self.0.get(n)?;

    // helper function to constrain the closure type
    fn check<F: Fn(&[u8]) -> &[u8]>(f: F) -> F { // note 3
        f
    }

    Some(check(move |i| {
        // placeholder: do something with the input using the &Opaque
        &i[opaque.0..]
    }))
}
Run Code Online (Sandbox Code Playgroud)

操场

请注意以下事项:

  1. &'a self对于这个函数来说太保守了,因为'a是引用的生命周期参数self 包含,而不是self借用的生命周期。通常,您几乎不应该从外部作用域写入&'a self&'a mut selfwhere 'ais 命名生命周期。
  2. 我发现在+ 'along trait 的末尾很容易错过,尤其是Fn带有返回类型的trait。我建议在对开像这样的情况下,寿命(把它先)明确指出,它更多地涉及了impl比对&'inner [u8]。这是一种风格选择。
  3. Fn(&[u8]) -> &[u8]实际上与 完全相同for<'inner> Fn(&'inner [u8]) -> &'inner [u8],因为特征的省略规则Fn与常规函数相同。无论哪种方式都很好;我发现较短的版本更容易阅读。

类似问题