This post shows how to use the Iterator design pattern in Rust without using the Iterator trait or any crate. Suppose we have a struct with multiple collections of various types that implement the same trait.
The Iterator Design Pattern Use Case
For this example, we have a use case to provide client codes with the names of prescribed and over-the-counter drugs in a list format. Moreover, we use the pallet concept to simulate an aggregate object with a complex internal data structure. Hypothetically, for our example, each Pallet can contain a list of prescribed and non-prescribed medicines. However, in reality, the content of any Pallet could be in a very complex structure or organization.
1 2 3 4 5 | #[derive(Debug)] struct Pallet { prescription_drug_list: Vec<PrescriptionDrug>, over_the_counter_drug_list: Vec<OverTheCounterDrug> } |
Instead of letting the client codes go through the internal collections, we provide them with a list of drug names.
Rust Traits And Other Structs
Whether the medicines are prescribed drugs or not, they all have names. Therefore, we can create a trait that their struct representatives can implement. In that way, we can retrieve their names.
1 2 3 | trait Traceable { fn get_name(&self) -> String; } |
Then, we create the individual structs for the drug types. Although they could contain more properties, we can stick with medicine names for simplicity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #[derive(Debug, Clone)] struct PrescriptionDrug { drug_name: String } #[derive(Debug, Clone)] struct OverTheCounterDrug { drug_name: String } impl Traceable for PrescriptionDrug { fn get_name(&self) -> String { self.drug_name.clone() } } impl Traceable for OverTheCounterDrug { fn get_name(&self) -> String { self.drug_name.clone() } } |
Next, we implement a function in the Pallet struct that returns a list of drug names. This Rust function provides access to a simplified version of the Pallet internal data as per the Iterator design pattern.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | impl Pallet { pub fn all_drugs_iterator(&self) -> Vec<String> { let mut all_drugs: Vec<String> = Vec::new(); for traceable_drug in &self.prescription_drug_list { all_drugs.push(traceable_drug.drug_name.clone()); } for traceable_drug in &self.over_the_counter_drug_list { all_drugs.push(traceable_drug.drug_name.clone()); } all_drugs } } |
Test Iterator Design Pattern Rust Codes
To test our example, consider the following Rust codes. First, the main function creates both lists of prescribed and over-the-counter medicines. Then, it makes a Pallet with those lists.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | fn main() { let presciption_drug_list_temp: Vec<PrescriptionDrug> = vec![ PrescriptionDrug{drug_name: "Abilify".to_owned()}, PrescriptionDrug{drug_name: "Aldara".to_owned()}, PrescriptionDrug{drug_name:"Avonex".to_owned()}]; let otc_drug_list_tmp: Vec<OverTheCounterDrug> = vec![ OverTheCounterDrug{drug_name:"Advil".to_owned()}, OverTheCounterDrug{drug_name:"Cepacol Antibacterial".to_owned()} ]; let pallet = Pallet{ prescription_drug_list: presciption_drug_list_temp, over_the_counter_drug_list: otc_drug_list_tmp }; println!("{:?}", pallet.all_drugs_iterator()); } |
Next, we retrieve a single list of all drug names without worrying about the complexity of the Pallet’s internal data. That is the essence of the Iterator design pattern!
When we run all the codes, we get the following output.
1 | ["Abilify", "Advil", "Aldara", "Avonex", "Cepacol Antibacterial"] |
In case we want to improve the content of the list, we only modify it in one place – the Pallet struct.