user2282732
user2282732

Reputation:

Storing a closure with lifetimes in a struct

I'm trying to store closures in a Vec that is part of a struct. The closure is a factory function which receives 2 references as arguments and produces a trait object which stores the references the closure receives as arguments.

Because of that, the produced trait object has a lifetime that must not exceed the lifetime of the references. Also component_registrations will be accessed from multiple threads and is therefore wrapped in an Arc<Mutex>.

I tried implementing it but the compiler says that the generic parameter F of the register_component function doesn't satisfy the trait bound used in component_registrations.

This is the relevant part of the code:

use std::sync::Mutex;
use std::sync::Arc;

pub mod gl {
    pub struct Gl();
}

pub struct ComponentRegistry<'a> {
    gl: &'a gl::Gl
}

pub trait Component<'a> {
}

pub struct Application {
    component_registrations: Arc<Mutex<Vec<Box<for<'b> Fn(&'b ComponentRegistry<'b>, &'b gl::Gl) -> Box<Component<'b>> + Send + 'static>>>>
}

impl Application {
    pub fn new() -> Application {
        Application {
            component_registrations: Arc::new(Mutex::new(vec![]))
        }
    }

    pub fn register_component<'a, F>(&mut self, register: F) where F: Fn(&'a ComponentRegistry<'a>, &'a gl::Gl) -> Box<Component<'a>> + Send + 'static {
        self.component_registrations.lock().unwrap().push(Box::new(register));
    }
}
error[E0277]: the trait bound `for<'b> F: std::ops::Fn<(&'b ComponentRegistry<'b>, &'b gl::Gl)>` is not satisfied
  --> src/main.rs:27:59
   |
27 |         self.component_registrations.lock().unwrap().push(Box::new(register));
   |                                                           ^^^^^^^^^^^^^^^^^^ the trait `for<'b> std::ops::Fn<(&'b ComponentRegistry<'b>, &'b gl::Gl)>` is not implemented for `F`
   |
   = help: consider adding a `where for<'b> F: std::ops::Fn<(&'b ComponentRegistry<'b>, &'b gl::Gl)>` bound
   = note: required for the cast to the object type `for<'b> std::ops::Fn(&'b ComponentRegistry<'b>, &'b gl::Gl) -> std::boxed::Box<Component<'b>> + std::marker::Send`

error[E0271]: type mismatch resolving `for<'b> <F as std::ops::FnOnce<(&'b ComponentRegistry<'b>, &'b gl::Gl)>>::Output == std::boxed::Box<Component<'b>>`
  --> src/main.rs:27:59
   |
27 |         self.component_registrations.lock().unwrap().push(Box::new(register));
   |                                                           ^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter 'b, found concrete lifetime
   |
note: concrete lifetime that was found is the lifetime 'a as defined on the method body at 26:5
  --> src/main.rs:26:5
   |
26 | /     pub fn register_component<'a, F>(&mut self, register: F) where F: Fn(&'a ComponentRegistry<'a>, &'a gl::Gl) -> Box<Component<'a>> + Send + 'static {
27 | |         self.component_registrations.lock().unwrap().push(Box::new(register));
28 | |     }
   | |_____^
   = note: required for the cast to the object type `for<'b> std::ops::Fn(&'b ComponentRegistry<'b>, &'b gl::Gl) -> std::boxed::Box<Component<'b>> + std::marker::Send`

Upvotes: 2

Views: 344

Answers (1)

Tibor Benke
Tibor Benke

Reputation: 1280

If you use a higher ranked lifetime when you define your component_registrations struct field, you should use a higher ranked lifetime for F as well.

Also, if you say Box<Component<'b>>, it really means Box<Component<'b> + 'static> (so the trait object can contain only owned data). What you really need is Box<Component<'b> + 'b>, which means it is a trait object that implements Component<'b> and it can also contain borrowed data which live at least as long as 'b.

The relevant part is

pub struct Application {
    component_registrations: Vec<Box<for<'b> Fn(&'b ComponentRegistry<'b>, &'b gl::Gl) -> Box<Component<'b> + 'b> + Send + 'static>>
}

impl Application {
    pub fn register_component<F>(&mut self, register: F) where F: for<'b> Fn(&'b ComponentRegistry<'b>, &'b gl::Gl) -> Box<Component<'b> + 'b> + Send + 'static {
        self.component_registrations.push(Box::new(register));
    }
}

You can see the full example. Note, that I removed the Arc and Mutex types from your example since they were not relevant.

Upvotes: 2

Related Questions