Paul Rayner
Paul Rayner

Reputation: 402

Lifetime issue adding a function pointer to a struct

I'm trying to add a function pointer to a struct, and can't figure out how to do it. Here is a simple example:

struct Ure<'a> {
    num: u64,
    func: Option<&'a Fn(u64) -> u64>,
}

impl<'a> Ure<'a> {
    fn g42_ure(n: u64) -> Ure<'a> {
        Ure {
          num: n,
          func: Some(&Ure::g42),
        }
    }

    fn g42(u: u64) -> u64 {
        if u > 42 { u } else { 42 }
    }
}

This leads to the following error:

error: borrowed value does not live long enough
  --> <anon>:10:23
   |
10 |           func: Some(&Ure::g42),
   |                       ^^^^^^^^ does not live long enough
11 |         }
12 |     }
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the body at 7:34...
  --> <anon>:7:35
   |
7  |       fn g42_ure(n: u64) -> Ure<'a> {
   |  ___________________________________^ starting here...
8  | |         Ure {
9  | |           num: n,
10 | |           func: Some(&Ure::g42),
11 | |         }
12 | |     }
   | |_____^ ...ending here

Is there a way to get a reference to function g42 in this example to live long enough without passing it as an argument to g42_ure? It does not matter to me where g42 is defined (whether it is inside impl Ure, g42_ure() or neither), but coming from an OOP background it seems neater inside impl Ure.

I'm just learning Rust (and very much enjoying it), so any help would be appreciated, thanks.

Upvotes: 0

Views: 313

Answers (1)

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88576

The type Fn(u64) -> u64 is not a function pointer, but a trait (-object). Function pointers in Rust are written fn(u64) -> u64, with a lowercase f! Also note that fn(u64) -> u64 is already a function pointer, no need to say &fn(u64) -> u64!

So one way to make it work is by using function pointers (Playground):

struct Ure {
    num: u64,
    func: Option<fn(u64) -> u64>,
    //           ^^^^^^^^^^^^^^
}

impl Ure {
    fn g42_ure(n: u64) -> Ure {
        Ure {
            num: n,
            func: Some(Ure::g42),
            //         ^^^^^^^^
        }
    }
    // ...
}

Function pointers have limitations, though. Specifically, they can't have environments like closures have. That's there the Fn trait comes in which (together with its siblings FnMut and FnOnce) abstracts over callable things in general (including function pointers and closures).

The problem is that you can't use trait objects that easily. Probably the easiest is to use a Box to store and own a trait object on the heap. It would look like this (Playground):

struct Ure {
    num: u64,
    func: Option<Box<Fn(u64) -> u64>>,
    //           ^^^^^^^^^^^^^^^^^^^
}

impl Ure {
    fn g42_ure(n: u64) -> Ure {
        Ure {
            num: n,
            func: Some(Box::new(Ure::g42)),
            //         ^^^^^^^^^^^^^^^^^^
        }
    }   
    // ...
} 

Upvotes: 4

Related Questions