Skip to content

Instantly share code, notes, and snippets.

@lancegatlin
Last active October 5, 2019 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lancegatlin/f38fc64ab46803e7e42d1db86331c20b to your computer and use it in GitHub Desktop.
Save lancegatlin/f38fc64ab46803e7e42d1db86331c20b to your computer and use it in GitHub Desktop.
// 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(' ')
}
// 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>())
}
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)
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);
}
/*
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.
*/
/* 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)
}
/*
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)
*/
/*
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)
}
/*
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
/*
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)
}
/*
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)
}
/*
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))
}
/*
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)
}
}
}
/*
(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);
}
/*
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)
}
/*
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