diff --git a/src/patterns/behavioural/newtype.md b/src/patterns/behavioural/newtype.md index 9e9f019..d7b5a82 100644 --- a/src/patterns/behavioural/newtype.md +++ b/src/patterns/behavioural/newtype.md @@ -17,42 +17,31 @@ This creates a new type, rather than an alias to a type (`type` items). ## Example -```rust,ignore -// Some type, not necessarily in the same module or even crate. -struct Foo { - //.. -} - -impl Foo { - // These functions are not present on Bar. - //.. -} - -// The newtype. -pub struct Bar(Foo); +```rust +use std::fmt::Display; -impl Bar { - // Constructor. - pub fn new( - //.. - ) -> Self { - - //.. +// Create Newtype Password to override the Display trait for String +struct Password(String); +impl Display for Password { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "****************") } - - //.. } fn main() { - let b = Bar::new(...); - - // Foo and Bar are type incompatible, the following do not type check. - // let f: Foo = b; - // let b: Bar = Foo { ... }; + let unsecured_password: String = "ThisIsMyPassword".to_string(); + let secured_password: Password = Password(unsecured_password.clone()); + println!("unsecured_password: {unsecured_password}"); + println!("secured_password: {secured_password}"); } ``` +```shell +unsecured_password: ThisIsMyPassword +secured_password: **************** +``` + ## Motivation The primary motivation for newtypes is abstraction. It allows you to share @@ -108,4 +97,4 @@ with `Bar`. - [Type aliases](https://doc.rust-lang.org/stable/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases) - [derive_more](https://crates.io/crates/derive_more), a crate for deriving many builtin traits on newtypes. -- [The Newtype Pattern In Rust](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html) +- [The Newtype Pattern In Rust](https://web.archive.org/web/20230519162111/https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html) diff --git a/src/patterns/structural/compose-structs.md b/src/patterns/structural/compose-structs.md index 013f6cb..62fd30e 100644 --- a/src/patterns/structural/compose-structs.md +++ b/src/patterns/structural/compose-structs.md @@ -1,6 +1,4 @@ -# Compose structs together for better borrowing - -TODO - this is not a very snappy name +# Struct decomposition for independent borrowing ## Description @@ -20,71 +18,93 @@ Here is a contrived example of where the borrow checker foils us in our plan to use a struct: ```rust -struct A { - f1: u32, - f2: u32, - f3: u32, +struct Database { + connection_string: String, + timeout: u32, + pool_size: u32, } -fn foo(a: &mut A) -> &u32 { &a.f2 } -fn bar(a: &mut A) -> u32 { a.f1 + a.f3 } +fn print_database(database: &Database) { + println!("Connection string: {}", database.connection_string); + println!("Timeout: {}", database.timeout); + println!("Pool size: {}", database.pool_size); +} -fn baz(a: &mut A) { - // The later usage of x causes a to be borrowed for the rest of the function. - let x = foo(a); - // Borrow checker error: - // let y = bar(a); // ~ ERROR: cannot borrow `*a` as mutable more than once - // at a time - println!("{}", x); +fn main() { + let mut db = Database { + connection_string: "initial string".to_string(), + timeout: 30, + pool_size: 100, + }; + + let connection_string = &mut db.connection_string; + print_database(&db); // Immutable borrow of `db` happens here + // *connection_string = "new string".to_string(); // Mutable borrow is used + // here } ``` -We can apply this design pattern and refactor `A` into two smaller structs, thus -solving the borrow checking issue: +We can apply this design pattern and refactor `Database` into three smaller +structs, thus solving the borrow checking issue: ```rust -// A is now composed of two structs - B and C. -struct A { - b: B, - c: C, -} -struct B { - f2: u32, -} -struct C { - f1: u32, - f3: u32, +// Database is now composed of three structs - ConnectionString, Timeout and PoolSize. +// Let's decompose it into smaller structs +#[derive(Debug, Clone)] +struct ConnectionString(String); + +#[derive(Debug, Clone, Copy)] +struct Timeout(u32); + +#[derive(Debug, Clone, Copy)] +struct PoolSize(u32); + +// We then compose these smaller structs back into `Database` +struct Database { + connection_string: ConnectionString, + timeout: Timeout, + pool_size: PoolSize, } -// These functions take a B or C, rather than A. -fn foo(b: &mut B) -> &u32 { &b.f2 } -fn bar(c: &mut C) -> u32 { c.f1 + c.f3 } +// print_database can then take ConnectionString, Timeout and Poolsize struct instead +fn print_database(connection_str: ConnectionString, + timeout: Timeout, + pool_size: PoolSize) { + println!("Connection string: {:?}", connection_str); + println!("Timeout: {:?}", timeout); + println!("Pool size: {:?}", pool_size); +} -fn baz(a: &mut A) { - let x = foo(&mut a.b); - // Now it's OK! - let y = bar(&mut a.c); - println!("{}", x); +fn main() { + // Initialize the Database with the three structs + let mut db = Database { + connection_string: ConnectionString("localhost".to_string()), + timeout: Timeout(30), + pool_size: PoolSize(100), + }; + + let connection_string = &mut db.connection_string; + print_database(connection_string.clone(), db.timeout, db.pool_size); + *connection_string = ConnectionString("new string".to_string()); } ``` ## Motivation -TODO Why and where you should use the pattern +This pattern is most useful, when you have a struct that ended up with a lot of +fields that you want to borrow independently. Thus having a more flexible +behaviour in the end. ## Advantages -Lets you work around limitations in the borrow checker. - -Often produces a better design. +Decomposition of structs lets you work around limitations in the borrow checker. +And it often produces a better design. ## Disadvantages -Leads to more verbose code. - -Sometimes, the smaller structs are not good abstractions, and so we end up with -a worse design. That is probably a 'code smell', indicating that the program -should be refactored in some way. +It can lead to more verbose code. And sometimes, the smaller structs are not +good abstractions, and so we end up with a worse design. That is probably a +'code smell', indicating that the program should be refactored in some way. ## Discussion