30 days of Rust - Day Twenty - Patterns and Matching
Time moves so fast. Today marks 20 days since I started to learn the Rust programming language. Two thirds of the way through and I can't and won't stop now. I can see the light at the end of the tunnel.
I am finding the idea of spaced repetition, which involves learning something new and returning to it later, quite powerful. One could squeeze learning Rust into two days for example, but the brain will probably forget a lot of the content very quickly. What do you think of it?
Day 20 - Patterns and Matching
Rust provides a way to match against the structure of both simple and complex types. They can help us write more concise code. A patterns can be made up of;
- Literals.
- Destructured arrays, enums, structs, tuples.
- Variables.
- Wildcards.
- Placeholders.
In order to use a pattern, we compare it to some value and if it does match we use that part of our code.
Where are patterns used?
They can be found in a number of places such as the following;
Match Arms
Match expressions as seen on day 4, are similar to Switch statements from other programming languages and are a common way we use patterns. The syntax is as follow
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
There's one rule to this, being that the match must cover all possible values. The example below would be invalid. The reason is because not all possible outcomes are catered for.
fn main() {
let age = 2;
match age {
21 => println!("Happy 21st birthday"),
}
}
A solution to the above code is to add a catchall pattern for the last arm with the underscore as seen below;
fn main() {
let age = 2;
match age {
21 => println!("Happy 21st birthday."),
_ => println!("Your birthday doesn't matter.")
}
}
while let
For as long as the condition matches, out program will run the code inside the brackets;
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
The above code creates a vector and populates it with 1,2 and 3. The while let Some(top)
will be true for as long as stack.pop()
returns Some
value. Pop basically remove the last element of a list and returns it, so once the list is empty it shall return None
and the while loop will stop.
For loops
This was the most surprising to me. I didn't think of this having anything to do with patterns. So apparently the pattern is the value that comes after the for
keyword.
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
let statements
The most used pattern of all time. I was also surprised to learn that the declaration of a variable had a pattern. The syntax is as follows;
let PATTERN = EXPRESSION;
or more concretely;
let name = "Masai"
function parameters
The parameters or arguments of a function are a pattern. The function below called greeting
takes in a name of type String.
fn greeting(name: String){
// say something nice
}
Pattern Syntax
There are many ways to use patterns and I will only cover a few that I found interesting and believe are used most common.
Matching literals
The code below prints one
as it matches with the concrete value 1
.
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
Named variables
This is somewhat a tricky one due to scopes.
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
The code above outputs
Matched, y = 5
at the end: x = Some(5), y = 10
The reason is because the y
inside the match statement matches any value that is not None
. It is declared inside the match and takes on the value of x
.
Multiple Patterns
We can use the OR operator in rust by using the |
syntax. This allows us to match for more than one value. If one of them matches, then the code inside that arm shall be run.
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
Matching ranges with ..=
Rust also allows us to match a value within a particular range. So for example, the code below matches all values between 1 and 5.
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
Destructuring structs
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
Conclusion
Rust provides us many options of which we could match data types we use. These patterns I found quite interesting and there is still a lot more to this that I have left out.
Thanks for spending some time learning Rust with me. Until next time. Peace ✌🏼 .