30 days of Rust - Day Nine - Generics
If you have been a programmer for some time, chances are you have heard of the DRY principle, which means Don't Repeat Yourself. This simply means you should not duplicate your code. Try by all means to make it more re-usable.
Let's see how Rust's generics allow us to write DRY code.
Day 9 - Generics
In order to appreciate generics, we first need to write duplicate code π΄. Let's say your boss asks you to sort a list of integers. Then you get straight to writing a bubble sort algorithm. For the Computer Science geeks, before you burst my bubble π«§, we're only using bubble sort for demonstration purposes. Even Barack Obama knows that bubble sort is the worst way to go about it, watch the video below;
So let's get cracking.
fn main() {
// sort from smallest to biggest
let mut numbers = [22, 87, -1, 2022, 55, 33, 100, 98];
println!("Unsorted List: {:?}", numbers);
for i in 0..numbers.len() {
for j in 0..numbers.len() - 1 - i {
if numbers[j] > numbers[j + 1] {
numbers.swap(j, j + 1);
}
}
}
println!("Sorted List: {:?}", numbers);
}
The output of the code above is;
Unsorted List: [22, 87, -1, 2022, 55, 33, 100, 98]
Sorted List: [-1, 22, 33, 55, 87, 98, 100, 2022]
Great, this works perfectly. Now, your boss realizes that you're a genius and asks you to do the same for sorting a list of client names Alphabetically. You realize that effectively, the problem is the same and you just copy and paste the above code and make a few changes. Your code then looks like this;
fn main() {
println!("Sort numbers ascending");
let mut numbers = [22, 87, -1, 2022, 55, 33, 100, 98];
println!("Unsorted List: {:?}", numbers);
for i in 0..numbers.len() {
for j in 0..numbers.len() - 1 - i {
if numbers[j] > numbers[j + 1] {
numbers.swap(j, j + 1);
}
}
}
println!("Sorted List: {:?}", numbers);
// Sorting clients alphabetically
let mut clients = ["Thato", "Desiree", "Matome", "Karabo", "Kwena", "Piet", "Xolani"];
println!("Unsorted Clients: {:?}", clients);
for i in 0..clients.len() {
for j in 0..clients.len() - 1 - i {
if clients[j] > clients[j + 1] {
clients.swap(j, j + 1);
}
}
}
println!("Sorted Clients: {:?}", clients);
}
The output of the code above is;
Unsorted List: [22, 87, -1, 2022, 55, 33, 100, 98]
Sorted List: [-1, 22, 33, 55, 87, 98, 100, 2022]
Unsorted Clients: ["Thato", "Desiree", "Matome", "Karabo", "Kwena", "Piet"]
Sorted Clients: ["Desiree", "Karabo", "Kwena", "Matome", "Piet", "Thato"]
This is amazing πΊπΌ, you'll probably even get a pay raise too. The only problem is that, the above code is a repetition of the exact same thing, the only difference is in one case we use a list of numbers and a list of Strings in the other. We are still not having DRY code.
Creating a Generic function
In order to create a function, we need to tell Rust what data types we are expecting as parameters. The convention in Rust is to use the type name T
as a generic, which is a placeholder for ANY TYPE.
fn bubble_sort<T>(list: &mut [T]) {}
The above line is read as; the function bubble_sort
is a generic over some type T
. The function has only one parameter named list
, which is a mutable slice of type T.
In order for us to have a function that caters for both Numbers and String, we can use Rust's generics. So our function will look like;
fn bubble_sort<T: Ord>(list: &mut [T]) {
for i in 0..list.len() {
for j in 0..list.len() - 1 - i {
if list[j] > list[j + 1] {
list.swap(j, j + 1);
}
}
}
}
We are then able to simply call the above function whenever we feel like in our code like;
fn main() {
//create numbers and clients
bubble_sort(&mut numbers);
bubble_sort(&mut clients);
println!("Sorted Clients: {:?}", clients);
println!("Sorted List: {:?}", numbers);
}
Performance cost
For those who are interested in knowing if using Generics makes our code slower or not. The answer is that there is a zero cost on performance when using Generics. Our code performs just as fast.
The reason for this is that the Rust compiler does something known as monomorphization
. I know, it's a BIG word. In essence, it means that it turns the generic code into concrete code by looking at how you use the generics.
In our case, our function would then look like this at runtime;
fn bubble_sort_i32(list: &mut [i32]) {}
fn bubble_sort_str(list: &mut [str]) {}
Conclusion
We have now created DRY code, all thanks to Generics. This feature is quite amazing, as this allows us to cater for a wide variety of use cases.
Thank you for taking time out of your day to learn about Rust with me.
Please let me know how I could make this 30-day series better. I really love hearing from people who have read my posts.
Until next time, (tomorrow). Peace βπΌ .