"borrowed value does not live long enough" but it isn't really a problem

Hi all. This is a stripped-down version of a bigger section of code.

#[derive(Debug)]
enum Output {
    Value(String)
}

type Callback<'a> = dyn Fn(&'a [u32]) -> Output + 'a;

struct Thing<'a> {
    func: Box<Callback<'a>>
}

impl<'a> Thing<'a> {

    fn new<F>(func: F) -> Self
    where
        F: Fn(&'a [u32]) -> Output + 'a
    {
        Self { func: Box::new(func) }
    }

    fn run(&self, numbers: &'a [u32]) -> Output {
        (self.func)(numbers)
    }

}

fn build_thing<'a>(multiplier: u32) -> Thing<'a> {

    Thing::new(
        move |v: &'a [u32]| {
            let t: u32 = v.iter().sum::<u32>().try_into().unwrap();
            Output::Value(format!("RESULT:{}", t * multiplier))
        }
    )

}

fn main() {

    let thing = build_thing(10);

    for up_to in 1 ..= 10 {
        let numbers: Vec<u32> = (0 .. up_to).collect();
        let result = thing.run(&numbers);
        println!("{up_to} -> {result:?}");
    }

}

The compiler rejects my code:

44 |         let numbers: Vec<u32> = (0 .. up_to).collect();
   |             ------- binding `numbers` declared here
45 |         let result = thing.run(&numbers);
   |                                ^^^^^^^^ borrowed value does not live long enough
46 |         println!("{up_to} -> {result:?}");
47 |     }
   |     - `numbers` dropped here while still borrowed
48 |
49 | }
   | - borrow might be used here, when `thing` is dropped and runs the destructor for type `Thing<'_>`

As far as I understand, the compiler can't tell whether the &numbers reference is kept inside the thing object, and thus it complains to me. Please correct me if I'm wrong here.
But the reality is that this isn't the case: the &numbers reference is used by the closure function placed inside thing by build_thing(), but no reference is kept.

I can solve my problem by moving the line let thing = build_thing(10); inside the for loop, so that thing doesn't outlive numbers anymore. But this would call build_thing() every loop, I think I can even pay the cost of doing that, but I also would like to do the right thing.

To sum up, there's a way to tell the compiler that no &numbers reference is kept, so it can compile?

You probably want to adjust the trait object type to use a HRTB[1] for the function signature:

- type Callback<'a> = dyn Fn(&'a [u32]) -> Output + 'a;
+ type Callback<'a> = dyn Fn(&[u32]) -> Output + 'a;

impl<'a> Thing<'a> {

    fn new<F>(func: F) -> Self
    where
-       F: Fn(&'a [u32]) -> Output + 'a
+       F: Fn(&[u32]) -> Output + 'a
    {
        Self { func: Box::new(func) }
    }

-   fn run(&self, numbers: &'a [u32]) -> Output {
+   fn run(&self, numbers: &[u32]) -> Output {
        (self.func)(numbers)
    }

}

fn build_thing<'a>(multiplier: u32) -> Thing<'a> {

    Thing::new(
-       move |v: &'a [u32]| {
+       move |v: &[u32]| {
            let t: u32 = v.iter().sum::<u32>().try_into().unwrap();
            Output::Value(format!("RESULT:{}", t * multiplier))
        }
    )

}

Rust Playground

Note that dyn Fn(&[u32]) -> Output + 'a is a sytactic shorthand for:
dyn for<'b> Fn(&'b [u32]) -> Output + 'a.


  1. see the nomicon for more information ↩ī¸Ž

2 Likes

I made some attempts using a second 'b lifetime but I failed miserably. One day I'll learn how lifetimes work. One day.

Thank you a lot!

1 Like

The compiler doesn't reason at that level really, but that is something that it is possible to do given your original API. The API said, "everything passed to thing has to be borrowed for the same duration". And that's incompatible with passing in a borrow of numbers created and destroyed in a loop.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.