BYU logo Computer Science
CS 465 Introduction to Security and Privacy

Learning Rust 3

Introductions

  • introductions of two students

Learning Rust, Section 9

We are using The Rust Programming Language and rustlings.

Error handling

Rust does not have exceptions. Instead, it has:

  • Result<T, E> for recoverable errors
  • panic! for unrecoverable errors

Panic

Panic prints a failure message, unwinds (cleans up the stack), and quits. You can display the call stack by setting an environment variable.

fn main() {
panic!("crash and burn");
}

This will cause a panic:

fn main() {
let v = vec![1, 2, 3];
v[99];
}

Errors

Rust includes a Result enum:

enum Result<T, E> {
Ok(T),
Err(E),
}

Here is an example of how to use it:

use std::fs::File;
fn main() { let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {error:?}"),
};
}

Note, instead of a panic, here, you could have an interface that asks the user to choose a different file.

Exercises

Complete the rustlings exercises for section 13, numbers 1-3. Numbers 4-6 contain advanced material.

Learning Rust, Section 10

Generic data types

Rust has generic data types, which allow a function to operate on a type that is not known in advance.

For example, this function can find the largest value in vector of numbers or characters:

fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {result}");
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {result}");
}

You can also use generics when defining types, such as a Point that can handle integer and floating point coordinates, and each coordinate can have a different type:

struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
}

Likewise, you can use generics in methods:

struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}

Here, the function x() is defined on Point and it returns a reference to x, which is of type T.

Traits

Rust uses traits to constrain types in a generic definition to ensure they implement some desired functionality. For example, this Summary trait indicates that a type should have a summarize() function:

pub trait Summary {
fn summarize(&self) -> String;
}

Now we can define different types (as structs) that implement the Summary trait:

pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}

Assuming the above is in a library crate called aggregator, we can use it in our main function:

use aggregator::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}

Binding traits

You can require a type implement a trait. For example, the notify() function below requires an item that implements the Summary trait:

pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}

You can do this with a longer-form notation as well:

pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}

These are equivalent!

You can specify multiple traits this way:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

or with a where clause:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{

Exercises

You are now ready to do rustlings section 14 on generics and 15 on traits.