Rust Learning Journey
Day 1 (2021/05/27)
Explored constants, variables, data types, control flow, and ownership.
charoccupies 4 bytes, equivalent to a Unicode scalar value- Control flow expressions don't require parentheses
- Tuples in Rust closely resemble C++ tuple usage
// clang++ test.cpp -std=c++11 && ./a.out
#include <iostream>
#include <string>
#include <tuple>
int main() {
std::tuple<int, std::string> data = std::make_tuple(42, "example");
std::cout << std::get<0>(data) << " " << std::get<1>(data) << std::endl;
std::string text;
int number;
std::tie(number, text) = std::make_tuple(100, "sample-data");
std::cout << number << " " << text << std::endl;
}
- Ownership:
- Clear and strict rules - resources are released at scope end
- Assignment transfers ownership, invalidating the previous value (except for primitive types)
- Multiple mutable references in same scope are prohibited, reducing race conditions
- Cannot have mutable and immutable references simultaneously
- Slices: Similar to C++'s string_view or Go's slices for function parameters
- Structs: Usage similar to C++
- Method definitions equivalent to C++
- Associated functions similar to C++ static methods
#[derive(Debug)]enables printing entire structure
Exercises
Temperature Conversion
fn celsius_to_fahrenheit(celsius: f32) -> f32 {
1.8 * celsius + 32.0
}
fn fahrenheit_to_celsius(fahrenheit: f32) -> f32 {
(fahrenheit - 32.0) / 1.8
}
Fibonacci Sequence
fn compute_fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 | 2 => 1,
_ => compute_fibonacci(n - 1) + compute_fibonacci(n - 2),
}
}
Day 2 (2021/05/28)
Enumerations
Simple enums work like C++:
enum Color { Red, Green, Blue }
Rust allows variants to hold different data types:
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn process_message(msg: Message) {
println!("{:?}", msg);
}
fn main() {
process_message(Message::Quit);
process_message(Message::Move { x: 10, y: 20 });
process_message(Message::Write(String::from("test-message")));
process_message(Message::ChangeColor(255, 0, 0));
}
Option<T>
Similar to C++'s type_traits for handling optional values:
enum Option<T> {
None,
Some(T),
}
Functions return None for empty values or Some(value) for valid results.
Pattern Matching
match is an expression where all arms must return the same type. It complements enums perfectly:
fn analyze_message(msg: Message) {
match msg {
Message::Quit => println!("Exiting"),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Writing: {}", text),
Message::ChangeColor(r, g, b) => println!("RGB: ({}, {}, {})", r, g, b),
}
}
Syntactic sugar if let simplifies single-variant matches.
Day 3 (2021/05/30)
Package Management
Cargo manages Rust projects with these concepts:
- Packages: Collections of source files with Cargo.toml
- Crates: Module trees (library or binary)
- Modules: Control scope and privacy
- Paths: Naming items like structs, functions, modules
Collections
Vector
- Provides
vec!macro for initialization - Prevents iterator invalidation through borrow checking
- Grows by factor of 2 like most implementations
String
- Built-in
str(usually as borrowed&str) - Owned
Stringfor mutable operations - UTF-8 encoded,
Vec<u8>internally - No random access to avoid invalid UTF-8 slices
HashMap
- Key-value store with ownership rules
- Access requires careful reference handling
Exercise: Statistics
fn calculate_mean(values: &Vec<i32>) -> Option<f64> {
if values.is_empty() {
return None;
}
let sum: i32 = values.iter().sum();
Some(sum as f64 / values.len() as f64)
}
fn find_median(values: &Vec<i32>) -> Option<i32> {
if values.is_empty() {
return None;
}
let mut sorted = values.clone();
sorted.sort();
Some(sorted[sorted.len() / 2])
}
fn determine_mode(values: &Vec<i32>) -> Option<i32> {
if values.is_empty() {
return None;
}
let mut frequencies = HashMap::new();
for value in values.iter() {
*frequencies.entry(value).or_insert(0) += 1;
}
frequencies.iter()
.max_by_key(|&(_, count)| count)
.map(|(&value, _)| *value)
}
Day 4 (2021/05/31)
Error Handling
panic! terminates execution. Use RUST_BACKTRACE=1 for stack traces.
Core error structure:
enum Result<T, E> {
Ok(T),
Err(E),
}
? operator propagates errors:
fn read_config() -> Result<Config, Error> {
let content = std::fs::read_to_string("config.toml")?;
Ok(parse_config(content)?)
}
Day 5 (2021/06/02)
Generics
Generic addition requires trait bounds:
use std::ops::Add;
fn add_values<T>(x: T, y: T) -> T
where
T: Add<Output = T>,
{
x + y
}
Traits
Traits define shared behavior, combining C++ type_traits and interface cocnepts:
pub trait Measurable {
fn get_height(&self) -> usize;
fn get_weight(&self) -> usize { 0 }
}
struct Person {
name: String,
}
impl Measurable for Person {
fn get_height(&self) -> usize {
self.name.len()
}
}
Lifetimes
Lifetime annotations prevent dangling references:
fn find_longer<'a>(first: &'a str, second: &'a str) -> &'a str {
if first.len() > second.len() {
first
} else {
second
}
}
Day 6 (2021/06/06)
Closures
Closures capture environment with three traits:
FnOnce: consumes captured valuesFnMut: mutably borrows captured valuesFn: immutably borrows captured values
Iterators
Iterator trait defines next() method:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Day 7 (2021/06/07)
Smart Pointers
Box<T> for heap allocation (like C++ unique_ptr)
struct CustomBox<T> {
data: T,
}
impl<T> CustomBox<T> {
fn create(value: T) -> CustomBox<T> {
CustomBox { data: value }
}
}
impl<T> std::ops::Deref for CustomBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
Rc<T> for reference counting (like shared_ptr)
RefCell<T> for interior mutability with runtime borrow checking
Concurrency
1:1 threading model with ownership transfer:
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Data: {:?}", data);
});
handle.join().unwrap();
}
Message passing via channels:
use std::sync::mpsc;
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
sender.send("Hello").unwrap();
});
println!("Received: {:?}", receiver.recv());
Day 8 (2021/06/08)
Unsafe Code
Unsafe blocks for:
- Raw pointer operations
- Union access
- Foreign function interface
- Calling unsafe functions
Advanced Traits
Associated types as type aliases:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Supertraits for trait dependencies:
trait Printable: std::fmt::Display {
fn print_border(&self) {
let output = self.to_string();
println!("+{}+", "-".repeat(output.len()));
}
}