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

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.

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.

Related

How to use the typestate pattern in other struct

I want to use the typestate pattern to define several states that allow some exclusive operations on each of them.
I'm using traits instead of an enum to allow further customizations.
So, I'm able to use this pattern until I try to include it inside a struct (the Session part) that is mutated when files are added, changed or removed.
trait IssueState {}
struct Open;
impl IssueState for Open {}
struct WIP {
elapsed_time: u32,
}
impl IssueState for WIP {}
struct Closed {
elapsed_time: u32,
}
impl IssueState for Closed {}
struct Issue<T: IssueState + ?Sized> {
state: Box<T>,
comments: Vec<String>,
}
impl<T: IssueState> Issue<T> {
pub fn comment<S: Into<String>>(&mut self, comment: S) -> &mut Self {
self.comments.push(comment.into());
self
}
}
impl Issue<Open> {
pub fn new() -> Self {
Self {
state: Box::new(Open),
comments: vec![],
}
}
pub fn start(self) -> Issue<WIP> {
Issue {
state: Box::new(WIP { elapsed_time: 0 }),
comments: self.comments,
}
}
}
impl Issue<WIP> {
pub fn work(&mut self, time: u32) -> &mut Self {
self.state.elapsed_time += time;
self
}
pub fn done(self) -> Issue<Closed> {
let elapsed_time = self.state.elapsed_time;
Issue {
state: Box::new(Closed { elapsed_time }),
comments: self.comments,
}
}
}
impl Issue<Closed> {
pub fn elapsed(&self) -> u32 {
self.state.elapsed_time
}
}
struct Session<T: IssueState> {
user: String,
current_issue: Issue<T>,
}
impl<T: IssueState> Session<T> {
pub fn new<S: Into<String>>(user: S, issue: Issue<T>) -> Self {
Self {
user: user.into(),
current_issue: issue,
}
}
pub fn comment<S: Into<String>>(&mut self, comment: S) {
self.current_issue.comment(comment);
}
}
impl Session<WIP> {
pub fn work(&mut self, time: u32) {
self.current_issue.work(time);
}
}
trait Watcher {
fn watch_file_create(&mut self);
fn watch_file_change(&mut self);
fn watch_file_delete(&mut self);
}
impl<T: IssueState> Watcher for Session<T> {
fn watch_file_create(&mut self) {
self.current_issue = Issue::<Open>::new();
}
fn watch_file_change(&mut self) {}
fn watch_file_delete(&mut self) {}
}
fn main() {
let open = Issue::<Open>::new();
let mut wip = open.start();
wip.work(10).work(30).work(60);
let closed = wip.done();
println!("Elapsed {}", closed.elapsed());
let mut session = Session::new("Reviewer", closed);
session.comment("It is OK");
session.watch_file_create();
}
Rust Playground (original)
Rust Playground (edited)
What can I do to fix the problems?
Is the typestate pattern limited to only some situations that do not depend a lot on external events? I mean, I'm trying to use it for processing events, but is it a dead end?, why?
Your Session has a Issue<dyn IssueState> member, but you want to implement its work method by calling Issue<WIP>'s work method. The compiler complains, because an Issue<dyn IssueState> is not (necessarily) a Issue<WIP> and so does not implement that method.

How do I dynamically create a struct from a string from input?

Given a situation where we receive inputs for some nodes type like 'nodeA' or 'nodeB', and we want to initialize structs with that same input. Is it possible without a gigantic switch block? These structs share similar behaviour (using a Trait for that) but with some differences.
pub trait Executable {
fn run(&self);
}
pub struct NodeA {}
impl Executable for NodeA {
fn run(&self) {}
}
pub struct NodeB {}
impl Executable for NodeB {
fn run(&self) {}
}
Flow:
User inputs 'nodeA'
Program initializes struct nodeA with some data
User inputs 'nodeB'
Program initializes struct nodeB with some data
...
To specify better, the final use case is reading a JSON file with all the nodes and respective params to be instantiated. Some of those nodes can come from external plugins, so the number of existing nodes can become very big.
For smaller, static number of nodes, I think a match - case construct is perfectly fine.
But if you have a larger number of nodes, or the available nodes is dynamically changing, I would implement something like this:
pub trait Executable {
fn run(&self);
}
pub struct NodeA {}
impl Executable for NodeA {
fn run(&self) {
println!("NodeA::run()");
}
}
pub struct NodeB {}
impl Executable for NodeB {
fn run(&self) {
println!("NodeB::run()");
}
}
pub trait Matcher {
fn try_match(&self, s: &str) -> Option<Box<dyn Executable>>;
}
pub struct NodeAMatcher;
pub struct NodeBMatcher;
impl Matcher for NodeAMatcher {
fn try_match(&self, s: &str) -> Option<Box<dyn Executable>> {
(s == "NodeA").then(|| Box::new(NodeA {}) as Box<dyn Executable>)
}
}
impl Matcher for NodeBMatcher {
fn try_match(&self, s: &str) -> Option<Box<dyn Executable>> {
(s == "NodeB").then(|| Box::new(NodeB {}) as Box<dyn Executable>)
}
}
struct MatcherRegistry {
matchers: Vec<Box<dyn Matcher>>,
}
impl MatcherRegistry {
fn new() -> Self {
Self { matchers: vec![] }
}
fn register_matcher(&mut self, matcher: impl Matcher + 'static) {
self.matchers.push(Box::new(matcher));
}
fn try_get_node(&self, s: &str) -> Option<Box<dyn Executable>> {
self.matchers
.iter()
.filter_map(|matcher| matcher.try_match(s))
.next()
}
fn try_execute(&self, s: &str) {
if let Some(node) = self.try_get_node(s) {
node.run();
} else {
println!("'{}' not found.", s);
}
}
}
fn main() {
let mut registry = MatcherRegistry::new();
registry.register_matcher(NodeAMatcher);
registry.register_matcher(NodeBMatcher);
registry.try_execute("NodeA");
registry.try_execute("NodeB");
registry.try_execute("NodeC");
}
NodeA::run()
NodeB::run()
'NodeC' not found.
Here, you have a factory pattern.
The structs NodeAMatcher and NodeBMatcher are factories for NodeA and NodeB. They can check if the input matches, and then create an Executable object.
Then, you collect all possible factories (or Matchers here) in a registry, here called MatcherRegistry. You can then, at runtime, add or remove matchers as you wish.
Of course, if you don't need to create a new object every time and the act of executing doesn't consume it, you can reduce the complexity a little by bypassing the factory pattern:
use std::collections::HashMap;
pub trait Executable {
fn run(&self);
}
pub struct NodeA {}
impl Executable for NodeA {
fn run(&self) {
println!("NodeA::run()");
}
}
pub struct NodeB {}
impl Executable for NodeB {
fn run(&self) {
println!("NodeB::run()");
}
}
struct ExecutableRegistry {
executables: HashMap<&'static str, Box<dyn Executable>>,
}
impl ExecutableRegistry {
fn new() -> Self {
Self {
executables: HashMap::new(),
}
}
fn register_executable(
&mut self,
command: &'static str,
executable: impl Executable + 'static,
) {
self.executables.insert(command, Box::new(executable));
}
fn try_execute(&self, s: &str) {
if let Some(node) = self.executables.get(s) {
node.run();
} else {
println!("'{}' not found.", s);
}
}
}
fn main() {
let mut registry = ExecutableRegistry::new();
registry.register_executable("NodeA", NodeA {});
registry.register_executable("NodeB", NodeB {});
registry.try_execute("NodeA");
registry.try_execute("NodeB");
registry.try_execute("NodeC");
}
Of course there exists a large mount of other variations of the same patterns. Which one you implement is up to you and your usecase.

Rust: How to return a reference to an Rc<RefCell<HashMap<K, V>> value?

I'm trying to learn Rust and I'm having some problems with different smart pointers and stuff.
Here is my code:
pub struct MyMap<T> {
map: Rc<RefCell<HashMap<String, T>>>,
}
impl <T> MyMap<T> {
// Not entire sure if it's supposed to be Option<Ref<T>> or something else here.
pub fn get(&self, key: &str) -> Option<Ref<T>> {
todo!("What do I do here?")
}
}
The closest I've got is by searching the HashMap twice:
impl <T> MyMap<T> {
pub fn get(&self, key: &str) -> Option<Ref<T>> {
if self.map.borrow().contains_key(key) {
Some(Ref::map(self.map.borrow(), |m| m.get(key).unwrap()))
} else {
None
}
}
}
Which isn't very elegant to say the least.
Two solutions that come to my mind:
Use the unstable Ref::filter_map, which will potentially be stabilized in 1.63.0.
Use a context manager pattern, which circumvents the entire problem.
filter_map:
#![feature(cell_filter_map)]
use std::{
cell::{Ref, RefCell},
collections::HashMap,
rc::Rc,
};
pub struct MyMap<T> {
map: Rc<RefCell<HashMap<String, T>>>,
}
impl<T> MyMap<T> {
pub fn get(&self, key: &str) -> Option<Ref<T>> {
Ref::filter_map(self.map.borrow(), |map| map.get(key)).ok()
}
}
fn main() {
let map: MyMap<u32> = MyMap {
map: Rc::new(RefCell::new(HashMap::from([
("meaning".to_string(), 42),
("nice".to_string(), 69),
]))),
};
println!("{:?}", map.get("meaning"));
}
Some(42)
Context manager:
The idea here is that instead of returning a reference, you pass in a closure of the action that needs the value. That entirely circumvents the lifetime problem, because the variables inside of the get (or with_value in the example below) are still in scope while the closure gets executed.
use std::{cell::RefCell, collections::HashMap, rc::Rc};
pub struct MyMap<T> {
map: Rc<RefCell<HashMap<String, T>>>,
}
impl<T> MyMap<T> {
pub fn with_value<F, O>(&self, key: &str, f: F) -> O
where
F: FnOnce(Option<&T>) -> O,
{
f(self.map.borrow().get(key))
}
}
fn main() {
let map: MyMap<u32> = MyMap {
map: Rc::new(RefCell::new(HashMap::from([
("meaning".to_string(), 42),
("nice".to_string(), 69),
]))),
};
map.with_value("meaning", |value| {
println!("{:?}", value);
});
}
Some(42)

Turn an upstream struct into a trait

I've got to interact with some upstream code that exposes an interface that could be a trait but is instead implemented directly on a struct. I'd like to pull out that interface into a trait so that my code can support alternative implementations. This can in fact be done, but isn't particularly ergonomic:
pub mod upstream_code {
pub struct Foo(());
impl Foo {
pub fn foo(&self) -> &'static str {
"foo"
}
}
impl Default for Foo {
fn default() -> Self {
Foo(())
}
}
}
mod my_code {
pub trait Foo {
fn foo(&self) -> &'static str;
}
impl Foo for super::upstream_code::Foo {
fn foo(&self) -> &'static str {
self.foo()
}
}
pub fn do_something<T: Foo>(t: T) {
println!("foo: {}", t.foo());
}
}
fn main() {
my_code::do_something(upstream_code::Foo::default());
}
Specifically, note that I have to regurgitate each function in wrapper form inside the impl Foo for super::upstream_code::Foo block.
There's Got To Be A Better Way! What's the most idiomatic way of handling this?

Mismatched types error when moving a method from a trait implementation to a trait definition

I have two structs, VM and Word. I need a new struct Control which behaves just like VM, but with one more field master. Because Rust has no inheritance, I try to extend the struct through composition. I move the functions of VM into a new trait Core, and implement Core for Control. The resulting code works.
struct Word<T> {
action: fn(target: &T)
}
struct VM {
word: Word<VM>
}
trait Core<T> {
fn word(&self) -> &Word<T>;
fn hello(&self) { println!("Hello"); }
fn execute(&self);
}
impl Core<VM> for VM {
fn word(&self) -> &Word<VM> { &self.word }
fn execute(&self) { (self.word().action)(self); }
}
struct Control {
word: Word<Control>,
master: i32,
}
impl Core<Control> for Control {
fn word(&self) -> &Word<Control> { &self.word }
fn execute(&self) { (self.word().action)(self); }
}
fn main() {
let vm = VM{
word: Word {action: Core::hello}
};
vm.execute();
let control = Control{
word: Word {action: Core::hello},
master: 0,
};
vm.execute();
}
The two implementation of execute are identical. So I move execute into trait Core.
trait Core<T> {
fn word(&self) -> &Word<T>;
fn hello(&self) { println!("Hello"); }
fn execute(&self) { (self.word().action)(self); }
}
impl Core<VM> for VM {
fn word(&self) -> &Word<VM> { &self.word }
}
impl Core<Control> for Control {
fn word(&self) -> &Word<Control> { &self.word }
}
Which compiles with the following error:
main.rs:14:44: 14:48 error: mismatched types:
expected `&T`,
found `&Self`
(expected type parameter,
found Self) [E0308]
main.rs:14 fn execute(&self) { (self.word().action)(self); }
How can I solve this problem?
if you move execute in Core, there is nothing in the trait definition that says that T is the same type as Self.
trait Core<T> {
fn word(&self) -> &Word<T>;
fn hello(&self) { println!("Hello"); }
fn execute(&self) {
(self.word() // this is a &Word<T>
.action) // this is a fn(T)
(self); // this type is Self. T is not necessarily = Self
}
}
When execute is in the impl Core<Control> for Control, the impl says that Self and T are both = Control, so execute works. But if T can be anything like in the definition of the trait, Rust can't let your code compile.
How to fix it depends on what you need to do.
If your trait is always meant to be implemented this way (impl Core<Something> for Something or impl Core<SomethingElse> for SomethingElse but NEVER as impl Core<Something> for SomethingElse), you can drop the parameter from the trait definition and have just:
trait Core: Sized {
fn word(&self) -> &Word<Self>; // now you can't parametrize what
// Word to return. It will be Self.
fn hello(&self) { println!("Hello"); }
fn execute(&self) { (self.word().action)(self); } // ...and this works
}

Resources