diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 68ed441..a768c7d 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -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", diff --git a/Implementing OO Design Pattern.md b/Implementing OO Design Pattern.md new file mode 100644 index 0000000..acf0cf9 --- /dev/null +++ b/Implementing OO Design Pattern.md @@ -0,0 +1 @@ +# Implementing an Object-Oriented Design Pattern diff --git a/Trait Objects that allow for Values of Different Types.md b/Trait Objects that allow for Values of Different Types.md index 6d458b9..50f2829 100644 --- a/Trait Objects that allow for Values of Different Types.md +++ b/Trait Objects that allow for Values of Different Types.md @@ -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` 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`, 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>, +} +``` +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 { + pub components: Vec, +} + +impl Screen +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` that contains a `Box