From 849d24a10b1b7034afd695b4013793aa7ef7407b Mon Sep 17 00:00:00 2001 From: darkicewolf50 Date: Mon, 7 Apr 2025 16:56:06 -0600 Subject: [PATCH] finished ch18.3 and ch18 --- .obsidian/workspace.json | 22 +++- Implementing OO Design Pattern.md | 164 ++++++++++++++++++++++++++++++ Pattern Matching.md | 1 + 3 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 Pattern Matching.md diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index a768c7d..b4fd9a2 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -35,6 +35,20 @@ "title": "Implementing OO Design Pattern" } }, + { + "id": "629d55df46f486d8", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "Pattern Matching.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "Pattern Matching" + } + }, { "id": "6ed9f182aa3d5f3e", "type": "leaf", @@ -46,7 +60,7 @@ } } ], - "currentTab": 1 + "currentTab": 2 } ], "direction": "vertical" @@ -189,10 +203,11 @@ "command-palette:Open command palette": false } }, - "active": "f9ef446f856cead7", + "active": "629d55df46f486d8", "lastOpenFiles": [ - "Trait Objects that allow for Values of Different Types.md", "Implementing OO Design Pattern.md", + "Pattern Matching.md", + "Trait Objects that allow for Values of Different Types.md", "Characteristics of OO Languages.md", "OOP Programming Features.md", "Futures, Tasks and Threads Together.md", @@ -216,7 +231,6 @@ "Improving The IO Project.md", "Tests.md", "The Preformance Closures and Iterators.md", - "minigrep/README.md", "minigrep/src/lib.rs", "does_not_compile.svg", "Untitled.canvas", diff --git a/Implementing OO Design Pattern.md b/Implementing OO Design Pattern.md index c884fc6..3474e91 100644 --- a/Implementing OO Design Pattern.md +++ b/Implementing OO Design Pattern.md @@ -538,3 +538,167 @@ By implementing the state pattern exactly as it is defined for object-oriented l Now lets look at some changes to make the `blog` crate that can make invalid states and transitions into compile time errors. ## Encoding States and Behavior as Types +We will show how you can rethink the state pattern to get a different set of trade-offs. + +Rather than encapsulating the states and transitions completely so outside code has no knowledge of them, we will encode the states into different types. + +Rust's type checking system will prevent attempts to use draft posts where only published posts are allowed by issuing a compiler error. + +Lets consider this first part of `main` from before +```rust +fn main() { + let mut post = Post::new(); + + post.add_text("I ate a salad for lunch today"); + assert_eq!("", post.content()); +} +``` +We still need to enable the creation of new posts in the draft state using `Post::new` and the ability to add text to the post's content. + +Instead of having a `content` method on a draft post that returns an empty string, we will make it so draft posts don't have the `content` method at all. + +This way if we try to get a draft post's content, we will get a compiler error telling us the method doesn't exist. + +This results in being impossible for us to accidentally display draft post content in production, because that code won't even compile. + +Here is the definition of a `Post` struct and a `DraftPost` struct as well as methods on each. +```rust +pub struct Post { + content: String, +} + +pub struct DraftPost { + content: String, +} + +impl Post { + pub fn new() -> DraftPost { + DraftPost { + content: String::new(), + } + } + + pub fn content(&self) -> &str { + &self.content + } +} + +impl DraftPost { + pub fn add_text(&mut self, text: &str) { + self.content.push_str(text); + } +} +``` +Both the `Post` and `DraftPost` structs have a private `content` field that stores the blog post text. + +The structs no longer have the `state` field because we are moving the encoding of that state to the types of structs. + +The `Post` struct will represent a published post, and it has a `content` method that returns the `content`. + +We still have a `Post::new` function, but instead of returning an instance of `Post`, it returns an instance of `DraftPost`. + +Due to `content` being private and there aren't any functions that return `Post`, it is not possible to create an instance of `Post` right now. + +The `DraftPost` struct has an `add_text`method, so we can add text to `content` as before. + +Note that `DraftPost` does not have a `content` method defined. + +So now the program ensures all posts start as draft posts, and draft posts don't have their content available for display. + + +Any attempt to get around these constraints will result in a compiler error. + +## Implementing Transitions as Transformations into Different Types +How do we get a published post? + +We want to enforce the rule that a draft post has to be reviewed and approved before it can be published. + +A post in the pending review state should still not display any content. + +We will implement these constraints by adding another struct, `PendingReviewPost`. + +We will define the `request_review` method on `DraftPost` to return a `PendingReviewPost`. + +Finally we will define an `approve` method on `PendingReviewPost` to return a `Post`. + +Here is the code implementation. +```rust +impl DraftPost { + // --snip-- + pub fn request_review(self) -> PendingReviewPost { + PendingReviewPost { + content: self.content, + } + } +} + +pub struct PendingReviewPost { + content: String, +} + +impl PendingReviewPost { + pub fn approve(self) -> Post { + Post { + content: self.content, + } + } +} +``` +Here the `request_review` and `approve` methods take ownership of `self`. + +This thus consumes the `DraftPost` and `PendingReviewPost` instances and transforming them into a `PendingReviewPost` and a published `Post`. + +This way we will not have any lingering `DraftPost` instances after we called `request_review` on them and so on. + +The `PendingReviewPost` struct also doesn't have a `content` method defined on it. + +Again attempting to read its content results in a compiler error. + +Because the only way to get a published `Post` instance that does have a `content` method defined is to call the `approve` method on a `PendingReviewPost`, and the only way to get a `PendingReviewPost` is to call the `request_review` method on a `DraftPost`. + +Now we have encoded the blog post workflow into the type system. + +We also have to make some changes to `main`. + +The `reequest_review` and `approve`methods return new instances rather than modifying the struct they are called on. + +We need to add more `let post =` shadowing assignments to save the returned instances. + +We also can't have assertions about the draft and pending review posts' contents being empty strings, nor do we need them. + +We are unable to compile any code that tires to use the content of posts in those states any longer. + +Here is the updated code in `main` +```rust +use blog::Post; + +fn main() { + let mut post = Post::new(); + + post.add_text("I ate a salad for lunch today"); + + let post = post.request_review(); + + let post = post.approve(); + + assert_eq!("I ate a salad for lunch today", post.content()); +} +``` +The changes we need to make to `main` to reassign `post` mean that this implementation doesn't quite follow the object oriented state pattern anymore. + +The transformations between the states are no longer encapsulated entirely within the `Post` implementation. + +However our gain is that invalid states are now impossible because of the type system and the type checking that happens at compile time. + +This enforces that certain bugs, such as display of the content of an unpublished post, will be discovered before they make it production. + + +Try the tasks suggested before on the `blog` crate as it is after to see what you think about the design of this version of the code. + +Note that some of the tasks might be completed already in this design. + +We have seen that even though Rust is capable of implementing object-oriented design patterns, other patterns such as encoding state into the type system, are also available in Rust. + +These patterns have different trade0ffs. + +While you may be very familiar with object-oriented patterns, rethinking the problem to take advantage of Rust's features can provide benefits, such as preventing some bugs due to certain features, like ownership, that object-oriented languages don't have. \ No newline at end of file diff --git a/Pattern Matching.md b/Pattern Matching.md new file mode 100644 index 0000000..50821a3 --- /dev/null +++ b/Pattern Matching.md @@ -0,0 +1 @@ +# Patterns and Matching