State quarter from Alaska!
Value in cents: 25
()
Performant Software Systems with Rust — Lecture 5
Structs
Just like struct
in C or class
in Python
Like tuples, pieces of a struct
can be different types
Unlike tuples, in a struct
each piece of data has a name
Network Simulator: A Real-World Example
// simple time type
pub type Time = f64;
pub struct Packet {
/// the time when the packet is sent to the next switch
pub time: Time,
/// the time when the packet is originally generated
pub creation_time: Time,
/// the size of the packet in bytes
pub size: usize,
/// a unique identifier
pub packet_id: usize,
/// the flow identifier that the packet belongs to
pub flow_id: usize
}
Creating Instances of Structs
Learn more: Defining and Instantiating Structs
Using the Dot Notation to Access Specific Values
Learn more: Defining and Instantiating Structs
Using the Dot Notation to Access Specific Values
Learn more: Defining and Instantiating Structs
Can We Use References for Struct Data?
Learn more: Ownership of Struct Data
Returning an Instance in a Function
Learn more: Defining and Instantiating Structs
Using the Field Init Shorthand
Learn more: Using the Field Init Shorthand
Struct Update Syntax
Can we still use user1
after this?
Learn more: Struct Update Syntax
Tuple Structs
Unit-Like Structs
Can We Print an Instance of a Struct?
#[derive(Debug)]
Learn more: Adding Useful Functionality with Derived Traits
Refactoring fn area()
Rather than
Refactoring fn area()
It’s much better to write
Live Demo
Learn more: Refactoring with Structs
Methods
enum
or a trait object, to be discussed laterself
Back to Rectangle
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 { // short for self: &Self
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area() // using the method syntax
// rather than area(&rect1)
);
}
Learn more: Defining Methods
Where’s the ->
operator?
Learn more: Where’s the ->
operator?
Multiple impl
Blocks Are Fine
Learn more: Multiple impl
Blocks
Associated Functions
Associated functions don’t have self
as their first parameter, and are not methods
Often used for constructors
Learn more: Associated Functions
Enums
Enums allow you to define a type by enumerating its possible variants
Its value is one of a possible set of values
Learn more: Defining an Enum
Network Simulator: Real-World Examples
Learn more: Defining an Enum
Creating Instances of Enum Variants
Learn more: Enum Values
But What about IP Address Data?
There Is a Better Way
We can put data directly into each enum
variant!
name of each enum variant becomes a function that constructs an instance of the enum
You can put any kind of data inside an enum variant: strings, numeric types, or structs
You can even include another enum!
Another Example of Data in Enum Variants
Why Are Enums Better Than Using Structs?
Even Better Way of Defining IP Addresses
Learn more: Enum Values
How the Rust Standard Libary Did it
Simulator: Real-World Example
Learn more: Enum Values
The Option
Enum
Option
: a value can be something or nothing
Looks like null
in other programming languages
null
(or nil
) or non-null
Learn more: The Option
Enum
Null References: The Billion Dollar Mistake
— Tony Hoare (of the Hoare Semantics fame), 2009
I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
Learn more: The Option
Enum
The Rust Way of Handling Values that Are Absent
<T>
is generic type parameter, which will be introduced later
<T>
means that the Some
variant of the Option
enum can hold one piece of data of any concrete type
and each concrete type that gets used in place of T
makes Option<T>
type a different type
Different Option
Types
Option<T>
any better than having null
?Option<T>
and T
(where T
can be any type) are different types, the compiler won’t let us use an Option<T>
value as if it were definitely a valid value
you have to convert an Option<T>
to a T
before you can perform T
operations with it!
Live Demo
This idea eliminates the risk of incorrectly assuming a non-null
value
To have a value that can possibly be null
, you must explicitly opt in by making the type of that value Option<T>
, and to explicitly handle the case when the value is null
How do we get the T
value out of a Some
variant when you have a value of type Option<T>
?
Seriously — Use match
Expressions on Enums
Pattern Matching: if
vs. match
Conditions in if
expressions must evaluate a bool
value
In match
, any type is fine
Patterns That Bind to Values
Patterns That Bind to Values
State quarter from Alaska!
Value in cents: 25
()
Learn more: Patterns That Bind to Values
This Is All We Need for Matching with Option<T>
Live Demo
Learn more: Matching with Option
Matches Are Exhaustive
Compile-Time Error —
Learn more: Matches Are Exhaustive
Catch-all Patterns
Learn more: Matches Are Exhaustive
The _
Placeholder
Live Demo
Learn more: Catch-all Patterns and the _ Placeholder
Concise Control Flow with if let
Rather than
The Power of Enums
If debugging is the process of removing software bugs, then programming must be the process of putting them in.
— Edsger W. Dijkstra (1930 – 2002)
error[E0004]: non-exhaustive patterns: `ChristmasTree::Dead` not
covered
--> src/main.rs:8:11
|
8 | match tree {
| ^^^^ pattern `ChristmasTree::Dead` not covered
|
note: `ChristmasTree` defined here
--> src/main.rs:2:10
|
2 | enum ChristmasTree {
| ^^^^^^^^^^^^^
3 | Alive { growing: bool },
4 | Dead,
| ---- not covered
= note: the matched value is of type `ChristmasTree`
help: ensure that all possible cases are being handled by adding a
match arm with a wildcard pattern or an explicit pattern as shown
|
9 ~ ChristmasTree::Alive { .. } => println!("Alive."),
10~ ChristmasTree::Dead => todo!(),
|
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
— Joe Armstrong, creator of Erlang
Characteristics of Object-Oriented Languages
Objects contain data and behaviour (Design Patterns, Gang of Four, 1994)
impl
blocks provide methods (behaviour)Encapsulation that hides implementation details
Learn more: Characteristics of Object-Oriented Languages
Characteristics of Object-Oriented Languages
Inheritance — an object can inherit elements from its parent object’s definition
Rust is not object-oriented if inheritance is required
If you just need to reuse code, you can do this in a limited way using trait
method implementations
trait
, and that’s excellent!Learn more: Characteristics of Object-Oriented Languages
Characteristics of Object-Oriented Languages
Polymorphism — you can substitute multiple objects of different types at runtime if they share certain characteristics
Learn more: Characteristics of Object-Oriented Languages
Rust uses generic types instead to abstract over different types
Rust also implements bounded parametric polymorphism, which uses train objects and trait bounds to impose constraints on what these types must provide
Learn more: Characteristics of Object-Oriented Languages
The Rust Programming Language, Chapter 5, 6, and 18.1