Simple Patterns
I’ve been using Rust on-again-off-again for several years now. I’m not ashamed to say that a lot of my Rust journey has mirrored my early C++ journey… I just add sigils (or take them away) until the compiler stops complaining. I also read The Rust Book but, again, to be honest, I usually do a very surface reading until I think I’ve figured out how to solve an immediate problem. But I’ve got some time finally and I’m going back through and updating/cleaning my mental model for how things work. Hopefully someone else well find this useful too.
When I’m learning a new language I generally think of two specific patterns I want to know. “How do I make a concrete implementation of an action and then pass that to something else” (Strategy) and “How do I make a concrete implementation of a thing when I don’t know what I need up-front?” (Factory).
Strategy
use std::cell::RefCell;
pub trait Strategy {
fn do_something(&self);
}
struct TypeA {}
struct TypeB {}
struct TypeC {
internal: RefCell<i32>
}
impl Strategy for TypeA {
fn do_something(&self) {
println!("A doing a thing");
}
}
impl Strategy for TypeB {
fn do_something(&self) {
println!("B doing a thing");
}
}
impl Strategy for TypeC {
fn do_something(&self) {
// "Internal mutability"
// Allows a change behind a &self reference
// without needing to change API to &mut self.
let mut underlying_value = self.internal.borrow_mut();
println!("C doing a thing {}", *underlying_value);
(*underlying_value) += 1;
}
}
// The Generic version uses monomorphism to do everything
// at compile time.
struct Executor<T:Strategy> {
strategy: T
}
impl <T:Strategy> Executor<T> {
fn new(input: T) -> Self {
Executor { strategy: input }
}
fn execute(&self) {
self.strategy.do_something();
}
}
// The dyn version uses vtables and does everything
// at runtime.
struct ExecutorWithDyn<'a> {
strategy: &'a dyn Strategy
}
impl <'a> ExecutorWithDyn<'a> {
fn new(input: &'a impl Strategy) -> Self {
ExecutorWithDyn { strategy: input }
}
fn execute(&self) {
self.strategy.do_something();
// This also works
// let e = &self.strategy;
// (*e).do_something();
}
}
fn main() {
let a = TypeA {};
let b = TypeB {};
let c = TypeC {internal: RefCell::new(0)};
let e = Executor::new(a);
e.execute();
let e1 = ExecutorWithDyn::new(&b);
e1.execute();
let e2 = ExecutorWithDyn::new(&c);
e2.execute();
e2.execute();
e2.execute();
e2.execute();
}
Factory
struct Factory {}
pub trait SomeImpl {
fn do_a_thing(&mut self);
}
struct TypeA {}
impl SomeImpl for TypeA {
fn do_a_thing(&mut self) {
println!("Type A did a thing");
}
}
impl Default for TypeA {
fn default() -> Self {
Self { }
}
}
struct TypeB {}
impl SomeImpl for TypeB {
fn do_a_thing(&mut self) {
println!("Type B did a thing");
}
}
impl Default for TypeB {
fn default() -> Self {
Self { }
}
}
impl Factory {
pub fn build(&self, input: i32) -> Box<dyn SomeImpl> {
if input % 2 == 0 {
return Box::new(TypeA{});
} else {
return Box::new(TypeB{});
}
}
pub fn build_generics<T:SomeImpl+Default>(&self) -> Box<T> {
return Box::new(T::default())
}
}
fn main() {
println!("Factory");
let f = Factory{};
let mut r = f.build(3);
r.do_a_thing();
// It's cool this works, but again, you have to
// know it at compile time.
let mut r1 = f.build_generics::<TypeA>();
r1.do_a_thing();
}