├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── images ├── mediator-mut-problem.png ├── mediator-rust-approach.jpg ├── problem.png └── solution.png ├── mediator-dynamic ├── Cargo.toml ├── README.md ├── main.rs ├── train_station.rs └── trains │ ├── freight_train.rs │ ├── mod.rs │ └── passenger_train.rs └── mediator-static-recommended ├── Cargo.toml ├── README.md ├── main.rs ├── train_station.rs └── trains ├── freight_train.rs ├── mod.rs └── passenger_train.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "mediator-dynamic", 5 | "mediator-static-recommended", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alexander Fadeev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _For other design patterns in Rust, see [https://github.com/fadeevab/design-patterns-rust](https://github.com/fadeevab/design-patterns-rust)._ 2 | 3 | # Mediator Pattern in Rust 4 | 5 | _*Mediator* is a challenging pattern to be implemented in *Rust*._ Here is **why** and **what ways** are there to implement Mediator in Rust. 6 | 7 | ## How to Run 8 | 9 | ```bash 10 | cargo run --bin mediator-dynamic 11 | cargo run --bin mediator-static-recommended 12 | ``` 13 | 14 | ## Execution Result 15 | 16 | Output of the `mediator-static-recommended`. 17 | 18 | ``` 19 | Passenger train Train 1: Arrived 20 | Freight train Train 2: Arrival blocked, waiting 21 | Passenger train Train 1: Leaving 22 | Freight train Train 2: Arrived 23 | Freight train Train 2: Leaving 24 | 'Train 3' is not on the station! 25 | ``` 26 | 27 | ## Problem 28 | 29 | A typical Mediator implementation in other languages is a classic anti-pattern in Rust: many objects hold mutable cross-references on each other, trying to mutate each other, which is a deadly sin in Rust - the compiler won't pass your first naive implementation unless it's oversimplified. 30 | 31 | By definition, [Mediator][1] restricts direct communications between the objects and forces them to collaborate only via a mediator object. It also stands for a Controller in the MVC pattern. Let's see the nice diagrams from https://refactoring.guru: 32 | 33 | | Problem | Solution | 34 | | ---------------------------- | ----------------------------- | 35 | | ![image](images/problem.png) | ![image](images/solution.png) | 36 | 37 | A common implementation in object-oriented languages looks like the following pseudo-code: 38 | 39 | ```java 40 | Controller controller = new Controller(); 41 | 42 | // Every component has a link to a mediator (controller). 43 | component1.setController(controller); 44 | component2.setController(controller); 45 | component3.setController(controller); 46 | 47 | // A mediator has a link to every object. 48 | controller.add(component1); 49 | controller.add(component2); 50 | controller.add(component2); 51 | ``` 52 | 53 | Now, let's read this in **Rust** terms: _"**mutable** structures have **mutable** references to a **shared mutable** object (mediator) which in turn has mutable references back to those mutable structures"_. 54 | 55 | Basically, you can start to imagine the unfair battle against the Rust compiler and its borrow checker. It seems like a solution introduces more problems: 56 | 57 | ![image](images/mediator-mut-problem.png) 58 | 59 | 1. Imagine that the control flow starts at point 1 (Checkbox) where the 1st **mutable** borrow happens. 60 | 2. The mediator (Dialog) interacts with another object at point 2 (TextField). 61 | 3. The TextField notifies the Dialog back about finishing a job and that leads to a **mutable** action at point 3... Bang! 62 | 63 | The second mutable borrow breaks the compilation with an error (the first borrow was on the point 1). 64 | 65 | **In Rust, a widespread Mediator implementation is mostly an anti-pattern.** 66 | 67 | ## Existing Primers 68 | 69 | You might see a reference Mediator examples in Rust like [this][5]: the example is too much synthetic - there are no mutable operations, at least at the level of trait methods. 70 | 71 | The [rust-unofficial/patterns](https://github.com/rust-unofficial/patterns) repository doesn't include a referenced Mediator pattern implementation as of now, see the [Issue #233][2]. 72 | 73 | **Nevertheless, we don't surrender.** 74 | 75 | ## Cross-Referencing with `Rc>` 76 | 77 | There is an example of a [Station Manager example in Go][4]. Trying to make it with Rust leads to mimicking a typical OOP through reference counting and borrow checking with mutability in runtime (which has quite unpredictable behavior in runtime with panics here and there). 78 | 79 | 👉 Here is a Rust implementation: [mediator-dynamic](https://github.com/fadeevab/mediator-pattern-rust/mediator-dynamic) 80 | 81 | 🏁 I would recommend this approach for applications that need **multi-threaded support**: particular components after being added to the mediator can be sent to different threads and modified from there. 82 | 83 | 📄 Real-world example: [`indicatif::MultiProgress`](https://docs.rs/indicatif/latest/indicatif/struct.MultiProgress.html) (mediates progress bars with support of being used in multiple threads). 84 | 85 | Key points: 86 | 87 | 1. All trait methods look like read-only (`&self`): immutable `self` and parameters. 88 | 2. `Rc`, `RefCell` are extensively used under the hood to take responsibility for the mutable borrowing from compilation time to runtime. Invalid implementation will lead to panic in runtime. 89 | 90 | 91 | ## Top-Down Ownership 92 | 93 | ☝ The key point is thinking in terms of OWNERSHIP. 94 | 95 | ![Ownership](images/mediator-rust-approach.jpg) 96 | 97 | 1. A mediator takes ownership of all components. 98 | 2. A component doesn't preserve a reference to a mediator. Instead, it gets the reference via a method call. 99 | ```rust 100 | // A train gets a mediator object by reference. 101 | pub trait Train { 102 | fn name(&self) -> &String; 103 | fn arrive(&mut self, mediator: &mut dyn Mediator); 104 | fn depart(&mut self, mediator: &mut dyn Mediator); 105 | } 106 | 107 | // Mediator has notification methods. 108 | pub trait Mediator { 109 | fn notify_about_arrival(&mut self, train_name: &str) -> bool; 110 | fn notify_about_departure(&mut self, train_name: &str); 111 | } 112 | ``` 113 | 3. Control flow starts from `fn main()` where the mediator receives external events/commands. 114 | 4. `Mediator` trait for the interaction between components (`notify_about_arrival`, `notify_about_departure`) is not the same as its external API for receiving external events (`accept`, `depart` commands from the main loop). 115 | ```rust 116 | let train1 = PassengerTrain::new("Train 1"); 117 | let train2 = FreightTrain::new("Train 2"); 118 | 119 | // Station has `accept` and `depart` methods, 120 | // but it also implements `Mediator`. 121 | let mut station = TrainStation::default(); 122 | 123 | // Station is taking ownership of the trains. 124 | station.accept(train1); 125 | station.accept(train2); 126 | 127 | // `train1` and `train2` have been moved inside, 128 | // but we can use train names to depart them. 129 | station.depart("Train 1"); 130 | station.depart("Train 2"); 131 | station.depart("Train 3"); 132 | ``` 133 | 134 | A few changes to the direct approach leads to a safe mutability being checked at compilation time. 135 | 136 | 👉 A Train Station primer **without** `Rc`, `RefCell` tricks, but **with** `&mut self` and compiler-time borrow checking: https://github.com/fadeevab/mediator-pattern-rust/mediator-static-recommended. 137 | 138 | 👉 A real-world example of such approach: [Cursive (TUI)][5]. 139 | 140 | [1]: https://refactoring.guru/design-patterns/mediator 141 | [2]: https://github.com/rust-unofficial/patterns/issues/233 142 | [3]: https://chercher.tech/rust/mediator-design-pattern-rust 143 | [4]: https://refactoring.guru/design-patterns/mediator/go/example 144 | [5]: https://crates.io/crates/cursive 145 | -------------------------------------------------------------------------------- /images/mediator-mut-problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fadeevab/mediator-pattern-rust/2d2b7faa952ac879e0d2654335158dbead638cb1/images/mediator-mut-problem.png -------------------------------------------------------------------------------- /images/mediator-rust-approach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fadeevab/mediator-pattern-rust/2d2b7faa952ac879e0d2654335158dbead638cb1/images/mediator-rust-approach.jpg -------------------------------------------------------------------------------- /images/problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fadeevab/mediator-pattern-rust/2d2b7faa952ac879e0d2654335158dbead638cb1/images/problem.png -------------------------------------------------------------------------------- /images/solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fadeevab/mediator-pattern-rust/2d2b7faa952ac879e0d2654335158dbead638cb1/images/solution.png -------------------------------------------------------------------------------- /mediator-dynamic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "mediator-dynamic" 4 | version = "0.1.0" 5 | 6 | [[bin]] 7 | name = "mediator-dynamic" 8 | path = "main.rs" 9 | -------------------------------------------------------------------------------- /mediator-dynamic/README.md: -------------------------------------------------------------------------------- 1 | # Mediator 2 | 3 | ## Mimicking a Typical OOP 4 | 5 | It implements a [Station Manager example in Go][4]. 6 | 7 | 🏁 I would recommend this approach for applications that need **multi-threaded support**: particular components after being added to the mediator can be sent to different threads and modified from there. 8 | 9 | 📄 Real-world example: [`indicatif::MultiProgress`](https://docs.rs/indicatif/latest/indicatif/struct.MultiProgress.html) (mediates progress bars with support of being used in multiple threads). 10 | 11 | Key points: 12 | 13 | 1. All trait methods look like read-only (`&self`): immutable `self` and parameters. 14 | 2. `Rc`, `RefCell` are extensively used under the hood to take responsibility for the mutable borrowing from compilation time to runtime. Invalid implementation will lead to panic in runtime. 15 | 16 | See the full article: [README.md](../README.md). 17 | 18 | [4]: https://refactoring.guru/design-patterns/mediator/go/example 19 | -------------------------------------------------------------------------------- /mediator-dynamic/main.rs: -------------------------------------------------------------------------------- 1 | mod train_station; 2 | mod trains; 3 | 4 | use std::{cell::RefCell, rc::Rc}; 5 | 6 | use train_station::StationManager; 7 | use trains::{FreightTrain, PassengerTrain, Train}; 8 | 9 | fn main() { 10 | let station = Rc::new(RefCell::new(StationManager::default())); 11 | 12 | let train1 = Rc::new(PassengerTrain::new("Train 1".into(), station.clone())); 13 | let train2 = Rc::new(FreightTrain::new("Train 2".into(), station.clone())); 14 | 15 | { 16 | let mut station = station.borrow_mut(); 17 | station.register(train1.clone()); 18 | station.register(train2.clone()); 19 | } 20 | 21 | train1.arrive(); 22 | train2.arrive(); 23 | train1.depart(); 24 | train2.depart(); 25 | } 26 | -------------------------------------------------------------------------------- /mediator-dynamic/train_station.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::{HashMap, VecDeque}, 4 | rc::Rc, 5 | }; 6 | 7 | use crate::trains::Train; 8 | 9 | pub trait Mediator { 10 | fn notify_about_arrival(&self, train: &dyn Train) -> bool; 11 | fn notify_about_departure(&self, train: &dyn Train); 12 | } 13 | 14 | #[derive(Default)] 15 | pub struct StationManager { 16 | trains: HashMap>, 17 | train_queue: RefCell>, 18 | train_on_platform: RefCell>, 19 | } 20 | 21 | impl StationManager { 22 | pub fn register(&mut self, train: Rc) { 23 | self.trains.insert(train.name().clone(), train); 24 | } 25 | } 26 | 27 | impl Mediator for StationManager { 28 | fn notify_about_arrival(&self, train: &dyn Train) -> bool { 29 | let train_name = train.name().clone(); 30 | 31 | self.trains.get(&train_name).expect("A train should exist"); 32 | 33 | if self.train_on_platform.borrow().is_some() { 34 | self.train_queue.borrow_mut().push_back(train_name.clone()); 35 | return false; 36 | } 37 | 38 | self.train_on_platform.replace(Some(train_name)); 39 | return true; 40 | } 41 | 42 | fn notify_about_departure(&self, train: &dyn Train) { 43 | if Some(train.name().clone()) != self.train_on_platform.replace(None) { 44 | return; 45 | } 46 | 47 | let next_train = self.train_queue.borrow_mut().pop_front(); 48 | 49 | if let Some(next_train_name) = next_train { 50 | let next_train = self.trains.get(&next_train_name).unwrap(); 51 | next_train.arrive(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mediator-dynamic/trains/freight_train.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use super::Train; 4 | use crate::train_station::Mediator; 5 | 6 | pub struct FreightTrain { 7 | name: String, 8 | mediator: Rc>, 9 | } 10 | 11 | impl FreightTrain { 12 | pub fn new(name: String, mediator: Rc>) -> Self { 13 | Self { name, mediator } 14 | } 15 | } 16 | 17 | impl Train for FreightTrain { 18 | fn name(&self) -> &String { 19 | &self.name 20 | } 21 | 22 | fn arrive(&self) { 23 | if !self.mediator.borrow().notify_about_arrival(self) { 24 | println!("Freight train {}: Arrival blocked, waiting", self.name); 25 | return; 26 | } 27 | 28 | println!("Freight train {}: Arrived", self.name); 29 | } 30 | 31 | fn depart(&self) { 32 | println!("Freight train {}: Leaving", self.name); 33 | self.mediator.borrow().notify_about_departure(self); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mediator-dynamic/trains/mod.rs: -------------------------------------------------------------------------------- 1 | mod freight_train; 2 | mod passenger_train; 3 | 4 | pub use freight_train::FreightTrain; 5 | pub use passenger_train::PassengerTrain; 6 | 7 | pub trait Train { 8 | fn name(&self) -> &String; 9 | fn arrive(&self); 10 | fn depart(&self); 11 | } 12 | -------------------------------------------------------------------------------- /mediator-dynamic/trains/passenger_train.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use super::Train; 4 | use crate::train_station::Mediator; 5 | 6 | pub struct PassengerTrain { 7 | name: String, 8 | mediator: Rc>, 9 | } 10 | 11 | impl PassengerTrain { 12 | pub fn new(name: String, mediator: Rc>) -> Self { 13 | Self { name, mediator } 14 | } 15 | } 16 | 17 | impl Train for PassengerTrain { 18 | fn name(&self) -> &String { 19 | &self.name 20 | } 21 | 22 | fn arrive(&self) { 23 | if !self.mediator.borrow().notify_about_arrival(self) { 24 | println!("Passenger train {}: Arrival blocked, waiting", self.name); 25 | return; 26 | } 27 | 28 | println!("Passenger train {}: Arrived", self.name); 29 | } 30 | 31 | fn depart(&self) { 32 | println!("Passenger train {}: Leaving", self.name); 33 | self.mediator.borrow().notify_about_departure(self); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mediator-static-recommended/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "mediator-static-recommended" 4 | version = "0.1.0" 5 | 6 | [[bin]] 7 | name = "mediator-static-recommended" 8 | path = "main.rs" 9 | -------------------------------------------------------------------------------- /mediator-static-recommended/README.md: -------------------------------------------------------------------------------- 1 | # Mediator Pattern 2 | 3 | ## Top-Down Ownership 4 | 5 | The key point is thinking in terms of OWNERSHIP. 6 | 7 | 1. A mediator takes ownership of all components. 8 | 2. A component doesn't preserve a reference to a mediator. Instead, it gets the reference via a method call. 9 | 3. Control flow starts from the `fn main()` where the mediator receives external events/commands. 10 | 4. Mediator trait for the interaction between components is not the same as its external API for receiving external events (commands from the main loop). 11 | 12 | See the full article: [README.md](../README.md). 13 | 14 | 👉 A real-world example of such approach: [Cursive (TUI)][5]. 15 | 16 | ![Ownership model in Rust](../images/mediator-rust-approach.jpg) 17 | 18 | [5]: https://crates.io/crates/cursive 19 | -------------------------------------------------------------------------------- /mediator-static-recommended/main.rs: -------------------------------------------------------------------------------- 1 | mod train_station; 2 | mod trains; 3 | 4 | use train_station::TrainStation; 5 | use trains::{FreightTrain, PassengerTrain}; 6 | 7 | fn main() { 8 | let train1 = PassengerTrain::new("Train 1"); 9 | let train2 = FreightTrain::new("Train 2"); 10 | 11 | // Station has `accept` and `depart` methods, 12 | // but it also implements `Mediator`. 13 | let mut station = TrainStation::default(); 14 | 15 | // Station is taking ownership of trains. 16 | station.accept(train1); 17 | station.accept(train2); 18 | 19 | // `train1` and `train2` have been moved inside, 20 | // but we can use train names to depart them. 21 | station.depart("Train 1"); 22 | station.depart("Train 2"); 23 | station.depart("Train 3"); 24 | } 25 | -------------------------------------------------------------------------------- /mediator-static-recommended/train_station.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, VecDeque}; 2 | 3 | use crate::trains::Train; 4 | 5 | // Mediator has notification methods. 6 | pub trait Mediator { 7 | fn notify_about_arrival(&mut self, train_name: &str) -> bool; 8 | fn notify_about_departure(&mut self, train_name: &str); 9 | } 10 | 11 | #[derive(Default)] 12 | pub struct TrainStation { 13 | trains: HashMap>, 14 | train_queue: VecDeque, 15 | train_on_platform: Option, 16 | } 17 | 18 | impl Mediator for TrainStation { 19 | fn notify_about_arrival(&mut self, train_name: &str) -> bool { 20 | if self.train_on_platform.is_some() { 21 | self.train_queue.push_back(train_name.into()); 22 | return false; 23 | } else { 24 | self.train_on_platform.replace(train_name.into()); 25 | return true; 26 | } 27 | } 28 | 29 | fn notify_about_departure(&mut self, train_name: &str) { 30 | if Some(train_name.into()) == self.train_on_platform { 31 | self.train_on_platform = None; 32 | 33 | if let Some(next_train_name) = self.train_queue.pop_front() { 34 | let mut next_train = self.trains.remove(&next_train_name).unwrap(); 35 | next_train.arrive(self); 36 | self.trains.insert(next_train_name.clone(), next_train); 37 | 38 | self.train_on_platform = Some(next_train_name); 39 | } 40 | } 41 | } 42 | } 43 | 44 | impl TrainStation { 45 | pub fn accept(&mut self, mut train: impl Train + 'static) { 46 | if self.trains.contains_key(train.name()) { 47 | println!("{} has already arrived", train.name()); 48 | return; 49 | } 50 | 51 | train.arrive(self); 52 | self.trains.insert(train.name().clone(), Box::new(train)); 53 | } 54 | 55 | pub fn depart(&mut self, name: &'static str) { 56 | let train = self.trains.remove(name.into()); 57 | if let Some(mut train) = train { 58 | train.depart(self); 59 | } else { 60 | println!("'{}' is not on the station!", name); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mediator-static-recommended/trains/freight_train.rs: -------------------------------------------------------------------------------- 1 | use super::Train; 2 | use crate::train_station::Mediator; 3 | 4 | pub struct FreightTrain { 5 | name: String, 6 | } 7 | 8 | impl FreightTrain { 9 | pub fn new(name: &'static str) -> Self { 10 | Self { name: name.into() } 11 | } 12 | } 13 | 14 | impl Train for FreightTrain { 15 | fn name(&self) -> &String { 16 | &self.name 17 | } 18 | 19 | fn arrive(&mut self, mediator: &mut dyn Mediator) { 20 | if !mediator.notify_about_arrival(&self.name) { 21 | println!("Freight train {}: Arrival blocked, waiting", self.name); 22 | return; 23 | } 24 | 25 | println!("Freight train {}: Arrived", self.name); 26 | } 27 | 28 | fn depart(&mut self, mediator: &mut dyn Mediator) { 29 | println!("Freight train {}: Leaving", self.name); 30 | mediator.notify_about_departure(&self.name); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mediator-static-recommended/trains/mod.rs: -------------------------------------------------------------------------------- 1 | mod freight_train; 2 | mod passenger_train; 3 | 4 | pub use freight_train::FreightTrain; 5 | pub use passenger_train::PassengerTrain; 6 | 7 | use crate::train_station::Mediator; 8 | 9 | // A train gets a mediator object by reference. 10 | pub trait Train { 11 | fn name(&self) -> &String; 12 | fn arrive(&mut self, mediator: &mut dyn Mediator); 13 | fn depart(&mut self, mediator: &mut dyn Mediator); 14 | } 15 | -------------------------------------------------------------------------------- /mediator-static-recommended/trains/passenger_train.rs: -------------------------------------------------------------------------------- 1 | use super::Train; 2 | use crate::train_station::Mediator; 3 | 4 | pub struct PassengerTrain { 5 | name: String, 6 | } 7 | 8 | impl PassengerTrain { 9 | pub fn new(name: &'static str) -> Self { 10 | Self { name: name.into() } 11 | } 12 | } 13 | 14 | impl Train for PassengerTrain { 15 | fn name(&self) -> &String { 16 | &self.name 17 | } 18 | 19 | fn arrive(&mut self, mediator: &mut dyn Mediator) { 20 | if !mediator.notify_about_arrival(&self.name) { 21 | println!("Passenger train {}: Arrival blocked, waiting", self.name); 22 | return; 23 | } 24 | 25 | println!("Passenger train {}: Arrived", self.name); 26 | } 27 | 28 | fn depart(&mut self, mediator: &mut dyn Mediator) { 29 | println!("Passenger train {}: Leaving", self.name); 30 | mediator.notify_about_departure(&self.name); 31 | } 32 | } 33 | --------------------------------------------------------------------------------