Set Default Values in Rust
Rust
Rust provides many ways to set a default value - or to fallback gracefully in the event of an error. This article focuses on how to handle Option
types.
The Option<T>
type represents an optional value. An Option<T>
can either contain Some()
value, or None
(which is self-explanatory). Specifically, Option<T>
is an enum with two variants:
None
: indicating lack of value or failureSome(value)
: A tuple struct that wraps a value of typeT
Unwrapping an Option
If the Option
is a Some
variant, the value can be accessed using the unwrap()
method:
let x: Option<u8> = Some(42);
println!("x unwraps to give the value {}", x.unwrap());
// x unwraps to give the value 42
Trying to unwrap()
a None
variant causes a panic!
, which is not a particulary elegant way of terminating early:
let y: Option<u8> = None;
println!("unwrapping y will cause a panic! {}", y.unwrap());
// thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'...
Unwrap, Or?
If you use the unwrap_or()
method to unwrap an Option<T>
, you get to specify a fallback/default value.
This ensures that the programme does not panic and is useful if the only appropriate response to the None
variant is a default value:
fn main() {
// Simulate function returning Option
let x: Option<u8> = Some(42); // Some variant
let y = None; // None variant
let a = x.unwrap_or(13);
let b = y.unwrap_or(13);
println!("a = {}", a);
println!("b = {}", b);
}
// Output:
// a = 42
// b = 13
Another example that uses Option<&'static str>
:
fn main() {
// Simulate function returning Option of Some() variant:
let message: Option<&'static str> = Some("+++MELON MELON MELON+++");
// Simulate function returning Option of None variant:
let empty_message = None;
let a = message.unwrap_or("+++Divide By Cucumber Error. Please Reinstall Universe And Reboot +++");
let b = empty_message.unwrap_or("+++Divide By Cucumber Error. Please Reinstall Universe And Reboot +++");
println!("a = {}", a);
println!("b = {}", b);
}
// Output:
// a = +++MELON MELON MELON+++
// b = +++Divide By Cucumber Error. Please Reinstall Universe And Reboot +++
Let Match
A match
statement can be used to match against an Option<T>
pattern, which allows for more complex handling of each variant.
This example accomplishes the same thing as the one above - as such it’s not that useful, but is presented as a comparison:
fn main() {
// Match is an expression as well as a statement. This example shows how
// a variable is set based on an Option enum.
// ------------------------------------------
let msg_option = Some("+++MELON MELON MELON+++"); // Some variant
let msg_option_empty = None; // None variant
let msg = match msg_option {
Some(m) => m, // m is the unwrapped data from the option
None => "Out of Cheese Error",
};
println!("msg = {}", msg);
// As above, but handling the None variant
let msg_2 = match msg_option_empty {
Some(m) => m, // In this case we won't enter this branch
None => "Out of Cheese Error", // msg_2 set to this value
};
println!("msg_2 = {}", msg_2);
}
// Output:
// msg = +++MELON MELON MELON+++
// msg_2 = Out of Cheese Error
The following example shows how we might handle Option
return from a function.
In this case, if the function returns the None
variant, we print a message and end the programme early:
fn main() {
let i = 5;
//let input: Option<u8> = Some(i);
let input: Option<u8> = None;
// If the Option returned by square() has Some() value, set s to this value.
// Otherwise, return early.
let s = match square(input) {
Some(ans) => ans,
None => {
println!("square() did not return a value.");
return
},
};
// We didn't return early, so s has a value. There is no need to unwrap.
println!("{operand} * {operand} = {}", s, operand = i);
}
/// Check if arg has a value. If it does, set val to the value.
/// Otherwise, return None. Once we have a val, return the square.
fn square(arg: Option<u8>) -> Option<u8> {
let val = if let Some(val) = arg {
val
} else {
return None;
};
Some(val * val)
}
The if let Idiom
In the square()
function in the example above, you could write the guard clause as:
let val = if let Some(val) = arg { val } else { return None; };
// If arg Option destructures, set val to it's value. Otherwise, enter the else block.
This has the effect of setting val
to the value of arg
if it is the Some()
variant. If arg
is the None
variant, the function returns early with the returned Option
being the None
variant.
The if let
idiom combines a pattern match
and an if
statement.
If you just want to operate on the value held by the Option
when it is of the Some()
variant:
fn main() {
let n = Some(42); // The Option
// If you just want to operate on the value held by the `Option`.
// If the option is the `None` variant, do nothing.
// "If n is the `Some()` variant, bind it's value to r and use this in the
// following block."
if let Some(r) = n {
// We only enter this block if n is the Some() variant
print_num(r);
}
// This has the same result as the above if let block:
match n {
Some(r) => print_num(r),
_ => {},
}
}
Failure Case/Fallback Required
fn main() {
let n = None;//Some(42);
let fallback = 17;
// If a failure case is required.
// "If n is the `Some()` variant, bind it's destructured value to r and pass this
// to the following block. Otherwise, enter the else block."
if let Some(r) = n {
print_num(r);
} else {
print_num(fallback);
}
// This has the same result as the above:
match n {
Some(r) => print_num(r),
_ => print_num(fallback),
}
}
Save Destructured Value into a Variable with a Default
The following example shows how to save the destructured value into a variable (in the event of the Option
being the Some()
variant) with a default value as a fallback:
fn main() {
let n = None;//Some(42);
// Save the destructured value into a variable.
// If the Option variant is Some(), destructure it's value and save it into
// the variable `result`. In the case of the `None` variant, set result to
// the default value of 13.
let result = if let Some(r) = n { r } else { 13 };
// Equivalent to the above:
let result_2 = match n {
Some(x) => x,
None => 13,
};
println!("result = {}", result);
println!("result _2 = {}", result_2);
}
References
- Rust by Example: Option - has a good practical example that handles divide by zero
- Rust by Example: Result - follows on from the above, with better handling of divide by zero.
- Concise, clear description of if let
comments powered by Disqus