COMP3007 - Modern Programming Languages
Week 6: Rust - Error Handling, Generic Types, Traits, and Lifetimes
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel
Department of Computer Engineering
Fall 2024-2025
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 1 / 26
Outline
1 Introduction to Error Handling in Rust
2 Unrecoverable Errors with panic!
3 Recoverable Errors with Result
4 Shortcuts for Panic on Error
5 Propagating Errors
6 To panic! or Not to panic!
7 Creating Custom Types for Validation
8 Conclusion
9 Generic Types, Traits, and Lifetimes
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 2 / 26
Error Handling Philosophy in Rust
Rust doesn’t have exceptions
Errors are handled explicitly
Two main categories of errors:
Recoverable errors (Result<T, E>)
Unrecoverable errors (panic!)
Emphasis on compile-time error checking
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 3 / 26
The panic! Macro
1 fn main () {
2 panic !("crash and burn");
3 }
Used for unrecoverable errors
Prints a failure message, unwinds, cleans up the stack, and quits
Can be used to check for conditions that should never happen
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 4 / 26
Using a panic! Backtrace
Set the RUST_BACKTRACE environment variable to get a backtrace
Useful for debugging panic situations
Example: RUST_BACKTRACE=1 cargo run
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 5 / 26
The Result Enum
1 enum Result <T, E> {
2 Ok(T),
3 Err(E),
4 }
T: The type of the value that will be returned in a success case
E: The type of the error that will be returned in a failure case
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 6 / 26
Using the Result Type
1 use std::fs:: File;
2
3 fn main () {
4 let f = File :: open("hello.txt");
5
6 let f = match f {
7 Ok(file) => file ,
8 Err(error) => panic !("Problem opening the file: {:?}", error),
9 };
10 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 7 / 26
Matching on Different Errors
1 use std::fs:: File;
2 use std::io:: ErrorKind;
3
4 fn main () {
5 let f = File :: open("hello.txt");
6
7 let f = match f {
8 Ok(file) => file ,
9 Err(error) => match error.kind () {
10 ErrorKind :: NotFound => match File :: create("hello.txt") {
11 Ok(fc) => fc ,
12 Err(e) => panic !("Problem creating the file: {:?}", e),
13 },
14 other_error => {
15 panic !("Problem opening the file: {:?}", other_error)
16 }
17 },
18 };
19 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 8 / 26
Shortcuts for Panic on Error: unwrap and expect
1 use std::fs:: File;
2
3 fn main () {
4 let f = File :: open("hello.txt").unwrap ();
5 }
1 use std::fs:: File;
2
3 fn main () {
4 let f = File :: open("hello.txt").expect("Failed to open hello.txt");
5 }
unwrap() calls panic! if the Result value is an Err
expect() lets us choose the panic! error message
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 9 / 26
Propagating Errors
1 use std::io;
2 use std::io:: Read;
3 use std::fs:: File;
4
5 fn read_username_from_file () -> Result <String , io::Error > {
6 let f = File :: open("hello.txt");
7
8 let mut f = match f {
9 Ok(file) => file ,
10 Err(e) => return Err(e),
11 };
12
13 let mut s = String ::new();
14
15 match f.read_to_string (& mut s) {
16 Ok(_) => Ok(s),
17 Err(e) => Err(e),
18 }
19 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 10 / 26
A Shortcut for Propagating Errors: the ? Operator
1 use std::io;
2 use std::io:: Read;
3 use std::fs:: File;
4
5 fn read_username_from_file () -> Result <String , io::Error > {
6 let mut f = File :: open("hello.txt")?;
7 let mut s = String ::new();
8 f.read_to_string (& mut s)?;
9 Ok(s)
10 }
The ? operator can only be used in functions that return Result
It’s a shorthand for the entire match expression
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 11 / 26
Guidelines for Error Handling
Use panic! when:
You’re in an unrecoverable state
Your code is in a bad state and it’s unexpected
You’re writing example code or prototypes
Use Result when:
An error is expected and can be handled
You’re writing a library and want to give users more control
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 12 / 26
Creating Custom Types for Validation
1 pub struct Guess {
2 value: i32 ,
3 }
4
5 impl Guess {x
6 pub fn new(value: i32) -> Guess {
7 if value < 1 || value > 100 {
8 panic !("Guess value must be between 1 and 100, got {}.", value);
9 }
10
11 Guess { value }
12 }
13
14 pub fn value (& self) -> i32 {
15 self.value
16 }
17 }
Encapsulate validation logic in types
Ensure invalid states are unrepresentable
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 13 / 26
Conclusion
Rust’s error handling is explicit and type-safe
Use panic! for unrecoverable errors
Use Result for recoverable errors
The ? operator simplifies error propagation
Custom types can encapsulate validation logic
Proper error handling leads to more robust and reliable code
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 14 / 26
Introduction to Generics
Generic types allow for flexible, reusable code
Reduce code duplication while maintaining type safety
Common in standard library collections
Zero-cost abstraction: No runtime overhead
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 15 / 26
Generic Functions
1 fn largest <T: PartialOrd >( list: &[T]) -> &T {
2 let mut largest = &list [0];
3
4 for item in list {
5 if item > largest {
6 largest = item;
7 }
8 }
9 largest
10 }
11
12 fn main () {
13 let numbers = vec ![34, 50, 25, 100, 65];
14 println !("Largest number: {}", largest (& numbers));
15
16 let chars = vec!['y', 'm', 'a', 'q'];
17 println !("Largest char: {}", largest (& chars));
18 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 16 / 26
Generic Structs
1 struct Point <T> {
2 x: T,
3 y: T,
4 }
5
6 struct MultiPoint <T, U> {
7 x: T,
8 y: U,
9 }
10
11 fn main () {
12 let integer_point = Point { x: 5, y: 10 };
13 let float_point = Point { x: 1.0, y: 4.0 };
14 let mixed_point = MultiPoint { x: 5, y: 4.0 };
15 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 17 / 26
Introduction to Traits
Traits define shared behavior across types
Similar to interfaces in other languages
Can be implemented for any type
Enable polymorphic behavior
Support default implementations
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 18 / 26
Defining and Implementing Traits
19 trait Summary {
20 fn summarize (& self) -> String;
21 fn default_behavior (& self) -> String {
22 String :: from("(Read more ...)")
23 }
24 }
25
26 struct NewsArticle {
27 headline: String ,
28 location: String ,
29 author: String ,
30 content: String ,
31 }
32
33 impl Summary for NewsArticle {
34 fn summarize (& self) -> String {
35 format !("{}, by {} ({})", self.headline , self.author , self.location)
36 }
37 }
38
39 struct Tweet {
40 username: String ,
41 content: String ,
42 }
43
44 impl Summary for Tweet {
45 fn summarize (& self) -> String {
46 format !("{}: {}", self.username , self.content)
47 }
48 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 19 / 26
Trait Bounds
49 fn notify <T: Summary >( item: &T) {
50 println !("Breaking news! {}", item.summarize ());
51 }
52
53 // Multiple trait bounds
54 fn complex_function <T: Summary + Display >( item: &T) {
55 println !("{}", item.summarize ());
56 }
57
58 // Where clause syntax
59 fn some_function <T, U>(t: &T, u: &U) -> i32
60 where T: Display + Clone ,
61 U: Clone + Debug
62 {
63 // Function body
64 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 20 / 26
Introduction to Lifetimes
Lifetimes ensure references are valid
Part of Rust’s borrow checker
Prevent dangling references
Most lifetimes are inferred
Generic lifetime parameters needed in some cases
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 21 / 26
Lifetime Annotation Syntax
65 // Lifetime annotation in function signatures
66 fn longest <'a>(x: &'a str , y: &'a str) -> &'a str {
67 if x.len() > y.len() {
68 x
69 } else {
70 y
71 }
72 }
73
74 // Lifetime annotations in structs
75 struct ImportantExcerpt <'a> {
76 part: &'a str ,
77 }
78
79 fn main () {
80 let string1 = String :: from("short");
81 let string2 = String :: from("longer");
82 let result = longest (&string1 , &string2);
83 println !("Longer string is {}", result);
84
85 let novel = String :: from("Call me Ishmael. Some years ago ...");
86 let first_sentence = novel.split('.').next ().expect("Could not find '.'");
87 let i = ImportantExcerpt {
88 part: first_sentence ,
89 };
90 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 22 / 26
Lifetime Elision Rules
Three rules that allow omitting lifetime annotations:
Each parameter gets its own lifetime parameter
If there is exactly one input lifetime parameter, it is assigned to all
output lifetime parameters
If there is a &self or &mut self parameter, its lifetime is assigned to all
output lifetime parameters
Compiler applies these rules automatically
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 23 / 26
Static Lifetime
91 // The 'static lifetime
92 let s: &'static str = "I have a static lifetime.";
93
94 // Generic type parameters , trait bounds , and lifetimes together
95 fn longest_with_announcement <'a, T>(
96 x: &'a str ,
97 y: &'a str ,
98 ann: T,
99 ) -> &'a str
00 where
01 T: Display ,
02 {
03 println !("Announcement! {}", ann);
04 if x.len() > y.len() {
05 x
06 } else {
07 y
08 }
09 }
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 24 / 26
Summary of Generics, Traits, and Lifetimes
Generic types provide code reuse with type safety
Traits define shared behavior between types
Lifetimes ensure reference validity
These features work together to provide:
Type-safe abstractions
Code reusability
Memory safety
Zero-cost abstractions
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 25 / 26
Thank You! Any Questions?
Dr. Öğr. Üyesi Yusuf Kürşat Tuncel (Department
COMP3007
of Computer
- Modern
Engineering)
Programming Languages Fall 2024-2025 26 / 26