Last active
October 5, 2019 20:29
-
-
Save lancegatlin/f38fc64ab46803e7e42d1db86331c20b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// LESSON1: to return an Iterator, must pass in a reference (since Iterator always references something) | |
// LESSON1a: more generally, to return any type that "points" at data (i.e. whose lifetime is based on the lifetime of the input), must pass in a reference | |
// fn lesson1_broken(from: String) -> impl Iterator<Item=&str> { | |
// from.split(' ') // Iterator produced from borrow of from | |
// // from goes out of scope | |
// } | |
fn lesson1_fixed1(from: &mut String) -> impl Iterator<Item=&str> { | |
from.split(' ') | |
} | |
fn lesson1_fixed2(from: &String) -> impl Iterator<Item=&str> { | |
from.split(' ') | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// LESSON2: to return some Iterators, compiler needs help annotating lifetime (unsure why Elison rules don't help here) | |
// LESSON2: traits are assumed 'static lifetime (except LESSON1 'a is inferred by Elison rules?) | |
// fn lesson2_broken(from: &String) -> impl Iterator<Item=Result<i64,std::num::ParseIntError>> { | |
// from.split(' ').map(|s|s.parse::<i64>()) | |
// } | |
fn lesson2_fix<'a>(from: &'a String) -> impl Iterator<Item=Result<i64,std::num::ParseIntError>>+'a { | |
from.split(' ').map(|s|s.parse::<i64>()) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait Parser<E> { | |
fn push_err(&mut self, err: E); | |
} | |
trait ParseFrom<I,E> where Self:Sized { | |
fn parse_from(parser: &mut impl Parser<E>, from: I) -> Result<Self,E>; | |
} | |
enum Err { | |
SomeError | |
} | |
impl ParseFrom<String,Err> for i64 { | |
fn parse_from(_parser: &mut impl Parser<Err>, from: String) -> Result<i64,Err> { | |
from.parse().map_err(|_|Err::SomeError) | |
} | |
} | |
// LESSON 3: for unknown reasons have to move &mut into closure here | |
// fn lesson3_broken<'a>(parser: &'a mut impl Parser<Err>, from: &'a String) -> impl Iterator<Item=Result<i64,Err>>+'a { | |
// from.split(' ') | |
// .map(|s|i64::parse_from(parser,s.to_string())) | |
// } | |
fn lesson3_fixed<'a>(parser: &'a mut impl Parser<Err>, from: &'a String) -> impl Iterator<Item=Result<i64,Err>>+'a { | |
from.split(' ') | |
.map(move |s|i64::parse_from(parser,s.to_string())) | |
} | |
// LESSON 3a: this means that &mut can only be borrowed once given any set of lazy Iterator transformations | |
// LESSON 3b: this means any type that needs to be passed &mut should be broken up into smaller sub types | |
// if expected to be used multiple times within a set of lazy Iterator transformations | |
// LESSON 3c: this means up front design of structs must consider certain Rust specific implementation gotcha | |
// like this (i.e. data model influenced by tech limits and instead of reality it models) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait Parser<E> { | |
fn push_err(&mut self, err: E); | |
} | |
trait ParseFrom<I,E> where Self:Sized { | |
fn parse_from(parser: &mut impl Parser<E>, from: I) -> Result<Self,E>; | |
} | |
#[derive(Debug)] | |
enum Err { | |
SomeError | |
} | |
impl ParseFrom<String,Err> for i64 { | |
fn parse_from(_parser: &mut impl Parser<Err>, from: String) -> Result<i64,Err> { | |
from.parse().map_err(|_|Err::SomeError) | |
} | |
} | |
fn parse<'a>(parser: &'a mut impl Parser<Err>, from: &'a String) -> impl Iterator<Item=Result<i64,Err>>+'a { | |
from.split(' ') | |
.map(move |s|i64::parse_from(parser,s.to_string())) | |
} | |
// LESSON 4: any return value whose lifetime is tied to a borrow, keeps that borrow open | |
// fn lesson4_broken(parser: &mut impl Parser<Err>, from: &String) { | |
// // note: for unknown reasons, compiler knows that parser stays borrowed until i's lifetime is over | |
// let i = parse(parser, from); | |
// parser.push_err(Err::SomeError); | |
// i.for_each(|r|println!("{:?}",r)); | |
// } | |
fn lesson4_fixed1(parser: &mut impl Parser<Err>, from: &String) { | |
parse(parser, from); | |
parser.push_err(Err::SomeError); | |
} | |
fn lesson4_fixed2(parser: &mut impl Parser<Err>, from: &String) { | |
{ | |
let i = parse(parser, from); | |
i.for_each(|r|println!("{:?}",r)) | |
} | |
parser.push_err(Err::SomeError); | |
} | |
fn lesson4_fixed3(parser: &mut impl Parser<Err>, from: &String) { | |
let i = parse(parser, from); | |
i.for_each(|r|println!("{:?}",r)); // note: this moves i so it's lifetime ends here | |
parser.push_err(Err::SomeError); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON5: When reading source code for a function/method, the return type isn't always helpful. | |
Often a specific type is returned when the intention is for it to be used as a particular trait: | |
[From in std::str::mod.rs]: | |
... | |
impl str { | |
... | |
pub fn split<'a, P: Pattern<'a>>(&'a self, pat: P) -> Split<'a, P> { | |
... | |
} | |
... | |
} | |
Return type of split (Split<'a, P>) doesn't tell reader that it is returned with intention to be used as an | |
Iterator (though it is). It is up to reader to either just know this or have an IDE capable of discovering all | |
traits Split supports (or for reader to grep for this) and deciding how this set of traits is useful. In this | |
case, IntelliJ tells me std::slice::Split supports fmt::Debug, Clone, Iterator, DoubleEndedIterator, SplitIter | |
and FusedIterator traits. | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* LESSON 6 In a function/method, it is possible to accept both "impl Trait" and "dyn Trait" as generic | |
parameter BUT comes at cost of spamming ?Sized. Dynamic dispatch should only happen when called with dyn | |
Trait (todo: verify this). | |
Note: can use generic <T:Trait> or "impl Trait" declaration sugar. See ParseFrom below | |
Alternative1: can use "ref Trait" to accept both "impl Trait" and "dyn Trait" but this comes at run time cost | |
of dynamic dispatch for both impl Trait and dyn Trait. See ParseFrom below | |
Alternative2: can put T:Trait+Sized in trait decl instead of method see ParseFrom2 below | |
*/ | |
#[derive(Clone, Debug)] | |
pub enum Selector { | |
// todo: make this &str | |
ByField(String), | |
ByIndex(usize) | |
} | |
#[derive(Clone, Debug)] | |
pub struct InputLocator { | |
// todo: file, source line/cursor, optional end line/cursor | |
pub opt_source: Option<String>, | |
pub selector: Vec<Selector> | |
} | |
pub trait ErrCtrl<E> { | |
fn push_err(&mut self, err: E); | |
fn try_warn(&mut self, err: E) -> Result<(),E>; | |
fn try_ignore(&mut self, err: E) -> Result<(), E>; | |
fn try_fix(&mut self, fix_message: String, err: E) -> Result<(), E>; | |
} | |
pub trait Parser<E> : ErrCtrl<E> { | |
fn input_locator(&self) -> InputLocator; | |
fn set_input_locator(&mut self, input_locator: InputLocator); | |
} | |
pub trait ParseFrom<I,E> : Sized { | |
fn parse_from_generic<P:Parser<E>+?Sized>(parser: &mut P, from: I) -> Result<Self,E>; | |
fn parse_from_dyn(parser: &mut Parser<E>, from: I) -> Result<Self,E>; | |
fn parse_from_impl(parser: &mut (impl Parser<E>+?Sized), from: I) -> Result<Self,E>; | |
} | |
pub trait ParseFrom2<I,E,P:Parser<E>+?Sized> : Sized { | |
fn parse_from_generic2(parser: &mut P, from: I) -> Result<Self,E>; | |
} | |
struct DummyParser { } | |
impl ErrCtrl<String> for DummyParser { | |
fn push_err(&mut self, err: String) { | |
println!("DummyParser.push_err") | |
} | |
fn try_warn(&mut self, err: String) -> Result<(),String> { | |
println!("DummyParser.try_warn"); | |
Ok(()) | |
} | |
fn try_ignore(&mut self, err: String) -> Result<(), String> { | |
println!("DummyParser.try_ignore"); | |
Ok(()) | |
} | |
fn try_fix(&mut self, fix_message: String, err: String) -> Result<(), String> { | |
println!("DummyParser.try_fix"); | |
Ok(()) | |
} | |
} | |
impl Parser<String> for DummyParser { | |
fn input_locator(&self) -> InputLocator { | |
println!("DummyParser.input_locator"); | |
InputLocator { | |
opt_source: None, | |
selector: Vec::new() | |
} | |
} | |
fn set_input_locator(&mut self, input_locator: InputLocator) { | |
println!("DummyParser.set_input_locator") | |
} | |
} | |
impl ParseFrom<String,String> for i64 { | |
fn parse_from_generic<P:Parser<String>+?Sized>(parser: &mut P, from: String) -> Result<i64,String> { | |
match from.parse::<i64>().map_err(|err|err.to_string()) { | |
Ok(value) => Ok(value), | |
Err(err) => { | |
match parser.try_ignore(err) { | |
Ok(()) => Ok(-1), | |
Err(err) => Err(err) | |
} | |
} | |
} | |
} | |
fn parse_from_dyn(parser: &mut Parser<String>, from: String) -> Result<i64,String> { | |
match from.parse::<i64>().map_err(|err|err.to_string()) { | |
Ok(value) => Ok(value), | |
Err(err) => { | |
match parser.try_ignore(err) { | |
Ok(()) => Ok(-1), | |
Err(err) => Err(err) | |
} | |
} | |
} | |
} | |
fn parse_from_impl(parser: &mut (impl Parser<String>+?Sized), from: String) -> Result<i64,String> { | |
match from.parse::<i64>().map_err(|err|err.to_string()) { | |
Ok(value) => Ok(value), | |
Err(err) => { | |
match parser.try_ignore(err) { | |
Ok(()) => Ok(-1), | |
Err(err) => Err(err) | |
} | |
} | |
} | |
} | |
} | |
impl<P:Parser<String>+?Sized> ParseFrom2<String,String,P> for i64 { | |
fn parse_from_generic2(parser: &mut P, from: String) -> Result<i64,String> { | |
match from.parse::<i64>().map_err(|err|err.to_string()) { | |
Ok(value) => Ok(value), | |
Err(err) => { | |
match parser.try_ignore(err) { | |
Ok(()) => Ok(-1), | |
Err(err) => Err(err) | |
} | |
} | |
} | |
} | |
} | |
fn main() { | |
let mut p = DummyParser { }; | |
let i1 : i64 = i64::parse_from_generic(&mut p, "123".to_string()).unwrap(); | |
let i2 : i64 = i64::parse_from_dyn(&mut p, "234".to_string()).unwrap(); | |
let i3 : i64 = i64::parse_from_impl(&mut p, "345".to_string()).unwrap(); | |
let i4 : i64 = i64::parse_from_generic2(&mut p, "456".to_string()).unwrap(); | |
let mut pbox : Box<dyn Parser<String>> = Box::new(DummyParser { }); | |
let i5 : i64 = i64::parse_from_generic(&mut *pbox, "abc".to_string()).unwrap(); | |
let i6 : i64 = i64::parse_from_dyn(&mut *pbox, "abc".to_string()).unwrap(); | |
let i7 : i64 = i64::parse_from_impl(&mut *pbox, "abc".to_string()).unwrap(); | |
let i8 : i64 = i64::parse_from_generic2(&mut *pbox, "abc".to_string()).unwrap(); | |
println!("i1={}",i1); | |
println!("i2={}",i2); | |
println!("i3={}",i3); | |
println!("i4={}",i4); | |
println!("i5={}",i5); | |
println!("i6={}",i6); | |
println!("i7={}",i7); | |
println!("i8={}",i8) | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 7: When using a trait to create extension methods, implementing methods in the trait that pass self as parm require Self:Sized. But move implementations to impl removes this requirement. | |
TODO: verify & example (reminder: ParserExt) | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 8: can't use extension methods with references since Sized is added | |
*/ | |
trait Parser<E> { | |
fn begin_field(&mut self); | |
fn end_field(&mut self); | |
} | |
trait ParseFrom<I,E> : Sized { | |
fn parse_from(parser: &mut Parser<E>, from: I) -> Result<Self,E>; | |
} | |
trait ParserExt<E> : Parser<E> { | |
fn parse_field_FAILS<I,O:ParseFrom<I,E>>(&mut self, field: String, from: I) -> Result<O,E> { | |
self.begin_field(); | |
let retv = O::parse_from(self, from); | |
self.end_field(); | |
retv | |
} | |
} | |
fn parse_field_WORKS<E,I,O:ParseFrom<I,E>>(parser: &mut Parser<E>, field: String, from: I) -> Result<O,E> { | |
parser.begin_field(); | |
let retv = O::parse_from(parser, from); | |
parser.end_field(); | |
retv | |
} | |
struct DummyParser { } | |
impl Parser<String> for DummyParser { | |
fn begin_field(&mut self) { } | |
fn end_field(&mut self) { } | |
} | |
impl ParseFrom<String,String> for i64 { | |
fn parse_from(parser: &mut Parser<String>, from: String) -> Result<i64,String> { | |
from.parse::<i64>().map_err(|err|err.to_string()) | |
} | |
} | |
fn main() { | |
let mut p = DummyParser { }; | |
let i : i64 = parse_field_WORKS(&mut p, "field".to_string(), "123".to_string()).unwrap(); | |
println!("i={}",i) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON9: Rust tracks moves and borrows on fields of a struct, but not when struct is used inside a closure. It borrows/moves entire struct | |
*/ | |
// todo: example |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON10: example of Rust traits and trait objects and lack of inheritance (and a workaround) | |
*/ | |
// Rust trait as type-class | |
trait Semigroup { | |
fn combine(&self, other: &Self) -> Self; | |
} | |
// note: not inheritance! | |
trait Monoid : Semigroup { | |
fn zero() -> Self; | |
} | |
// note: try commenting this out | |
impl Semigroup for String { | |
fn combine(&self, other: &Self) -> Self { | |
let mut retv = String::with_capacity(self.len()+other.len()); | |
retv.push_str(self); | |
retv.push_str(other); | |
retv | |
} | |
} | |
impl Monoid for String { | |
fn zero() -> String { | |
String::new() | |
} | |
} | |
// note: none of the above can be used as a trait object | |
/* | |
Rust trait as "trait obect" (OOP dynamic dispatch) | |
A trait is object-safe if both of these are true: | |
the trait does not require that Self: Sized (note: required to return Self) | |
all of its methods are object-safe | |
Each method must require that Self: Sized or all of the following: | |
must not have any type parameters (generics) | |
must not use Self type | |
must accept self as first param (aka OOP this pointer) | |
*/ | |
// note: can't use above since they are not "object-safe" | |
trait ByteWriter { | |
fn write_byte(&mut self, byte: u8) -> (); | |
} | |
// note: not inheritance! | |
trait TextWriter : ByteWriter { | |
fn write_string(&mut self, s: String) -> (); | |
// note: work around for lack of inheritance or casting below | |
fn as_byte_writer(&mut self) -> &mut dyn ByteWriter; | |
} | |
// dynamic dispatch | |
fn foo(w: &mut dyn TextWriter) -> () { | |
w.write_string("foo".to_string()) | |
} | |
fn bar(w: &mut dyn ByteWriter) -> () { | |
w.write_byte('b' as u8); | |
w.write_byte('a' as u8); | |
w.write_byte('r' as u8) | |
} | |
fn foobar(w: &mut dyn TextWriter) -> () { | |
foo(w); | |
// neither of these work since TextWriter doesn't inherit ByteWriter | |
// and there is no way to cast from TextWriter to ByteWriter due to way | |
// rust implements trait objects | |
//bar(w); | |
//bar(w as &mut dyn ByteWriter) | |
// using work around | |
bar(w.as_byte_writer()) | |
} | |
impl ByteWriter for String { | |
fn write_byte(&mut self, byte: u8) -> () { | |
self.push(byte as char) | |
} | |
} | |
impl TextWriter for String { | |
fn write_string(&mut self, s: String) -> () { | |
self.push_str(&s) | |
} | |
fn as_byte_writer(&mut self) -> &mut dyn ByteWriter { | |
self | |
} | |
} | |
fn main() { | |
let s : String = "hello".to_string(); | |
// note: static dispatch based on types known at compile time | |
let s1 = s.combine(&", world!".to_string()); | |
println!("combine='{}'", s1); | |
let s2 = String::zero(); | |
println!("zero='{}'", s2); | |
let mut s3 = String::new(); | |
foo(&mut s3); | |
bar(&mut s3); | |
println!("foobar1='{}'", s3); | |
let mut s4 = String::new(); | |
foobar(&mut s4); | |
println!("foobar2='{}'", s4) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 11: Service DI injection pattern | |
*/ | |
/* | |
"service" pattern (allows for configurable DI) | |
* self is encapsulated code object | |
* config/dependecies/implemenation are all encapsulated | |
* can inject different implementations | |
*/ | |
trait Validator<A,E> { | |
fn validate(&self, a: A, errs: &mut Vec<E>) -> (); | |
} | |
/* | |
type-class pattern | |
* self is type being acted on | |
* all code must live in same function | |
* no encapsulation of config | |
* no injection of dependencies | |
trait Validator<E> { | |
fn validate(&self, config: &Config, errs: &mut Vec<E>) -> (); | |
} | |
*/ | |
struct AgeValidator { | |
max_age: usize | |
} | |
impl Validator<usize,String> for AgeValidator { | |
fn validate(&self, age: usize, errs: &mut Vec<String>) -> () { | |
if age > self.max_age { | |
errs.push(format!("Max age is {} but found {}", self.max_age, age)) | |
} | |
} | |
} | |
struct NameValidator { | |
min_len: usize, | |
max_len: usize | |
} | |
impl Validator<String,String> for NameValidator { | |
fn validate(&self, name: String, errs: &mut Vec<String>) -> () { | |
if name.len() > self.max_len { | |
errs.push(format!("Name max length is {} but found {}", self.max_len, name.len())) | |
} | |
if name.len() < self.min_len { | |
errs.push(format!("Name min length is {} but found {}", self.min_len, name.len())) | |
} | |
} | |
} | |
struct Person { | |
name: String, | |
age: usize | |
} | |
struct PersonValidator { | |
pub name_validator: Box<Validator<String,String>>, | |
pub age_validator: Box<Validator<usize,String>> | |
} | |
impl Validator<Person,String> for PersonValidator { | |
fn validate(&self, person: Person, errs: &mut Vec<String>) -> () { | |
self.name_validator.validate(person.name, errs); | |
self.age_validator.validate(person.age, errs); | |
} | |
} | |
struct Config { | |
max_age: usize, | |
min_name_len: usize, | |
max_name_len: usize, | |
} | |
impl From<&Config> for Box<Validator<String,String>> { | |
fn from(config: &Config) -> Self { | |
Box::new(NameValidator { | |
min_len: config.min_name_len, | |
max_len: config.max_name_len | |
}) | |
} | |
} | |
impl From<&Config> for Box<Validator<usize,String>> { | |
fn from(config: &Config) -> Self { | |
Box::new(AgeValidator { | |
max_age: config.max_age | |
}) | |
} | |
} | |
impl From<&Config> for Box<Validator<Person,String>> { | |
fn from(config: &Config) -> Self { | |
// note: could inject different Person Validator depending on config | |
Box::new(PersonValidator { | |
name_validator: config.into(), | |
age_validator: config.into() | |
}) | |
} | |
} | |
fn main() { | |
let config = Config { | |
max_age: 120, | |
min_name_len: 2, | |
max_name_len: 30, | |
}; | |
let validator : Box<Validator<Person,String>> = (&config).into(); | |
let mut errs = Vec::new(); | |
let person = Person { | |
name: "a".to_string(), | |
age: 121 | |
}; | |
validator.validate(person, &mut errs); | |
println!("{:?}",errs) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 12: Use trait to create a "type alias" for functions & closures | |
*/ | |
// note: doesn't work (since trait isn't a type?) | |
// type Validator<I,E> = FnMut(&I) -> Result<(),E> | |
pub trait Validator<I,E> { | |
fn validate(&mut self, item: &I) -> Result<(),E>; | |
} | |
impl<I,E,F> Validator<I,E> for F where F:FnMut(&I) -> Result<(),E> { | |
fn validate(&mut self, item: &I) -> Result<(), E> { | |
(self)(item) | |
} | |
} | |
fn i64_validator1(i: &i64) -> Result<(), String> { | |
if *i == 0 { | |
Ok(()) | |
} else { | |
Err("terrible".to_string()) | |
} | |
} | |
fn mk_i64_validator2(v: i64) -> impl Validator<i64, String> { | |
move |i: &i64| { | |
if *i == v { | |
Ok(()) | |
} else { | |
Err("also terrible".to_string()) | |
} | |
} | |
} | |
fn main() { | |
println!("{:?}", i64_validator1.validate(&1)); | |
let mut i64_validator2 = mk_i64_validator2(2); | |
println!("{:?}", i64_validator2.validate(&1)); | |
let mut i64_validator3 = |i: &i64| { | |
if *i == 0 { | |
Ok(()) | |
} else { | |
Err("very very terrible".to_string()) | |
} | |
}; | |
println!("{:?}", i64_validator3.validate(&1)) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 13: working with &mut: | |
1) use assign to &mut self to overrwrite entire self | |
2) use std::mem::swap to effectively "move out" of &mut variable | |
*/ | |
enum Storage { | |
Empty, | |
Row(Vec<u8>), | |
Rows(Vec<Vec<u8>>) | |
} | |
impl Storage { | |
fn push_row(&self, row: Vec<u8>) { | |
*self = match self { | |
Empty => Row(row), | |
Row(first_row) => { | |
// note: first_row is &mut, can't move out | |
let owned_row = Vec::new(); | |
std::mem::swap(first_row,owned_row); | |
Rows(vec![ | |
owned_row, | |
row | |
]) | |
}, | |
Rows(rows) => rows.push(row) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
(WIP) LESSON 14: injecting config into trait impls is difficult | |
*/ | |
use std::fmt; | |
enum NumberFormat { | |
Decimal, | |
Hex | |
} | |
struct PrintConfig { | |
number_format: NumberFormat | |
} | |
trait HasPrintConfig { | |
fn print_config(&self) -> &PrintConfig; | |
} | |
trait Logger { | |
fn info(&mut self, msg: &str); | |
} | |
trait HasLogger { | |
fn logger(&mut self) -> &mut dyn Logger; | |
} | |
trait Print<World> { | |
fn print(&self, world: &mut World, f: &mut fmt::Formatter) -> fmt::Result; | |
} | |
impl<World:HasLogger+HasPrintConfig> Print<World> for i64 { | |
fn print(&self, world: &mut World, f: &mut fmt::Formatter) -> fmt::Result { | |
match world.print_config().number_format { | |
NumberFormat::Decimal => { | |
world.logger().info("wrote decimal"); | |
write!(f, "{}", self) | |
}, | |
NumberFormat::Hex => { | |
world.logger().info("wrote hex"); | |
write!(f, "{:X}", self) | |
} | |
} | |
} | |
} | |
struct StdOutLogger(); | |
impl Logger for StdOutLogger { | |
fn info(&mut self, msg: &str) { | |
println!("[INFO]: {}", msg) | |
} | |
} | |
struct World { | |
print_config: PrintConfig, | |
std_out_logger: StdOutLogger | |
} | |
impl HasPrintConfig for World { | |
fn print_config(&self) -> &PrintConfig { | |
&self.print_config | |
} | |
} | |
impl HasLogger for World { | |
fn logger(&mut self) -> &mut dyn Logger { | |
&mut self.std_out_logger | |
} | |
} | |
struct WriteFnMut<T>(T) where T: FnMut(&mut fmt::Formatter) -> fmt::Result; | |
impl<T> fmt::Display for WriteFnMut<T> where T: FnMut(&mut fmt::Formatter) -> fmt::Result { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
self.0(f) | |
} | |
} | |
pub fn write_to_string<F>(f: F) -> String where | |
F: (FnMut(&mut fmt::Formatter) -> fmt::Result) { | |
let write_fn = WriteFnMut(f); | |
format!("{}", write_fn) | |
} | |
fn main() { | |
let mut world1 = World { | |
print_config: PrintConfig { | |
number_format: NumberFormat::Hex | |
}, | |
std_out_logger: StdOutLogger() | |
}; | |
let mut world2 = World { | |
print_config: PrintConfig { | |
number_format: NumberFormat::Decimal | |
}, | |
std_out_logger: StdOutLogger() | |
}; | |
let i64 = 123; | |
let result1 = write_to_string(|f| i64.print(&mut world1, f)); | |
println!("{}", result1); | |
let result2 = write_to_string(|f| i64.print(&mut world2,f)); | |
println!("{}", result2); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 15: a basic DI pattern | |
*/ | |
trait Logger { | |
fn info(&mut self, msg: &str); | |
} | |
struct StdOutLogger(); | |
impl Logger for StdOutLogger { | |
fn info(&mut self, msg: &str) { | |
println!("{}", msg) | |
} | |
} | |
impl Logger for &mut StdOutLogger { | |
fn info(&mut self, msg: &str) { | |
println!("{}", msg) | |
} | |
} | |
trait Config1 { | |
fn a_setting(&self) -> bool; | |
} | |
trait Config2 { | |
fn another_setting(&self) -> bool; | |
} | |
trait Config : Config1+Config2 { } | |
trait HasLogger { | |
type Logger : Logger; | |
fn logger(&mut self) -> &mut Self::Logger; | |
} | |
trait ContextOut : HasLogger { } | |
struct MyConfig { | |
a_setting: bool, | |
another_setting: bool | |
} | |
impl Config1 for MyConfig { | |
fn a_setting(&self) -> bool { | |
self.a_setting | |
} | |
} | |
impl Config2 for MyConfig { | |
fn another_setting(&self) -> bool { | |
self.another_setting | |
} | |
} | |
struct MyFX<L:Logger> { | |
logger: L | |
} | |
impl<L:Logger> HasLogger for MyFX<L> { | |
type Logger = L; | |
fn logger(&mut self) -> &mut Self::Logger { | |
&mut self.logger | |
} | |
} | |
fn do_stuff<Cfg:Config1, FX:HasLogger>(cfg: &Cfg, i: i64, fx: &mut FX) -> i64 { | |
if cfg.a_setting() == true { | |
i + 1 | |
} else { | |
fx.logger().info("add 2"); | |
i + 2 | |
} | |
} | |
trait StuffDoer { | |
fn do_stuff(&mut self, i: i64) -> i64; | |
} | |
impl<F> StuffDoer for F where F:FnMut(i64) -> i64 { | |
fn do_stuff(&mut self, i: i64) -> i64 { | |
(self)(i) | |
} | |
} | |
fn mk_do_stuff<'a, Cfg:Config1, FX:HasLogger>(cfg: &'a Cfg, fx: &'a mut FX) -> impl StuffDoer+'a { | |
move |i| { | |
do_stuff(cfg, i, fx) | |
} | |
} | |
fn main() { | |
let cfg = MyConfig { | |
a_setting : false, | |
another_setting: true | |
}; | |
let mut logger = StdOutLogger(); | |
let mut fx = MyFX { | |
logger: &mut logger | |
}; | |
let mut doer = mk_do_stuff(&cfg, &mut fx); | |
let result = doer.do_stuff(40); | |
println!("{}", result) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
LESSON 16: a better DI pattern (that works and is safe by accident) | |
*/ | |
use std::cell::RefCell; | |
use std::sync::Mutex; | |
trait MutLogger { | |
fn log(&mut self, msg: String); | |
} | |
struct MemLogger(Vec<String>); | |
impl MutLogger for MemLogger { | |
fn log(&mut self, msg: String) { | |
self.0.push(msg) | |
} | |
} | |
trait Logger { | |
fn log(&self, msg: String); | |
} | |
// single threaded | |
impl<ML:MutLogger> Logger for RefCell<ML> { | |
fn log(&self, msg: String) { | |
self.borrow_mut().log(msg); | |
} | |
} | |
// multi threaded | |
impl<ML:MutLogger> Logger for Mutex<ML> { | |
fn log(&self, msg: String) { | |
self.lock().unwrap().log(msg); | |
} | |
} | |
trait Service1 { | |
fn foo(&self, i: i64); | |
} | |
struct Service1Impl<'a> { | |
logger: &'a dyn Logger, | |
i: i64 | |
} | |
impl<'a> Service1 for Service1Impl<'a> { | |
fn foo(&self, i: i64) { | |
self.logger.log("Service1Impl=".to_string() + &(self.i + i).to_string()); | |
} | |
} | |
struct MemLogger2<'a> { | |
s1: &'a dyn Service1, | |
msgs: Vec<String> | |
} | |
impl<'a> MutLogger for MemLogger2<'a> { | |
fn log(&mut self, msg: String) { | |
// note: this would be dangerous if we could do this | |
// note: but Rust coinicidentally won't let us create cycles | |
// note: in the dependency graph of structs as we build them | |
self.s1.foo(234); | |
self.msgs.push(msg) | |
} | |
} | |
fn main() { | |
let logger = RefCell::new(MemLogger(Vec::new())); | |
let s1 = Service1Impl { | |
logger: &logger, | |
i: 123 | |
}; | |
s1.foo(456); | |
let borrowed = logger.borrow(); | |
borrowed.0.iter().for_each(|msg| println!("{}",msg)) | |
// let logger2 = RefCell::new(MemLogger2 { | |
// Vec::new(), | |
// s1: &s2 // forward reference, won't compile | |
// }; | |
// let s2 = Service1Impl { | |
// logger: &logger2, | |
// i: 123 | |
// }; | |
// s2.foo(456); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment