最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Is there a way to bound `U` in `Fn(&T) -> U` with lifetime that `&T` has when `U` is a reference?

programmeradmin6浏览0评论

Here is the code:

fn main() {
    let foo: Foo<i32> = Foo(42);
    let bar: Foo<i64> = foo.map_ref(|x| *x as i64); // OK
    let baz: Foo<&i32> = foo.map_ref(|x| x); // Error
}

struct Foo<T>(T);

impl<T> Foo<T> {
    fn map_ref<U, F>(&self, f: F) -> Foo<U>
    where
        F: Fn(&T) -> U,
    {
        Foo(f(&self.0))
    }
}

It fails to compile when U is reference:

error: lifetime may not live long enough
 --> src\main.rs:4:48
  |
4 |     let baz: Foo<&i32> = foo.map_ref(|x: &i32| x); // Error
  |                                          -   - ^ returning this value requires that `'1` must outlive `'2`
  |                                          |   |
  |                                          |   return type of closure is &'2 i32
  |                                          let's call the lifetime of this reference `'1`
  |
help: dereference the return value
  |
4 |     let baz: Foo<&i32> = foo.map_ref(|x: &i32| *x); // Error
  |                                                +

I tried to introduce a generic lifetime 'a in Foo::map_ref and have no idea what to do next.

Here is the code:

fn main() {
    let foo: Foo<i32> = Foo(42);
    let bar: Foo<i64> = foo.map_ref(|x| *x as i64); // OK
    let baz: Foo<&i32> = foo.map_ref(|x| x); // Error
}

struct Foo<T>(T);

impl<T> Foo<T> {
    fn map_ref<U, F>(&self, f: F) -> Foo<U>
    where
        F: Fn(&T) -> U,
    {
        Foo(f(&self.0))
    }
}

It fails to compile when U is reference:

error: lifetime may not live long enough
 --> src\main.rs:4:48
  |
4 |     let baz: Foo<&i32> = foo.map_ref(|x: &i32| x); // Error
  |                                          -   - ^ returning this value requires that `'1` must outlive `'2`
  |                                          |   |
  |                                          |   return type of closure is &'2 i32
  |                                          let's call the lifetime of this reference `'1`
  |
help: dereference the return value
  |
4 |     let baz: Foo<&i32> = foo.map_ref(|x: &i32| *x); // Error
  |                                                +

I tried to introduce a generic lifetime 'a in Foo::map_ref and have no idea what to do next.

Share Improve this question asked yesterday Lingxuan YeLingxuan Ye 531 silver badge3 bronze badges New contributor Lingxuan Ye is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
Add a comment  | 

1 Answer 1

Reset to default 3

For this simple example, you don't even need to annotate U. Instead, you can help the compiler along by clarifying that the lifetime of F's parameter should be/will be at least as long as the reference to self:

impl<T> Foo<T> {
    fn map_ref<'a, U, F>(&'a self, f: F) -> Foo<U>
    where
        F: Fn(&'a T) -> U,
    {
        Foo(f(&self.0))
    }
}

When you call foo.map_ref(f), what really happens under the hood is Foo::<T>::map_ref(&foo, f): calling a method creates a reference to the object the method is called on. So now, when we call map_ref and give it a closure, the closure is given a parameter whose lifetime is the same length as the implicitly created &foo.

See here on the Rust Playground. Hopefully that answers your question. :-)


EDIT: In response to your comment about the HRTB: yes, you're exactly right. F is desugared into for <'b> Fn(&'b T) -> U. This means that the trait bound on F becomes, "F should be a function callable with a reference of any lifetime 'b." Or, if we want to use the same names as the error message, "any lifetime '1."

You can actually see this desugaring happen directly inside your IDE using rust-analyzer by turning on lifetime elision hints:

(rust-analyzer.inlayHints.lifetimeElisionHints.enable in VS Code).

With that in mind, let's reanalyze the error message the compiler gave you:

let baz: Foo<&i32> = foo.map_ref(|x: &i32| x); // Error
                                     -   - ^ returning this value requires that `'1` must outlive `'2`
                                     |   |
                                     |   return type of closure is &'2 i32
                                     let's call the lifetime of this reference `'1`

Personally, I find the introduction of a second '2 lifetime to be a bit confusing when considering why this is an error... but that's just me. The way I would explain this to someone is by saying, "you're trying to use '1 in the return type of your closure, which forces '1 to appear in U; but, from outside map_ref, there's no way to know how long '1 is. f could have been called with a reference to a local variable, for all we know!" In that case, it's kind of like we're asking the compiler to "extend" that local variable's lifetime so we can keep a Foo<U> around pointing at it. Obviously, that goes against Rust's dropping system.

The reason we need to annotate 'a explicitly is simply because this example falls outside the rules of lifetime elision. All references need a lifetime, it's just that the compiler lets us skip writing them sometimes, according to some rules. F's parameter needs something, but the compiler isn't going to use &self's lifetime automatically; doing so would be overly conservative for most use-cases (since f now can't be called with a reference to a local variable, for example).

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论