@bhushanwho
informal gentle nudge to rust
Feb 09, 2026here’s a loose, informal and gentle introduction and nudge into rust. at least the basics, syntactically.
this was buried in my obsidian notes and figured might as well be on here for reference.
random tutorial-doc snippets of rust.
tuples
let coords = (10.5, 20.3);
tuple elements can be accessed with a.0 or a.1. strange but oddly convenient.
matches
here’s how match can be used in Rust
enum Direction {
North,
South,
East,
West
}
fn get_direction( dir: Direction ) {
match dir {
Direction::North => (0, 1),
Direction::South => (0, -1),
Direction::East => (1, 0),
Direction::West => (-1, 0),
}
}
| matches, like everything, are expressions |
modules
use std::cmp::min;
use std::cmp::max;
// also, work
use std::cmp::{min, max};
// but like... why.
if-let and match-arms
woah.
struct Number {
odd: bool,
value: i32,
}
fn main() {
...
}
fn print_number( num: Number ) {
if let Number { odd: true, value } = num {
// something
} else if let Number { odd: false, value } = num {
// whatever
}
// similarly
match num {
Number { odd: true, value } => // something
Number { odd: false, value } => // whatever
}
}
a match has to be exhaustive, at least one has to match. say Number { value, .. } to catch-all, or better yet, just _ => // something |
structs
there’s the struct update syntax
let v3 = Vec2 { x: 14.0, ..v2 };
let v4 = Vec3 { ..v3 };
destructuring is a thing
let (left, right) = something();
let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
let Vec2 { x, .. } = v; // called throwaway, cause it throws away.
functions for a struct. you can say impl <struct Name> to add the function to it. implements.
struct Number {
odd: bool,
value: i32,
}
impl Number {
fn someFunction(self) -> bool {
println!("ayy pythonic 'self'");
self.value == 2
}
}
variable bindings are immutable, can’t change
fn main() {
let n = Number {
odd: true,
value: 3,
};
n.odd = false; // error
}
can’t reassign either.
say let mut n = 4 for mutability.
traits
traits are like interfaces, of sort. signature is written and you write implementations.
trait Signed {
fn is_signed(self) -> bool;
}
impl Signed for Number {
// implementation
}
also kind of overloading. say a custom implementation of negation
impl std::ops::Neg for Number {
type Output = Number;
fn neg(self) -> Number {
Number {
value: -self.value,
odd: self.odd,
}
}
}
// later in the program let m = -n of
// type Number will work because of that implementation.
Self is a reference to the for type of the implementation.
also passing by reference
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
marker traits, like marker interfaces in java.
impl std::marker::Copy for Number {}
since some traits are common, you can directly derive them through, directives?
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// shorthand for impl Clone for Number and impl Copy for Number blocks
well, formally “attributes” but you get the idea. INNER, attributes. implying the existence of other types. but you can go read about those.
panics
Option is an Enum actually, so is Result.
enum Option<T> { None, Some(T), }
enum Result<T, E> { Ok(T), Err(E), }
an .unwrap() on nothing causes a panic. so if you wanna panic in case of a failure, do that.
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
println!("{:?}", s);
// prints: "🍉"
let s = std::str::from_utf8(&[195, 40]).unwrap();
// prints: thread 'main' panicked at 'called `Result::unwrap()`
// on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
// src/libcore/result.rs:1165:5
}
or .expect(), for a custom message:
fn main() {
let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
// prints: thread 'main' panicked at 'valid utf-8: Utf8Error
// { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}
or, you can match:
fn main() {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => panic!(e),
}
// prints 🍉
}
or you can if-let:
fn main() {
if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
println!("{}", s);
}
// prints 🍉
}
or you can bubble up the error:
fn main() -> Result<(), std::str::Utf8Error> {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => return Err(e),
}
Ok(())
}
or you can use ?:
fn main() -> Result<(), std::str::Utf8Error> {
let s = std::str::from_utf8(&[240, 159, 141, 137])?;
println!("{}", s);
Ok(())
}
static lifetime.
struct Person {
name: &'static str,
}
fn main() {
let p = Person { name: "monkey", };
}
there’s a lot more to read about lifetimes. starts with a single quote, as 'static. go over it.
closures
closure (arrow functions in react. in a way. in a really confusing, convoluted way) are functions of type Fn, FnMut or FnOnce.
fn for_each_something<\T>(f: F)
where F: Fn(&'static str)
{
f(1);
f(2);
f(3);
}
for_each_something(|x| println!("{}", 2*x));
a countdown closure
fn countdown<\f>( count: usize, tick: F )
where F: Fn(usize)
{
for i in (1..=count).rev() {
tick(i);
}
}
fn main() {
countdown(3, |i| println!("tick {}", i));
}
familiar react-like functional code.
fn main() {
for c in "somestring"
.chars()
.filter(|c| c.is_lowercase())
.flat_map(|c| c.to_uppercase())
{
print!("{}", c)
}
println!();
}
it won’t hurt to keep reading rust code from time to time and say “ah yeah this”.
there’s a lot more rules, and verbosely boring concepts mutability, borrowing, lifetimes, pointers, dereferencing and such.
this was served as a silly lightweight nudge into looking at and understand rust code at a syntactical level. and thus, this, ends here.
have a nice day.