0

The system here is:

  • event: a trait to be "extended" for different types of events
  • window: controller responsible for window creation and propagating its events further up the queue
  • application: controller of the whole application which creates a window and makes other operations (not important for now).

The window and application objects must live as long as the program lives, as they ARE themselves the program.

I need to be able to create a callback function (a closure or a method) and pass it to the window struct with "set_event_callback" method, so it calls the handler when an event occurs.

However, I faced a problem of lifetimes, as the code below does not compile with an error:

error[E0521]: borrowed data escapes outside of method
  --> src/main.rs:34:9
   |
31 |       pub fn run(&mut self) {
   |                  ---------
   |                  |
   |                  `self` is a reference that is only valid in the method body
   |                  let's call the lifetime of this reference `'1`
...
34 | /         window.set_event_callback(|event| {
35 | |             self.data += 1;
36 | |             false
37 | |         });
   | |          ^
   | |          |
   | |__________`self` escapes the method body here
   |            argument requires that `'1` must outlive `'static`
trait Event {}

struct Window {
    callback: Box<dyn FnMut(&mut dyn Event) -> bool>
}

impl Window {
    pub fn new() -> Self {
        Self {
            callback: Box::new(|_| false)
        }
    }
    
    pub fn set_event_callback<C>(&mut self, callback: C) 
        where C: FnMut(&mut dyn Event) -> bool + 'static {
        self.callback = Box::new(callback);
    }
}

struct Application {
    data: i32
}

impl Application {
    pub fn new() -> Self {
        Self {
            data: 0
        }
    }
    
    pub fn run(&mut self) {
        let mut window = Window::new();
        
        window.set_event_callback(|event| {
            self.data += 1;
            false
        });
    }
}

fn main() {
    Application::new().run();
}

I suspect this might have happened because the compiler does not know about the lifetimes of the window and application objects. However, I was not able to find a solution in a week.

Note: adding 'static to the &self in the run method (pub fn run(&'static self)) leads to another error:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:42:5
   |
42 |     Application::new().run();
   |     ^^^^^^^^^^^^^^^^^^------
   |     |
   |     creates a temporary value which is freed while still in use
   |     argument requires that borrow lasts for `'static`
43 | }
   | - temporary value is freed at the end of this statement
Vladislav
  • 33
  • 4

2 Answers2

1

I have a second solution for you. This one abstracts lifetimes away.
The little overhead is:

  • 1 heap allocation
  • slower operations of cloning a handle to data
  • slower operations on data
use std::sync::{
    atomic::{AtomicI32, Ordering},
    Arc,
};

trait Event {}

struct Window {
    callback: Box<dyn FnMut(&mut dyn Event) -> bool>,
}

impl Window {
    pub fn new() -> Window {
        Self {
            callback: Box::new(|_| false),
        }
    }

    pub fn set_event_callback<C>(&mut self, callback: C)
    where
        C: FnMut(&mut dyn Event) -> bool + 'static,
    {
        self.callback = Box::new(callback);
    }
}

struct Application {
    data: Arc<AtomicI32>,
}

impl Application {
    pub fn new() -> Self {
        Self {
            data: Arc::new(AtomicI32::new(0)),
        }
    }

    pub fn run(&self) {
        let mut window = Window::new();

        let data_handle = Arc::clone(&self.data);

        window.set_event_callback(move |_event| {
            data_handle.fetch_add(1, Ordering::SeqCst);
            false
        });
    }
}

fn main() {
    let app = Application::new();
    app.run();
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
Siiir
  • 210
  • 6
0

I've fixed your code just by adding explicit lifetime annotations.
I've promised the compiler that callback will live no longer than the unique reference to application it uses.

    trait Event {}
    
    struct Window<'f> {
        callback: Box<dyn FnMut(&mut dyn Event) -> bool + 'f>,
    }
    
    impl<'f> Window<'f> {
        pub fn new() -> Window<'f> {
            Self {
                callback: Box::new(|_| false),
            }
        }
    
        pub fn set_event_callback<'w, C>(&'w mut self, callback: C)
        where
            C: FnMut(&mut dyn Event) -> bool + 'f,
        {
            self.callback = Box::new(callback);
        }
    }
    
    struct Application {
        data: i32,
    }
    
    impl Application {
        pub fn new() -> Self {
            Self { data: 0 }
        }
    
        pub fn run<'s>(&'s mut self) {
            let mut window = Window::<'s>::new();
    
            window.set_event_callback(|event| {
                self.data += 1;
                false
            });
        }
    }
    
    fn main() {
        let mut app = Application::new();
        app.run();
    }

But remember to generally avoid Rust lifetimes. It's just more mild incarnation of C++ pointer demon that is safer to deal with.
Yet they are often hard to write. There are very few use cases when they're the best tool to solve a problem.

Siiir
  • 210
  • 6
  • "But remember to avoid Rust lifetimes as hell. It's just another incarnation of C++ pointer demon." No it's not. It's way better. Avoid it only while you're a beginner. – Chayim Friedman Jul 26 '23 at 11:18
  • You're right. Although Vladislav probably was a beginner as of time when he wrote this. I'm sorry. I elaborated my advice to not mislead people who might use lifetimes one day :) – Siiir Jul 26 '23 at 11:26
  • Thank you for your advice! Generally, the project I'm working on is a way to learn Rust, so I think it's good I have issues like that and try to solve them (apparently, sometimes I can't do it myself). I heard Rust's lifetimes were quite hard to master and I totally agree. Though I used to write C++ production code, Rust really drives me mad sometimes :D – Vladislav Jul 26 '23 at 16:45