1.0 - Introduction
Rust error handling is based on two things: enumerations and matching. Rust's enumerations are more
powerful than those of C++ and C#. Each element of the enumeration may wrap some type, as shown below.
enum Option<T> { Some(T), None, }
Option is used when a value cannot be initialized at compile-time or a function that returns a value for
some paths but may have nothing to return for another path, e.g. return the first occurence of ...
For cases where that indeterminacy is caused by possible errors, Rust provides the Result type:
enum Result<T, E> { Ok(T), Err(E), }
Result returns one of either of two types of values, a valid return value or a specific error value, perhaps
a string or error code. Options and Returns are discussed in detail, with examples, in
the two following Error Handling and Options
Rust Bites.
2.0 - Custom Enumerations
Often in code we use enums defined in the standard libraries. However, it can be quite useful to develop
custom enumerations like the one shown below.
This example shows an
Event<T> enumeration that represents different levels of events, and wraps information
about the event inside the event item except for the NoEvent item. This example shows how to
write code to wrap and unwrap values from enumeration items.
Custom Enumeration Example
#![allow(unused_variables)]
/*-----------------------------------------------------
The generic parameter T represents some value
associated with events, perhaps a name String
or id number.
*/
#[derive(Debug, Clone)]
enum Event<T> {
Normal(T), Warning(T), Critical(T), NoEvent
}
impl<T> Event<T> {
fn unwrap(&self) -> &T {
if let Event::Normal(ev) = self{ ev }
else if let Event::Warning(ev) = self{ev}
else if let Event::Critical(ev) = self{ev}
else { panic!() }
}
}
use Event::*;
fn main() {
/*-- numbers are event ids --*/
let e1: Event<u8> = Event::<u8>::Normal(1);
let e2 = Warning(2);
let e3 = Critical(3);
let e4: Event<u8> = NoEvent;
/*-- match works like switch stmt on steriods --*/
match e3 {
Normal(ev) =>
print!("\n event {} is Normal", ev),
Warning(ev) =>
print!("\n event {} is Warning", ev),
Critical(ev) =>
print!("\n event {} is Critical!", ev),
NoEvent => print!("\n no events occurred"),
}
/*-------------------------------------------------
without the clone() operation below e2 would
move into e and become invalid for future
operations.
*/
let e = e2.clone();
/*-------------------------------------------------
if let statements use "=" as match operator
*/
if let Warning(ev) = e {
print!("\n event {} is Warning", ev);
}
/*-- next stmt panics if event is NoEvent type --*/
let v = e3.unwrap();
print!("\n inner value of {:?} is {}", e3, v);
/*-- will panic if you unwrap() e4 --*/
}
Output
event 3 is Critical!
event 2 is Warning
inner value of Critical(3) is 3
Comments
Enumerations can have methods defined
in the same way we define methods for
structs. That let's us acess and use their
inner values.
Match statements require us to handle all of
the enum items.
Sometimes we don't need to do that.
Then, we can use the if let statement to
test for a single enum item type.
Here,
the "=" operator is not assignment.
It is a matching operator that selects on a
single enum item type.
How cool is Rust? Very cool!
Example:
playground example
3.0 - Matching
Matching is Rust's model for alternates selection - more powerful than switch-case operations, as
shown by the example, above.
Matching works for more than just enum items. The example below shows matching on char ranges.
Matching Ordinary Data
fn do_match(c: char) {
match c {
('0'..='9') =>
print!("\n {} is a digit", c),
('a'..='z') =>
print!("\n {} is lower case ascii char", c),
('A'..='Z') =>
print!("\n {} is upper case ascii char", c),
_ => print!("\n {} is some other char type", c)
}
}
fn main() {
let x = 'a';
do_match(x);
do_match('Q');
do_match('7');
do_match('@');
}
Output
a is lower case ascii character
Q is upper case ascii character
7 is a digit
@ is some other char type
Comments
Lexer here we come!
playground example