Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Test MDX File V11

Format:

Rust concept demo

Shadowing in Rust

src/main.rs
1fn main() {
2 let x = 5;
3 let x = x + 1;
4 let x = x * 2;
5 println!("x = {x}");
6}
terminal — cargo run
$ cargo run
x = 12
What just happened

Each `let x = …` creates a brand-new variable that happens to reuse the name `x`. The previous `x` is shadowed — still alive in memory until the new binding takes its place, but no longer reachable by that name. This compiles even though every `x` is immutable, because we're not mutating anything: we're introducing fresh bindings.

Rust concept demo

Copy and Clone in Rust

src/main.rs
1fn main() {
2 let x = 5;
3 let y = x;
4 println!("x = {x}, y = {y}");
5}
terminal — cargo run
$ cargo run
x = 5, y = 5
What just happened

`i32` implements the `Copy` trait, so `let y = x;` makes a bit-for-bit copy of the value and both bindings stay valid afterwards. `Copy` is reserved for types whose whole value lives on the stack with no owned heap data — primitives (`i32`, `f64`, `bool`, `char`), shared references (`&T`), and tuples or arrays of `Copy` types. The duplication is cheap and safe, so Rust just does it for you on assignment.

Rust concept demo

Immutable & Mutable Borrows in Rust

src/main.rs
1fn main() {
2 let s = String::from("hello");
3 let r1 = &s;
4 let r2 = &s;
5 let r3 = &s;
6 println!("{r1} / {r2} / {r3}");
7}
terminal — cargo run
$ cargo run
hello / hello / hello
What just happened

A shared borrow `&s` gives read-only access to `s` without taking ownership. As many `&s` references can coexist as you like — they all see the same value, none can change it, and `s` itself stays usable too. Read-only access is safe to share, so Rust doesn't restrict it.

Rust concept demo

Lifetimes in Rust

src/main.rs
1fn main() {
2 let r;
3 {
4 let x = 5;
5 r = &x;
6 }
7 println!("r = {r}");
8}
terminal — cargo build
$ cargo build
error[E0597]: `x` does not live long enough
--> src/main.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x;
| ^^ borrowed value does not live long enough
6 | }
| - `x` dropped here while still borrowed
7 | println!("r = {r}");
| - borrow later used here
What the compiler is telling you

Every value has a **lifetime** — the span of code during which it is alive. `x` is declared inside the inner block, so its lifetime ends at the closing `}`. But `r` is declared in the outer scope and is still in use on the `println!` line. A reference cannot outlive what it points at, so the compiler refuses. This is the classic dangling-pointer bug — caught at compile time, before the program ever runs.

Rust concept demo

Array & String Slices in Rust

src/main.rs
1fn main() {
2 let arr = [10, 20, 30, 40, 50];
3 let s: &[i32] = &arr[1..4];
4 println!("{s:?}");
5}
terminal — cargo run
$ cargo run
[20, 30, 40]
What just happened

A slice is a *view* into a contiguous run of elements — it doesn't own anything, it just borrows. The range `1..4` is half-open: it includes index 1 but stops before index 4, giving you elements at positions 1, 2, and 3. Under the hood `s` is a 16-byte fat pointer: an address (pointing at `arr[1]`) plus a length (3). No allocation, no copy — slicing is essentially free.

Rust concept demo

Idiomatic Error Handling in Rust

src/main.rs
1fn main() {
2 let nums = vec![1, 2, 3, 4, 5];
3
4 match nums.iter().find(|&&n| n > 3) {
5 Some(n) => println!("found {n}"),
6 None => println!("nothing found"),
7 }
8}
terminal — cargo run
$ cargo run
found 4
What just happened

Anything that *might* be absent in Rust is wrapped in `Option<T>` — there are only two possibilities, `Some(value)` or `None`, and the compiler forces you to handle both. `match` is the standard way to do it: each arm pulls the value out (if any) and decides what to do. Compare to languages where the same code returns null and you can dereference it without thinking — Rust just doesn't let you reach the value without first acknowledging it might not be there.

The C++ comparison is genuinely useful in this demo because almost every C++ programmer has the same model (std::string and string_view) and can transfer most of their intuition straight across — what’s new is the type-system enforcement.

Rust concept demo

`String` vs `&str` in Rust

src/main.rs
1fn main() {
2 let lit: &str = "hello";
3 let owned: String = String::from("hello");
4
5 println!("{lit} ({} bytes long)", lit.len());
6 println!("{owned} ({} bytes long)", owned.len());
7}
terminal — cargo run
$ cargo run
hello (5 bytes long)
hello (5 bytes long)
What just happened

Rust has two string types and the difference is **ownership**, not content. `&str` is a borrow — a 16-byte fat pointer (data ptr + length) into bytes that live somewhere else; for a literal, those bytes are baked into the program's read-only data segment. `String` is an owned, growable buffer on the heap, represented on the stack as 24 bytes (ptr + length + capacity). Same characters, same `.len()`, very different storage and ownership stories.

C++ comparison

C++ touchstone: `String` is `std::string` — an owning, heap-backed, growable buffer. `&str` is closest to `std::string_view` (C++17): a non-owning view of someone else's bytes. The standard library and idiomatic APIs in modern C++ have been moving the same direction Rust started: take a view when you only need to read.

Rust concept demo

Generics & Trait Bounds in Rust

src/main.rs
1fn largest<T: PartialOrd>(list: &[T]) -> &T {
2 let mut biggest = &list[0];
3 for x in list {
4 if x > biggest { biggest = x; }
5 }
6 biggest
7}
8
9fn main() {
10 let nums = vec![34, 50, 25, 100, 65];
11 println!("largest = {}", largest(&nums));
12}
terminal — cargo run
$ cargo run
largest = 100
What just happened

`<T: PartialOrd>` declares a *type parameter* `T` together with a *trait bound* — any type used as `T` must implement `PartialOrd`, the trait that supplies `<`, `>`, `<=`, and `>=`. The bound is the contract the compiler needs in order to allow `x > biggest` inside the body. Without it, the compiler couldn't know whether `>` is even defined for whatever `T` turns out to be — see scenario 3 for what happens if you forget.

C++ comparison

C++ touchstone: this is a function template — `template <typename T> T const& largest(std::vector<T> const& v)`. Pre-C++20, the constraint was implicit and only surfaced as a wall of template-instantiation errors when `>` failed for some `T`. With C++20 *concepts* (`requires std::totally_ordered<T>`) you can finally say the constraint up front — exactly what Rust's trait bounds have done since day one.

Rust concept demo

Traits & `impl` in Rust

src/main.rs
1trait Greet {
2 fn hello(&self);
3}
4
5struct Dog;
6
7impl Greet for Dog {
8 fn hello(&self) {
9 println!("woof!");
10 }
11}
12
13fn main() {
14 let d = Dog;
15 d.hello();
16}
terminal — cargo run
$ cargo run
woof!
What just happened

A `trait` declares a set of method signatures any type can opt in to. An `impl Trait for Type` block supplies the bodies. Call the method on a value with the usual `d.hello()` dot-syntax — the compiler resolves it to `Dog`'s implementation at compile time. There's no inheritance involved: `Dog` is a plain struct that just happens to satisfy the `Greet` contract.

C++ comparison

C++ touchstone: a Rust trait is conceptually a pure-virtual interface or a C++20 concept. The `impl Greet for Dog` block is the equivalent of `class Dog : public Greet` plus method definitions — but Rust splits the type definition and the interface conformance into separate blocks, so `Dog` doesn't need to know about `Greet` at the point it's defined.

Rust concept demo

Move Semantics in Rust

src/main.rs
1fn take_ownership(s: String) {
2 println!("inside: {s}");
3}
4
5fn main() {
6 let s = String::from("hello");
7 take_ownership(s);
8 println!("outside: {s}");
9}
terminal — cargo build
$ cargo build
error[E0382]: borrow of moved value: `s`
--> src/main.rs:8:24
|
6 | let s = String::from("hello");
| - move occurs because `s` has type `String`,
| which does not implement the `Copy` trait
7 | take_ownership(s);
| - value moved here
8 | println!("outside: {s}");
| ^ value borrowed here after move
What the compiler is telling you

Passing a value by-value to a function **moves** it. Once `s` is moved into `take_ownership`, the binding `s` in `main` is invalidated — using it on the next line is a compile error. Notice this is the *same* mechanism as `let s2 = s;` — function parameters are just bindings, and binding to a non-`Copy` value moves rather than copies.

C++ comparison

C++ touchstone: by default C++ would copy `s` into the parameter (calling `std::string`'s copy constructor) and `s` would still be usable. To get Rust's behaviour you'd explicitly write `take_ownership(std::move(s))`, after which the C++ standard says `s` is in a "valid but unspecified state" — usable, but its contents are anyone's guess. Rust just removes the binding from the type system, so there is no zombie state to worry about.