iTranslated by AI
Language-Specific Syntax: Rust
This article is for Day 11 of the Programming Language Specific Syntax Advent Calendar 2025.
I'll introduce these while including some of my personal preferences.
Sample code for binary search
Implemented by intentionally leveraging the language's characteristics.
// Rust - Pattern matching + Result/Option + Iterators
use std::cmp::Ordering::*;
fn binary_search<T: Ord>(arr: &[T], target: &T) -> Option<usize> {
let (mut left, mut right) = (0, arr.len().checked_sub(1)?);
while left <= right {
let mid = left + (right - left) / 2;
match arr[mid].cmp(target) {
Equal => return Some(mid),
Less => left = mid + 1,
Greater => right = mid.checked_sub(1)?,
}
}
None
}
fn main() {
let arr = [1, 3, 5, 7, 9];
println!("{}", binary_search(&arr, &5).unwrap_or(!0)); // 2
}
Featured Syntax
Pattern Matching
Branching based on the structure of values can be performed using match expressions or if let.
// match expression
match value {
0 => println!("zero"),
1 | 2 => println!("one or two"),
3..=9 => println!("three to nine"),
n if n < 0 => println!("negative"),
_ => println!("other"),
}
// if let
if let Some(x) = optional {
println!("{}", x);
}
// let else
let Some(x) = optional else { return };
The richness of pattern matching expressions is great.
Option
Expresses a state where a value may or may not exist as a type. This prevents null reference errors.
// Optional values in CLI arguments
struct Cli {
port: Option<u16>, // Might not be specified
process: Option<String>,
}
// if let pattern matching
if let Some(port) = port_filter {
println!("Port: {}", port);
}
// Extracting by splitting
if let Some((start, end)) = "3000-3100".split_once('-') {
println!("{} to {}", start, end);
}
Option Chaining
// Chain with and_then, map, ok
extract_port(name).and_then(|s| {
s.parse::<u16>().ok().map(|port| PortInfo { port })
})
// unwrap-style methods
let cmd = get_command(pid).unwrap_or_else(|_| "unknown".to_string());
let time = get_time(pid).unwrap_or_default(); // Uses Default trait
let name = info.first().map(|i| i.name.clone()).unwrap_or_default();
Result
Represents success or failure as a type. Error handling without exceptions.
use anyhow::{Context, Result};
// Function returning Result
fn load_config() -> Result<Config> {
let path = config_path()?; // Early return with ?
let content = std::fs::read_to_string(&path)
.context("Failed to read config")?; // Add context to error
toml::from_str(&content).context("Failed to parse config")
}
// main can also return Result
fn main() -> Result<()> {
let config = load_config()?;
Ok(())
}
Ownership
A unique Rust system that guarantees memory safety without GC.
// Move ownership
let s1 = String::from("hello");
let s2 = s1; // Ownership of s1 moves to s2
// println!("{}", s1); // Compile error! s1 is invalid
// Copy types are copied instead of moved
let x = 5;
let y = x; // i32 is Copy, so it's copied
println!("{}", x); // OK
Borrowing Rules
let mut s = String::from("hello");
// Immutable borrowing: Multiple allowed
let r1 = &s;
let r2 = &s;
// Mutable borrowing: Only one
let r3 = &mut s;
// let r4 = &mut s; // Error! Two mutable references at the same time are not allowed
// Simultaneous use of immutable and mutable is also not allowed
// let r5 = &s; // Not allowed because r3 exists
This constraint prevents data races at compile time.
Lifetimes
// Explicitly state which reference the return value has the same lifetime as
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Detect dangling references at compile time
fn dangling() -> &String {
let s = String::from("hello");
&s // Error! s is dropped when it goes out of scope
}
Iterators and Closures
Features for lazy-evaluated iterator chains and anonymous functions.
// Iterator chain
let sum: i32 = (1..=10)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.sum();
// Closures
let add = |a, b| a + b;
let double = |x| x * 2;
// move closures
let s = String::from("hello");
let f = move || println!("{}", s);
I like that the operators for inclusive and exclusive ranges are easy to understand.
Macros
Declarative macros that generate code at compile time.
// Declarative macros
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp = Vec::new();
$( temp.push($x); )*
temp
}
};
}
// Built-in macros
println!("Hello, {}!", name);
format!("{:?}", value);
vec![1, 2, 3];
It's interesting that these are implemented as macros.
Trait Bounds
Allows specifying required functionality as constraints for generic types.
// Generic constraints
fn print_debug<T: std::fmt::Debug>(value: T) {
println!("{:?}", value);
}
// where clause
fn complex<T, U>(t: T, u: U)
where
T: Clone + Debug,
U: Into<String>,
{ }
// impl Trait
fn make_iter() -> impl Iterator<Item = i32> {
(0..10).filter(|x| x % 2 == 0)
}
Discussion