mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-08-01 07:40:54 -06:00
Compare commits
2 Commits
cb2ac6ab69
...
7118958a77
Author | SHA1 | Date | |
---|---|---|---|
7118958a77 | |||
559d4bcdef |
160
.obsidian/workspace.json
vendored
160
.obsidian/workspace.json
vendored
@ -7,172 +7,32 @@
|
||||
"id": "64904eb93f53e8e0",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "caf0233e624d6c1c",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Test Controls.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Test Controls"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "53b36d00b704136e",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Concurrency.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Concurrency"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "42d65c7d3f15198d",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Async, Await, Futures and Streams.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Async, Await, Futures and Streams"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6142f6650517896f",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Any Number of Futures.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Any Number of Futures"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8d868fd701da33a8",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Futures in Sequence.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Futures in Sequence"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ee4116419493acd3",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Traits for Async.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Traits for Async"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c00c13dd25b12ad4",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Futures, Tasks and Threads Together.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Futures, Tasks and Threads Together"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "e8a505fdeccc0275",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "OOP Programming Features.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "OOP Programming Features"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b49e674e0ebaaeb7",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Characteristics of OO Languages.md",
|
||||
"file": "Trait Objects that allow for Values of Different Types.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Characteristics of OO Languages"
|
||||
"title": "Trait Objects that allow for Values of Different Types"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2a974ca5442d705f",
|
||||
"id": "f9ef446f856cead7",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Sync and Send.md",
|
||||
"file": "Implementing OO Design Pattern.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Sync and Send"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3d0ca0b1691c4c2f",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Shared State Concurrency.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Shared State Concurrency"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b104e4647c0ac328",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Leaky Reference Cycles.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Leaky Reference Cycles"
|
||||
"title": "Implementing OO Design Pattern"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -186,7 +46,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"currentTab": 8
|
||||
"currentTab": 1
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
@ -329,10 +189,12 @@
|
||||
"command-palette:Open command palette": false
|
||||
}
|
||||
},
|
||||
"active": "b49e674e0ebaaeb7",
|
||||
"active": "f9ef446f856cead7",
|
||||
"lastOpenFiles": [
|
||||
"OOP Programming Features.md",
|
||||
"Trait Objects that allow for Values of Different Types.md",
|
||||
"Implementing OO Design Pattern.md",
|
||||
"Characteristics of OO Languages.md",
|
||||
"OOP Programming Features.md",
|
||||
"Futures, Tasks and Threads Together.md",
|
||||
"Traits for Async.md",
|
||||
"Futures in Sequence.md",
|
||||
@ -355,8 +217,6 @@
|
||||
"Tests.md",
|
||||
"The Preformance Closures and Iterators.md",
|
||||
"minigrep/README.md",
|
||||
"Project Organization.md",
|
||||
"Writing_Tests.md",
|
||||
"minigrep/src/lib.rs",
|
||||
"does_not_compile.svg",
|
||||
"Untitled.canvas",
|
||||
|
241
Implementing OO Design Pattern.md
Normal file
241
Implementing OO Design Pattern.md
Normal file
@ -0,0 +1,241 @@
|
||||
# Implementing an Object-Oriented Design Pattern
|
||||
The *state pattern* is an object-oriented design pattern.
|
||||
|
||||
The crux of the pattern is that we define a set of states a value can have internally.
|
||||
|
||||
The states are represented by a set of *state objects* and the value's behavior changes based on its state.
|
||||
|
||||
We are going to work through an example of a blog post struct that has aa filed to hold its state, this will be a state object form the set "draft", "review", or "published".
|
||||
|
||||
The sate objects share functionality. In Rust we use structs and traits rather than objects and inheritance.
|
||||
|
||||
Each state object is responsible for its own behavior and for governing when it should change into another state.
|
||||
|
||||
The value that holds a state object knows nothing about the different behavior of the states or when to transition between states.
|
||||
|
||||
|
||||
The advantage of using this is that when the business requirements of the program change, we won't need to change the code of the value holding the state or the code that uses the value.
|
||||
|
||||
We will only need to update the code inside one of the state objects to change its rules or perhaps add more state objects.
|
||||
|
||||
We are going to implement the state pattern in a more tradition object-oriented way.
|
||||
|
||||
Next we will use an approach that is a bit more natural in Rust.
|
||||
|
||||
Lets start with incrementally implementing a blog post workflow using the state pattern.
|
||||
|
||||
The final functionality will look like this:
|
||||
1. A blog post starts as an empty draft
|
||||
2. When the draft is complete, a review of the post is requested
|
||||
3. When the post is approved, it will be published
|
||||
4. Only published blog posts return content to print, so unapproved posts can't be accidentally be published
|
||||
Any other changes attempted on a post should have no effect.
|
||||
|
||||
An example of this is if we try to approve a draft blog post before we have requested a review, the post should remain an unpublished draft.
|
||||
|
||||
In this example, it shows the workflow in code form.
|
||||
|
||||
This is an example usage of the API, we will implement in a library crate named `blog`.
|
||||
|
||||
This will not compile yet because we haven't implemented the `blog` crate.
|
||||
|
||||
```rust
|
||||
use blog::Post;
|
||||
|
||||
fn main() {
|
||||
let mut post = Post::new();
|
||||
|
||||
post.add_text("I ate a salad for lunch today");
|
||||
assert_eq!("", post.content());
|
||||
|
||||
post.request_review();
|
||||
assert_eq!("", post.content());
|
||||
|
||||
post.approve();
|
||||
assert_eq!("I ate a salad for lunch today", post.content());
|
||||
}
|
||||
```
|
||||
We want to allow the user to create a new draft blog post with `Post::new`.
|
||||
|
||||
We also want to allow text to be added to be blog post.
|
||||
|
||||
If we try to get the post's content immediately, before approval, we shouldn't get any text because the post is still a draft.
|
||||
|
||||
We added `assert_eq!` for demonstration purposes.
|
||||
|
||||
An excellent unit test for this would be to assert that a draft post returns an empty string from the `content` method, but we are not going to write tests for this example.
|
||||
|
||||
Next, we want to enable a request for a review of the post and we want `content` to return an empty string while waiting for the review.
|
||||
|
||||
Then when the post receives approval, it should get published, meaning the text of the post will be returned when `content` is called.
|
||||
|
||||
Note that the only type we are interacting with from the crate s the Post type.
|
||||
|
||||
This type will use the state pattern and will hold a value that will be one of three state objects representing the various states a post can be in, draft, waiting for review or published.
|
||||
|
||||
Changing from one state change in response to the methods called by our library users on the `Post` instance.
|
||||
|
||||
They don't have to manage the state changes directly.
|
||||
|
||||
Users can't make a mistake with the states, like publishing a post before it is reviewed.
|
||||
|
||||
## Defining `Post` and Creating a New Instance in the Draft State
|
||||
First we need a public `Post` struct that holds some content.
|
||||
|
||||
so we will start with the definition of the struct and the associated public `new` function to create an instance of `Post`.
|
||||
|
||||
This is shown below.
|
||||
|
||||
We will also make a private `State` trait that will define the behavior that all state objects for a `Post` must have.
|
||||
|
||||
`Post` will hold a trait object of `Box<dyn State>` inside an `Option<T>` in a private field named `state` to hold the state object.
|
||||
|
||||
You will see why the `Option<T>` is necessary.
|
||||
```rust
|
||||
pub struct Post {
|
||||
state: Option<Box<dyn State>>,
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl Post {
|
||||
pub fn new() -> Post {
|
||||
Post {
|
||||
state: Some(Box::new(Draft {})),
|
||||
content: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait State {}
|
||||
|
||||
struct Draft {}
|
||||
|
||||
impl State for Draft {}
|
||||
```
|
||||
|
||||
The `State` trait defines the behavior shared by different post states.
|
||||
|
||||
The state objects are `Draft`, `PendingReview` and `Published`, and they will all implement the `State` trait.
|
||||
|
||||
For now the trait doesn't have any methods.
|
||||
|
||||
We will start by defining just the `Draft` state because that is the state we want a post to start in.
|
||||
|
||||
When we create a new `Post`, we set its `state` field to a `Some` value that holds a `Box`.
|
||||
|
||||
This `Box` points to aa new instance of the `Draft` struct.
|
||||
|
||||
This ensures whenever we create a new instance of `Post`, it will start out as a draft.
|
||||
|
||||
Due to the state field of `Post` being private, there is no way to create a `Psot` in any other state.
|
||||
|
||||
In the `Post::new` function, we set the `content` field to a new empty `String`.
|
||||
|
||||
## Storing the Text of the Post Content
|
||||
Previously we saw that we wanted to be able to call a method named `add_test` and pass it a `&str` that is then added as the text content of the blog post.
|
||||
|
||||
We implemented this as a method, rather than exposing the `content` field as `pub`, so that later we can implement a method that will control how the `content` field's data is read.
|
||||
|
||||
The `add_text` method is fairly straightforward, so lets add the implementation below to the `impl Post` block.
|
||||
```rust
|
||||
impl Post {
|
||||
// --snip--
|
||||
pub fn add_text(&mut self, text: &str) {
|
||||
self.content.push_str(text);
|
||||
}
|
||||
}
|
||||
```
|
||||
The `add_text` method takes a mutable reference to `self`.
|
||||
|
||||
Because we changed the `Post` instance that we are calling `add_text` on.
|
||||
|
||||
We then can call `push_str` on the `String` in `content` and pass the `text` argument to add to the saved `content`.
|
||||
|
||||
This behavior doesn't depend on the state the post is in, so it is not part of the state pattern.
|
||||
|
||||
The `add_text` method doesn't interact with the `state` field at all, but it is part of the behavior we want to support.
|
||||
|
||||
## Ensuring the Content of a Draft Post Is Empty
|
||||
Even after we called `add_text` and added some content to our post, we still want the `content` method to return an empty string slice because the post is still in the draft state.
|
||||
|
||||
For now we will implement the `content` method with the simplest thing that will fulfill this requirement.
|
||||
|
||||
Always returning an empty string slice.
|
||||
|
||||
We will change this late once we implement the ability to change a post's state so that it can be published.
|
||||
|
||||
Posts so far can only be in the draft state., so the post content should always be empty.
|
||||
|
||||
Here is a placeholder implementation:
|
||||
```rust
|
||||
impl Post {
|
||||
// --snip--
|
||||
pub fn content(&self) -> &str {
|
||||
""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Requesting a Review of the Post Changes Its State
|
||||
Next we need to add functionality to request a review of a post, which should change its state from `Draft` to `PendingReview`
|
||||
|
||||
Here is the code that shows this
|
||||
```rust
|
||||
impl Post {
|
||||
// --snip--
|
||||
pub fn request_review(&mut self) {
|
||||
if let Some(s) = self.state.take() {
|
||||
self.state = Some(s.request_review())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait State {
|
||||
fn request_review(self: Box<Self>) -> Box<dyn State>;
|
||||
}
|
||||
|
||||
struct Draft {}
|
||||
|
||||
impl State for Draft {
|
||||
fn request_review(self: Box<Self>) -> Box<dyn State> {
|
||||
Box::new(PendingReview {})
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingReview {}
|
||||
|
||||
impl State for PendingReview {
|
||||
fn request_review(self: Box<Self>) -> Box<dyn State> {
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
Here `request_review` is a public method on the `Post` struct, this will take a mutable reference to `self`.
|
||||
|
||||
Then we call an internal `request_review` method on the current state of `Post`.
|
||||
|
||||
The second `request_review` method consumes the current state and returns a new state.
|
||||
|
||||
We add the `request_review` method to the `State` trait.
|
||||
|
||||
Now all types that implement the trait will now need to implement the `request_review` method.
|
||||
|
||||
Note, rather than having `self`, `&self` or `&mut self` as the first parameter of the method, we have `self: Box<Self>`.
|
||||
|
||||
This syntax means the method is only valid when called on a `Box` holding the type.
|
||||
|
||||
This syntax takes ownership of `Box<Self>`, invalidating the old state so the state value of the `Post` can transform into a new state.
|
||||
|
||||
In order to consume the old state, the `request_review` method needs to take ownership of the state value.
|
||||
|
||||
This is where the `Option` in the `state` filed of `Post` comes in.
|
||||
|
||||
We call the `take` method to take the `Some` value out of the `state` field and leave a `None` in its place, because Rust not allowing us to have unpopulated fields in structs.
|
||||
|
||||
This lets us move the `state` value out of `Post` rather than borrowing it.
|
||||
|
||||
Then we will set the post's `state` value to the result of this operation.
|
||||
|
||||
We need to set `state` to `None` temporarily rather than setting it directly with something like `self.state = self.state.request_review();` to get ownership of the `state` value.
|
||||
|
||||
This ensures that `Post` can't use the old `state` value after we transformed it into a new state.
|
@ -1 +1,254 @@
|
||||
# Using Trait Objects That Allow for Values of Different Types
|
||||
In ch8 we discussed one of the limitation of vectors is that they can store elements of only one type
|
||||
|
||||
We created a workaround where we defined a `SpreadsheetCell` enum that had variants to hold integers, floats and text.
|
||||
|
||||
This meant that we could store different types of data in each cell and still have a vector that represented a row of cells.
|
||||
|
||||
This is a good solution when our interchangeable items are a fixed set of types that we know when our code is compiled.
|
||||
|
||||
However, sometimes we want our library user to be able to extend the set of types that are valid in a particular situation.
|
||||
|
||||
To show how we may achieve this, we will create a example graphical user interface (GUI) tool that iterates through a list of items, calling a `draw` method on each one to draw it to the screen.
|
||||
|
||||
This is a common technique for GUI tools.
|
||||
|
||||
We will start with creating a library crate called `gui` that contains the structure of a GUI library.
|
||||
|
||||
This crate might include some types for people to use, such as `Button` or `TextField`.
|
||||
|
||||
In addition `gui` users will want to create their own types that can be drawn.
|
||||
|
||||
For example, one programmer might add an `Image` and another programmer might add a `SelectBox`
|
||||
|
||||
In this example we won't build a fully fledged GUI library, but instead will show how all of the pieces would fit together.
|
||||
|
||||
At the time of writing the library, we cant know and define all the types other programmers might want to create.
|
||||
|
||||
We do know that `gui` needs to keep track of many values of different and it needs to call a `draw` method on each of these differently typed values.
|
||||
|
||||
It doesn't need to know exactly what will happen when we call the `draw` method, just that the value will have that method available for us to call.
|
||||
|
||||
In a language to do this, we might define a class named `Component` that has a method named `draw` on it.
|
||||
|
||||
The other classes, such as `Button`, `Image` and `SelectBox`, would inherit form `Component` and thus inherit the `draw` method.
|
||||
|
||||
|
||||
Each one would override the `draw` method to define their custom behavior, but the framework could treat all of the types as if they were `Component` instances and call `draw` on them.
|
||||
|
||||
Due to Rust not having inheritance, we need a different way to structure the `gui` library to allow users to extend it with new types.
|
||||
|
||||
## Defining a Trait for Common Behavior
|
||||
In order to implement the behavior we want `gui` to have, we will define a trait named `Draw` that will have one method named `draw`.
|
||||
|
||||
We then can define a vector that takes a *trait object*.
|
||||
|
||||
A trait object points to both an instance of a type implementing our specified trait and a table used to look up trait methods on that type at runtime.
|
||||
|
||||
We create a trait object by specifying some sort of pointer, such as a `&` reference or a `Box<T>` smart pointer, then the `dyn` keyword and then specifying the relevant trait.
|
||||
|
||||
We will discuss about the reason trait objects must use a pointer in Ch20.
|
||||
|
||||
We can use trait objects in place of a generic or concrete type.
|
||||
|
||||
Whenever we use a trait object, Rust's type system will ensure at compile time that any value used in that context will implement the trait object's trait.
|
||||
|
||||
Due to this we don't need to know all possible types at compile time.
|
||||
|
||||
We mentioned that in Rust, we refrain from calling structs an enums "objects" to distinguish them from other language's objects.
|
||||
|
||||
In a struct or enum, the data in the struct fields and the behavior in `impl` blocks are separated, where as in other languages, the data and behavior combined into one concept is often labeled an object.
|
||||
|
||||
Trait objects *are* more like objects in other languages in the sense that they combine data and behavior.
|
||||
|
||||
Trait objects still differ form traditional objects in that we can't add data to a trait object.
|
||||
|
||||
Trait objects aren't as generally useful as objects in other languages: their purpose is to allow abstraction across common behavior.
|
||||
|
||||
Here is an example of how to define a trait named `Draw` with one method named `draw`
|
||||
```rust
|
||||
pub trait Draw {
|
||||
fn draw(&self);
|
||||
}
|
||||
```
|
||||
This is similar syntax to how we defined traits in Ch10.
|
||||
|
||||
Next comes some new syntax.
|
||||
|
||||
Here we define a struct named `Screen` that holds a vector named `components`.
|
||||
|
||||
This vector is of type `Box<dyn Draw>`, which is a trait object; it is a stand in for any type inside a `Box` that implements the `Draw` trait.
|
||||
```rust
|
||||
pub struct Screen {
|
||||
pub components: Vec<Box<dyn Draw>>,
|
||||
}
|
||||
```
|
||||
Now on the `Screen` struct we will define a method named `run` that will call the `draw` method on each of its `components`
|
||||
```rust
|
||||
impl Screen {
|
||||
pub fn run(&self) {
|
||||
for component in self.components.iter() {
|
||||
component.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
This works differently form defining a struct that uses a generic type parameter with trait bounds.
|
||||
|
||||
A generic type parameter can only be substituted with one concrete type at a time, whereas trait objects allow for multiple concrete types to fill in for the trait object at runtime.
|
||||
|
||||
For example we could have defined the `Screen` struct using generic type and a trait bound, this is shown here.
|
||||
```rust
|
||||
pub struct Screen<T: Draw> {
|
||||
pub components: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Screen<T>
|
||||
where
|
||||
T: Draw,
|
||||
{
|
||||
pub fn run(&self) {
|
||||
for component in self.components.iter() {
|
||||
component.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
This restricts us to a `Screen` instance that has a list components all of type `Button` or all of type `TextField`.
|
||||
|
||||
If you only ever have homogeneous collections, using generics and trait bounds is preferable because the definitions will be monomerized at compile time to use the concrete types.
|
||||
|
||||
With the method using trait objects, one `Screen` instance can hold a `Vec<T>` that contains a `Box<Button>` as well as a `Box<TextField>`.
|
||||
Now lets look at how that works and then we will talk about the runtime performance implications.
|
||||
|
||||
## Implementing the Trait
|
||||
Next we will add some types that implement the `Draw` trait.
|
||||
|
||||
We will provide the `Button` type.
|
||||
|
||||
So the `draw` method won't have any useful implementation in its body.
|
||||
|
||||
To imagine what the implementation might look like, a `Button` struct might have fields for `width`, `height` and `label`.
|
||||
|
||||
Here is an example of this
|
||||
```rust
|
||||
pub struct Button {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
impl Draw for Button {
|
||||
fn draw(&self) {
|
||||
// code to actually draw a button
|
||||
}
|
||||
}
|
||||
```
|
||||
The `width`, `height` and `label` fields on `Button` will differ from the fields on other components.
|
||||
|
||||
For example a `TextField` type might have those same fields plus a `placeholder` field.
|
||||
|
||||
Each of the types we want to draw on the screen will implement the `Draw` trait, but will use different code in the `draw` method to define how to draw that particular type as `Button` has here (without any actual GUI code).
|
||||
|
||||
The `Button` type, for instance, might have an additional `impl` block containing method related to what happens when a user clicks the button.
|
||||
|
||||
These kinds of methods related to what happens when a user clicks the button.
|
||||
|
||||
These kinds of methods won't apply to types like `TextField`.
|
||||
|
||||
If someone using our library decides to implement a `SelectBox` struct that has `width`, `height`, and `options` fields, they implement the `Draw` trait on the `SelectBox` type as well.
|
||||
|
||||
This is shown below
|
||||
```rust
|
||||
use gui::Draw;
|
||||
|
||||
struct SelectBox {
|
||||
width: u32,
|
||||
height: u32,
|
||||
options: Vec<String>,
|
||||
}
|
||||
|
||||
impl Draw for SelectBox {
|
||||
fn draw(&self) {
|
||||
// code to actually draw a select box
|
||||
}
|
||||
}
|
||||
```
|
||||
When we originally wrote the library, we didn't know that someone might add the `SelectBox` type, but our `Screen` implementation was able to operate on the new type and draw it because `SelectBox` implements the `Draw` trait.
|
||||
|
||||
This means that it implements the `draw` method.
|
||||
|
||||
This concept of being concerned only with the messages a value responds to rather than the value's concrete type.
|
||||
|
||||
This is similar to the concept of *duck typing* in dynamically typed languages.
|
||||
|
||||
If it walks like a duck and quacks like a duck, then it must be a duck!
|
||||
|
||||
In the implementation of `Run` on `Screen`, `run` doesn't need to know what the concrete type of each component is.
|
||||
|
||||
It doesn't check whether a component is an instance of a `Button` or a `SelectBox`, it just calls the `draw` method on the component.
|
||||
|
||||
|
||||
By specifying `Box<dyn Draw>` as the type of the values in the `componets` vector.
|
||||
|
||||
Here we have defined `Screen` to need values that we can call the `draw` method on.
|
||||
|
||||
The advantage of using trait objects and Rust's type system to write code similar to code using duck typing is that we never have to check whether a value implements a particular method at runtime or worry about getting errors if a value doesn't implement a method but we call it anyway.
|
||||
|
||||
Rust will not compile the code if the values don't implement the traits that the trait objects need.
|
||||
|
||||
Here is an example of this shows what happens if we try to create a `Screen` with a `String` as a component
|
||||
```rust
|
||||
use gui::Screen;
|
||||
|
||||
fn main() {
|
||||
let screen = Screen {
|
||||
components: vec![Box::new(String::from("Hi"))],
|
||||
};
|
||||
|
||||
screen.run();
|
||||
}
|
||||
```
|
||||
Here we will get this error because `String` doesn't implement the `Draw` trait
|
||||
```
|
||||
$ cargo run
|
||||
Compiling gui v0.1.0 (file:///projects/gui)
|
||||
error[E0277]: the trait bound `String: Draw` is not satisfied
|
||||
--> src/main.rs:5:26
|
||||
|
|
||||
5 | components: vec![Box::new(String::from("Hi"))],
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not implemented for `String`
|
||||
|
|
||||
= help: the trait `Draw` is implemented for `Button`
|
||||
= note: required for the cast from `Box<String>` to `Box<dyn Draw>`
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
error: could not compile `gui` (bin "gui") due to 1 previous error
|
||||
```
|
||||
This error let us know that either we are passing something to `Screen` we didn't mean to pass and so should pass a different type or we should implement `Draw` on `String` so that `Screen` is able to call `draw` on it.
|
||||
|
||||
## Trait Objects Preform Dynamic Dispatch
|
||||
Recall the ["Performance of Code Using Generics"](./Generics.md#perfomance-of-code-using-generics) our discussion on the monomorphization process performed on generics by the compiler.
|
||||
|
||||
The compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter.
|
||||
|
||||
The code that results from monomorphization is doing *static dispatch*, which is when the compiler know what method you are calling at compile time.
|
||||
|
||||
This is the opposite to *dynamic dispatch*, which is when the compiler can't tell at compile time which method you are calling.
|
||||
|
||||
In dynamic dispatch cases, the compiler emits code that at runtime will figure out which method to call.
|
||||
|
||||
When we use trait objects, Rust must use dynamic dispatch.
|
||||
|
||||
The compiler won't know all the types that might be used with the code that is using trait objects, so it doesn't know all the types that might be used with the code that is using trait objects, so it doesn't know which method implemented on which type to call.
|
||||
|
||||
Instead at runtime, Rust uses the pointers inside the trait object to know which method to call.
|
||||
|
||||
This lookup incurs a runtime cost that doesn't occur with static dispatch.
|
||||
|
||||
Dynamic dispatch also prevents the compiler from choosing to inline a method's code, which prevents some optimizations, and Rust has some rules about where you can and cannot use dynamic dispatch.
|
||||
|
||||
|
||||
This is called [dyn compatibility](https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility).
|
||||
|
||||
However you will get the extra flexibility in the code that we wrote before and we were able to support in the following examples, so it is a trade-off to consider.
|
Reference in New Issue
Block a user