tombh
tombh

Reputation: 427

Allowing extension (beyond the crate) of implementation with event loop

Within the crate we can happily do something like this:

mod boundary {
    pub struct EventLoop;
    
    impl EventLoop {
        pub fn run(&self) {
            for _ in 0..2 {
                self.handle("bundled");
                self.foo();
            }
        }
    
        pub fn handle(&self, message: &str) {
            println!("{} handling", message)
        }
    }
    
    pub trait EventLoopExtend {
        fn foo(&self);
    }
}

use boundary::EventLoopExtend;

impl EventLoopExtend for boundary::EventLoop {
    fn foo(&self) {
        self.handle("extended")
    }
}   

fn main() {
    let el = boundary::EventLoop{};
    el.run();
}

But if mod boundary were a crate boundary we get error[E0117]: only traits defined in the current crate can be implemented for arbitrary types.

I gather that a potential solution to this could be the New Type idiom, so something like this:

mod boundary {
    pub struct EventLoop;
    
    impl EventLoop {
        pub fn run(&self) {
            for _ in 0..2 {
                self.handle("bundled");
                self.foo();
            }
        }
    
        pub fn handle(&self, message: &str) {
            println!("{} handling", message)
        }
    }
    
    pub trait EventLoopExtend {
        fn foo(&self);
    }
    
    impl EventLoopExtend for EventLoop {
        fn foo(&self) {
            self.handle("unimplemented")
        }
    }
}

use boundary::{EventLoop, EventLoopExtend};

struct EventLoopNewType(EventLoop);

impl EventLoopExtend for EventLoopNewType {
    fn foo(&self) {
        self.0.handle("extended")
    }
}   

fn main() {
    let el = EventLoopNewType(EventLoop {});
    el.0.run();
}

But then the problem here is that the extended trait behaviour isn't accessible from the underlying EventLoop instance.

I'm still quite new to Rust, so I'm sure I'm missing something obvious, I wouldn't be surprised if I need to take a completely different approach.

Specifically in my case, the event loop is actually from wgpu, and I'm curious if it's possible to build a library where end users can provide their own "render pass" stage.

Upvotes: 2

Views: 57

Answers (1)

tombh
tombh

Reputation: 427

Thanks to @AlexN's comment I dug deeper into the Strategy Pattern and found a solution:

mod boundary {
    pub struct EventLoop<'a, T: EventLoopExtend> {
        extension: &'a T
    }
    
    impl<'a, T: EventLoopExtend> EventLoop<'a, T> {
        pub fn new(extension: &'a T) -> Self {
            Self { extension }
        }
        
        pub fn run(&self) {
            for _   in 0..2 {
                self.handle("bundled");
                self.extension.foo(self);
            }
        }
    
        pub fn handle(&self, message: &str) {
            println!("{} handling", message)
        }
    }
    
    pub trait EventLoopExtend {
        fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
            el.handle("unimplemented")
        }
    }
}

use boundary::{EventLoop, EventLoopExtend};

struct EventLoopExtension;

impl EventLoopExtend for EventLoopExtension {
    fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
        el.handle("extended")
    }
}   

fn main() {
    let el = EventLoop::new(&EventLoopExtension {});
    el.run();
}

The basic idea is to use generics with a trait bound. I think the first time I looked into this approach I was worried about type recursion. But it turns out passing the EventLoop object as an argument to EventLoopExtend trait methods is perfectly reasonable.

Upvotes: 1

Related Questions