Reputation: 427
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
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