30 days of Rust - Day four - Enums
Oftentimes in code we come across situations where we need to represent data as just a set of possible variations. For instance, when making an online purchase, the merchant asks what type of bank account you use in order to complete the transaction. It's either ;
- Savings
- Cheque
- Current
Having these options as Strings or just a numbers/index increases the chances of you having errors (like typos). It also makes it easier for others to understand your code faster.
This is where enums (Enumerations) shine.
Enums in Rust
Let's first define a AccountType 🍻 enum in Rust;
enum AccountType {
Savings,
Cheque,
Current,
}
AccountType is now a custom data type that can be used in our code.
In order to get the value we simply;
let your_fnb_account = AccountType::Savings;
You can even pass it to functions,
fn get_bank_statement(account_type: AccountType){
//perform some actions
}
get_bank_statement(AccountType::Cheque);
Being able to pass it to functions makes it much safer to use compared to having to pass it as a string. A typo such as chequ
instead of cheque
may cost the bank billions 🤣.
get_bank_statement(String::from("cheque"));
Enum Methods
Enums have a lot in common with Structs. They have constructors, methods and can also have other data types associated with each variant;
enum AccountType{
Savings(String, f32), //account number , interest rate
Cheque(String), // account num
Current(String, f32), //account number, overdraft
}
Similar to structs, enums can also have methods associated with them and their definition is the same.
impl AccountType {
fn calculate_clearance_time(&self) -> i32{
// all the magic happens here
48 // working hours
}
}
let my_african_bank_current = AccountType::Current(String::from("12345678"), -2500);
my_african_bank_current.calculate_clearance_time();
Do we still need structs?
Well to my knowledge so far, it looks like they can co-exist. I would go for structs if I have many fields which I want named. e.g A person with an ID number, first name, last name, race, home language, job, etc. Whereas for smaller objects (Gender, Nationality, etc), an enum would work just fine.
Option and the end of Null
Null basically means having no value. Variables are either null or not null. In many programming languages, it introduces a lot of bugs causing software to crash. Rust's creators excluded the null value for this reason.
Instead, rust has an Option<T> which is implemented like;
enum Option<T> {
None,
Some(T),
}
Using Option with Match
In order to use the option, we use one of Rust's most loved features, called match.
fn square_number(x: Option<i32>) -> {
match x {
None => None,
Some(i) => Some(x * x),
}
}
let four = Some(4);
let sixteen = square_number(four);
let none = square_number(None);
A match is similar to a switch statement in other programming languages. The order in which the cases are executed are from top to bottom. Each of them is called an arm;
None => None
Some(4)
evaluated against None
does not match, so it checks the next arm;
Some(i) => Some(x * x),
Some(4)
matches with Some(i)
and so the code after the =>
is executed returning the square of the number.
Conclusion
Enums are quite a powerful tool to have in our toolbox. They allow us to introduce new data types to our application, which are useful for our use case.
Having the match control flow used together with enums helps make our code much safer and bug free.
I hope to implement these soon in an actual project to get a better feel of the concepts.
Thanks a million for taking time out of your day to read this blog post. If you haven't yet read my previous posts, please head over to my blog page to see my journey from day one.
Let me know if you're joining the 30 day challenge with me.