Output & Running
Hello, World
<?php
echo "Hello, World!\n"; println!("Hello, World!"); Output uses
println! — the trailing ! marks it a macro, not a function, because it does compile-time checking of the format string. It appends a newline for you, so you do not write \n. There is no <?php tag and no $ on variables.Interpreted vs Compiled
<?php
// PHP is interpreted — run the source directly:
// php script.php
$name = "World";
echo "Hello, {$name}!\n"; // Rust compiles to a native binary with no runtime:
// rustc script.rs && ./script
// Real projects use Cargo, the build tool + package manager:
// cargo run
let name = "World";
println!("Hello, {name}!"); Rust is compiled ahead of time to a self-contained native binary with no interpreter or garbage collector to ship — like C, but memory-safe. Cargo is the standard tool (PHP's Composer plus a build system):
cargo new, cargo build, cargo run, and crates from crates.io.Formatted Output
<?php
$name = "Alice";
$age = 30;
printf("%s is %d\n", $name, $age);
echo "{$name} is {$age}\n"; let name = "Alice";
let age = 30;
println!("{name} is {age}"); // inline captures
println!("{} is {}", name, age); // positional
println!("{:?}", (name, age)); // Debug formatting The format string captures variables inline (
{name}), much like PHP's "{$name}", or takes positional arguments with empty {}. The {:?} form is Debug output — a developer-facing representation of almost any value, akin to var_export.Variables & Mutability
let & Immutability
<?php
$count = 42;
$count = 43; // PHP variables are always mutable
echo $count, "\n"; let count = 42;
// count = 43; // compile error — bindings are immutable by default
let mut total = 42;
total = 43; // OK — 'mut' makes it mutable
println!("{total}"); The biggest habit change: a
let binding is immutable by default. To reassign, you must opt in with let mut. This is the opposite of PHP, where every variable is freely mutable, and it pushes you toward code where what can change is explicit.Scalar Types
<?php
$integer = 7; // int
$float = 3.5; // float
$flag = true; // bool
$letter = "A"; // string (no char type)
echo gettype($integer), " ", gettype($float), "\n"; let integer: i32 = 7; // 32-bit signed integer
let float: f64 = 3.5; // 64-bit float
let flag: bool = true;
let letter: char = 'A'; // a single Unicode scalar
println!("{integer} {float} {flag} {letter}"); Rust has a family of sized numeric types —
i32, i64, u8, usize, f32, f64 — instead of PHP's single int and float. It also has a distinct char type for one Unicode scalar (single quotes), separate from strings; PHP has no character type.Shadowing
<?php
$value = "42";
$value = (int) $value; // reuse the name, new value (still mutable var)
echo $value + 1, "\n"; // 43 let value = "42";
let value: i32 = value.parse().unwrap(); // shadow: new binding, new type
println!("{}", value + 1); // 43 Rust lets you shadow a name by declaring
let again, even changing the type (here &str to i32). Unlike PHP's reassignment, each let creates a fresh immutable binding, which is the idiomatic way to transform a value through stages without a mutable variable.Strings
String vs &str
<?php
// PHP has one string type:
$greeting = "Hello";
$greeting .= ", World"; // mutate in place
echo $greeting, "\n"; let literal: &str = "Hello"; // a borrowed string slice
let mut owned: String = String::from(literal);
owned.push_str(", World"); // grow the owned, heap String
println!("{owned}"); Rust splits strings into two types:
&str, a borrowed view (often a literal), and String, a growable, heap-owned buffer. You build and mutate a String; you pass around &str to read. PHP's single mutable string covers both roles, so this distinction is new and central.Common Methods
<?php
$text = " Hello, World ";
echo strtoupper(trim($text)), "\n"; // HELLO, WORLD
echo str_replace("World", "Rust", "Hello World"), "\n";
echo str_contains("Hello", "ell") ? "yes\n" : "no\n"; let text = " Hello, World ";
println!("{}", text.trim().to_uppercase()); // HELLO, WORLD
println!("{}", "Hello World".replace("World", "Rust"));
println!("{}", "Hello".contains("ell")); // true String slices have chainable methods (
text.trim().to_uppercase()) replacing the free functions strtoupper(trim($text)). Note these return new String values rather than mutating in place — Rust's immutability-by-default extends to strings.Building Strings
<?php
$name = "Alice";
$age = 30;
$line = sprintf("%s (%d)", $name, $age);
echo $line, "\n"; // Alice (30) let name = "Alice";
let age = 30;
let line = format!("{name} ({age})"); // returns a String
println!("{line}"); // Alice (30) The
format! macro is println!'s sibling that returns a String instead of printing — the counterpart to PHP's sprintf, but with the same inline-capture and placeholder syntax as println!.Arrays vs Vec & HashMap
One Array vs Several Types
<?php
// PHP's single array is both list and map:
$list = [1, 2, 3];
$map = ["name" => "Alice", "age" => "30"];
echo $list[0], " ", $map["name"], "\n"; use std::collections::HashMap;
let list: Vec<i32> = vec![1, 2, 3]; // growable vector
let mut map: HashMap<&str, &str> = HashMap::new();
map.insert("name", "Alice");
println!("{} {}", list[0], map["name"]); PHP's one array type splits into a
Vec<T> (a growable, homogeneously typed list) and a HashMap<K, V> for key-value data. Both are generic over their element types, fixed at compile time, so a Vec<i32> can never hold a string. There are also fixed-size arrays [i32; 3].Vec Operations
<?php
$numbers = [3, 1, 2];
$numbers[] = 4; // append
sort($numbers);
echo count($numbers), "\n"; // 4
echo in_array(2, $numbers) ? "yes\n" : "no\n"; let mut numbers = vec![3, 1, 2];
numbers.push(4);
numbers.sort();
println!("{}", numbers.len()); // 4
println!("{}", numbers.contains(&2)); // true A
Vec exposes methods — push, sort, len, contains — replacing array_push, sort, count, and in_array. Note contains(&2) takes a reference, and the vector must be mut to push or sort.Map, Filter & Collect
<?php
$numbers = [1, 2, 3, 4, 5];
$result = array_map(
fn($n) => $n * 2,
array_filter($numbers, fn($n) => $n % 2 === 1)
);
print_r(array_values($result)); // 2, 6, 10 let numbers = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = numbers
.iter()
.filter(|&n| n % 2 == 1)
.map(|n| n * 2)
.collect();
println!("{:?}", result); // [2, 6, 10] Rust's iterator adapters
filter and map chain left-to-right and are lazy — nothing runs until collect() consumes them into a concrete Vec. This is more readable than PHP's inside-out array_map(array_filter(...)), and the closures capture variables like PHP arrow functions.Iterating a Map
<?php
$scores = ["alice" => 90, "bob" => 85];
foreach ($scores as $name => $score) {
echo "{$name}: {$score}\n";
} use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("alice", 90);
scores.insert("bob", 85);
let mut total = 0;
for (name, score) in &scores {
total += score;
let _ = name; // (order is not guaranteed)
}
println!("total: {total}"); // 175 Iterating a map with
for (name, score) in &scores destructures each entry, like foreach ... as $name => $score. The & borrows the map so you can keep using it afterward. Unlike a PHP array, a HashMap has no guaranteed order, so this example sums rather than printing per-entry.Control Flow
if as an Expression
<?php
$score = 85;
$grade = $score >= 90 ? "A" : ($score >= 80 ? "B" : "C");
echo $grade, "\n"; // B let score = 85;
let grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else {
"C"
};
println!("{grade}"); // B In Rust
if is an expression that yields a value, so it replaces the ternary operator (which Rust does not have): the last expression in each branch becomes the result. Conditions take no parentheses, and every branch must produce the same type.Loops & Ranges
<?php
for ($index = 0; $index < 3; $index++) {
echo $index, " ";
}
echo "\n";
foreach (["a", "b", "c"] as $letter) {
echo $letter, " ";
}
echo "\n"; for index in 0..3 { // 0..3 is a half-open range
print!("{index} ");
}
println!();
for letter in ["a", "b", "c"] {
print!("{letter} ");
}
println!(); Rust has no C-style three-part
for; you iterate over a range (0..3, which stops before 3) or any iterable. The foreach-over-values form maps to for letter in [...]. Rust also has loop { } for an infinite loop and while.match vs match
<?php
$status = 404;
$message = match (true) {
$status < 300 => "ok",
$status < 400 => "redirect",
$status < 500 => "client error",
default => "server error",
};
echo $message, "\n"; // client error let status = 404;
let message = match status {
s if s < 300 => "ok",
s if s < 400 => "redirect",
s if s < 500 => "client error",
_ => "server error",
};
println!("{message}"); // client error Both languages have a value-returning
match, and PHP's was partly inspired by Rust's. Rust's is more powerful: arms are patterns, if guards refine them, the wildcard is _ instead of default, and the compiler enforces that the match is exhaustive.Ownership & Borrowing
Ownership & Move
<?php
// PHP just reference-counts and copies arrays as values; you never
// think about who "owns" a value:
$original = [1, 2, 3];
$copy = $original; // independent copy (arrays are value types)
$copy[] = 4;
echo count($original), "\n"; // 3 let original = String::from("hello");
let moved = original; // ownership MOVES to 'moved'
// println!("{original}"); // compile error — original was moved
println!("{moved}"); // hello
let cloned = moved.clone(); // explicit deep copy
println!("{moved} {cloned}"); Rust's defining idea: every value has a single owner, and assigning a heap value moves ownership, invalidating the old binding. This is how Rust frees memory deterministically with no garbage collector. To keep both, you
clone() explicitly. PHP's reference-counted runtime hides all of this from you.Borrowing & References
<?php
function describe(array $items): string {
return count($items) . " items";
}
$list = [1, 2, 3];
echo describe($list), "\n"; // 3 items
echo count($list), "\n"; // 3 — still usable fn describe(items: &Vec<i32>) -> String {
format!("{} items", items.len()) // borrows, does not take ownership
}
let list = vec![1, 2, 3];
println!("{}", describe(&list)); // 3 items
println!("{}", list.len()); // 3 — still usable To use a value without taking ownership, you borrow it with
& — a reference. The function reads &Vec<i32> and the caller keeps the original. The borrow checker enforces the rules (many readers OR one writer at a time), preventing data races at compile time — guarantees PHP's value-copy model never needs to make.Mutable References
<?php
function appendOne(array &$items): void { // PHP: pass by reference with &
$items[] = 1;
}
$list = [];
appendOne($list);
appendOne($list);
echo count($list), "\n"; // 2 fn append_one(items: &mut Vec<i32>) {
items.push(1);
}
let mut list = Vec::new();
append_one(&mut list);
append_one(&mut list);
println!("{}", list.len()); // 2 A
&mut reference lets a function mutate the caller's value — the parallel of PHP's &$items by-reference parameter. The difference is that Rust permits only one mutable borrow at a time (and none while shared borrows exist), which is what makes the no-data-race guarantee possible.Functions & Closures
Defining Functions
<?php
function add(int $first, int $second): int {
return $first + $second;
}
echo add(2, 3), "\n"; // 5 fn add(first: i32, second: i32) -> i32 {
first + second // last expression is the return value — no 'return'
}
println!("{}", add(2, 3)); // 5 Functions use
fn, with parameter types after each name and the return type after a -> arrow. The killer convenience: the final expression is the return value — drop the semicolon and omit return. An explicit return still works for early exits.Closures
<?php
$multiplier = 3;
$scale = fn($value) => $value * $multiplier; // auto-captures
echo $scale(5), "\n"; // 15 let multiplier = 3;
let scale = |value| value * multiplier; // captures automatically
println!("{}", scale(5)); // 15 A Rust closure uses pipe-delimited parameters
|value| ... and captures surrounding variables automatically, like PHP's arrow function fn() =>. The compiler infers how it captures (by reference, or by move with the move keyword) based on what the body does.Returning Tuples
<?php
function minMax(array $numbers): array {
return [min($numbers), max($numbers)];
}
[$low, $high] = minMax([3, 7, 1, 9]);
echo "{$low} {$high}\n"; // 1 9 fn min_max(numbers: &[i32]) -> (i32, i32) {
let low = *numbers.iter().min().unwrap();
let high = *numbers.iter().max().unwrap();
(low, high)
}
let (low, high) = min_max(&[3, 7, 1, 9]);
println!("{low} {high}"); // 1 9 Rust returns multiple values as a typed tuple
(i32, i32), which the caller destructures with let (low, high) — much like PHP's "return an array, then destructure", but each position has its own static type. The &[i32] parameter is a slice, a borrowed view over any array or Vec.Structs & Methods
Structs vs Classes
<?php
class Point {
public function __construct(public int $x, public int $y) {}
public function distance(): float {
return sqrt($this->x ** 2 + $this->y ** 2);
}
}
$point = new Point(3, 4);
echo $point->distance(), "\n"; // 5 struct Point {
x: i32,
y: i32,
}
impl Point {
fn distance(&self) -> f64 {
((self.x.pow(2) + self.y.pow(2)) as f64).sqrt()
}
}
let point = Point { x: 3, y: 4 };
println!("{}", point.distance()); // 5 Rust separates data (the
struct) from behavior (an impl block of methods). Methods take &self explicitly — the parallel of $this — and you construct with Point { x: 3, y: 4 }, no new keyword. There is no class; this split is the Rust way.Constructors (Associated Functions)
<?php
class Circle {
public function __construct(public float $radius) {}
public static function unit(): self {
return new self(1.0);
}
}
echo Circle::unit()->radius, "\n"; // 1 struct Circle {
radius: f64,
}
impl Circle {
fn new(radius: f64) -> Self {
Circle { radius }
}
fn unit() -> Self {
Circle::new(1.0)
}
}
println!("{}", Circle::unit().radius); // 1 Rust has no built-in constructor; the convention is an associated function named
new (no self parameter), called as Circle::new(...) — the rough equivalent of a PHP static factory. Self is shorthand for the type being implemented, like PHP's self.Derived Behavior
<?php
// PHP gives objects value-comparison and var_export for free:
class Point {
public function __construct(public int $x, public int $y) {}
}
$a = new Point(1, 2);
$b = new Point(1, 2);
echo $a == $b ? "equal\n" : "different\n"; // equal
var_dump($a); #[derive(Debug, PartialEq, Clone)]
struct Point {
x: i32,
y: i32,
}
let a = Point { x: 1, y: 2 };
let b = Point { x: 1, y: 2 };
println!("{}", a == b); // true (from PartialEq)
println!("{:?}", a); // Point { x: 1, y: 2 } (from Debug) Rust generates common behavior with a
#[derive(...)] attribute: Debug enables {:?} printing, PartialEq enables ==, and Clone enables .clone(). PHP grants object value-comparison and var_dump automatically; Rust makes you opt in, which keeps the costs explicit.Enums & Pattern Matching
Enums
<?php
enum Direction: string {
case North = "north";
case South = "south";
public function opposite(): self {
return match ($this) {
Direction::North => Direction::South,
Direction::South => Direction::North,
};
}
}
echo Direction::North->opposite()->value, "\n"; // south #[derive(Debug)]
enum Direction {
North,
South,
}
impl Direction {
fn opposite(&self) -> Direction {
match self {
Direction::North => Direction::South,
Direction::South => Direction::North,
}
}
}
println!("{:?}", Direction::North.opposite()); // South Both languages have rich enums with methods. The syntax rhymes: variants are listed, and methods live in an
impl block matching on self. Rust enums go further still — each variant can carry its own typed data, which makes them the foundation of Option and Result below.Option Instead of null
<?php
function findUser(int $id): ?string {
return $id === 1 ? "Alice" : null;
}
$name = findUser(2);
echo $name ?? "not found", "\n"; // not found fn find_user(id: i32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
let name = find_user(2);
println!("{}", name.unwrap_or(String::from("not found"))); Rust has no null. A value that might be absent is an
Option<T> — either Some(value) or None — and the compiler forces you to handle the None case before using the value. unwrap_or supplies a default, the parallel of PHP's ??, but you can never accidentally dereference a null.Destructuring with if let
<?php
$data = ["name" => "Alice"];
if (isset($data["name"])) {
$name = $data["name"];
echo "Hello, {$name}\n";
} else {
echo "No name\n";
} let found: Option<&str> = Some("Alice");
if let Some(name) = found {
println!("Hello, {name}"); // binds name only if Some
} else {
println!("No name");
} The
if let construct matches one pattern and binds its contents in a single line — here it unwraps Some(name) only when the Option is present. It is the concise alternative to a full match when you care about just one case, with no PHP equivalent beyond isset-and-assign.Traits vs Interfaces
Traits as Interfaces
<?php
interface Shape {
public function area(): float;
}
class Square implements Shape {
public function __construct(private float $side) {}
public function area(): float { return $this->side ** 2; }
}
echo (new Square(3))->area(), "\n"; // 9 trait Shape {
fn area(&self) -> f64;
}
struct Square {
side: f64,
}
impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
println!("{}", Square { side: 3.0 }.area()); // 9 A trait is Rust's interface: it declares method signatures, and a type provides them with
impl Trait for Type — separating the contract from the data. Unlike PHP interfaces, traits can supply default method bodies and can be implemented for types you do not own, which is far more flexible.Default Methods & Shared Behavior
<?php
// PHP shares default behavior with traits (a different meaning of "trait"):
trait Greetable {
public function greet(): string {
return "Hello, " . $this->name();
}
abstract public function name(): string;
}
class Person {
use Greetable;
public function __construct(private string $name) {}
public function name(): string { return $this->name; }
}
echo (new Person("Alice"))->greet(), "\n"; trait Greetable {
fn name(&self) -> String;
fn greet(&self) -> String { // default implementation
format!("Hello, {}", self.name())
}
}
struct Person {
name: String,
}
impl Greetable for Person {
fn name(&self) -> String {
self.name.clone()
}
}
println!("{}", Person { name: String::from("Alice") }.greet()); Confusingly, PHP also has a feature called "traits", but it is a code-reuse mixin. Rust's trait is closer to an interface with optional default methods:
greet here has a body that any implementor inherits for free, while name must be provided — combining PHP's interface and trait concepts into one.Generics
Generic Functions
<?php
// PHP has no real generics; you accept mixed and document with docblocks:
/** @template T @param T[] $items @return T */
function firstItem(array $items): mixed {
return $items[0];
}
echo firstItem([10, 20, 30]), "\n"; // 10 fn first_item<T: Copy>(items: &[T]) -> T {
items[0]
}
println!("{}", first_item(&[10, 20, 30])); // 10
println!("{}", first_item(&['a', 'b'])); // a Generics are a real, compile-time feature:
<T> is a type parameter, here bounded by T: Copy so the value can be returned by copy. Rust monomorphizes generics — it generates a specialized version per concrete type — so there is no runtime cost. PHP can only approximate this with @template docblocks for static analysers.Generic Structs
<?php
// A PHP container just holds mixed values:
class Wrapper {
public function __construct(public mixed $value) {}
}
$wrapped = new Wrapper(42);
echo $wrapped->value, "\n"; // 42 struct Wrapper<T> {
value: T,
}
impl<T: std::fmt::Display> Wrapper<T> {
fn show(&self) {
println!("{}", self.value);
}
}
Wrapper { value: 42 }.show(); // 42
Wrapper { value: "hi" }.show(); // hi Structs can be generic too:
Wrapper<T> holds a value of any type, and the impl block can require trait bounds like T: Display so the body may print it. This gives you type-safe containers — what the standard library's Vec<T> and Option<T> are built on — with no PHP equivalent.Error Handling
Result Instead of Exceptions
<?php
function parseAmount(string $input): int {
if (!is_numeric($input)) {
throw new InvalidArgumentException("not a number: {$input}");
}
return (int) $input;
}
try {
echo parseAmount("42"), "\n"; // 42
parseAmount("abc");
} catch (InvalidArgumentException $error) {
echo "Caught: ", $error->getMessage(), "\n";
} fn parse_amount(input: &str) -> Result<i32, String> {
input.parse::<i32>().map_err(|_| format!("not a number: {input}"))
}
match parse_amount("42") {
Ok(value) => println!("{value}"), // 42
Err(message) => println!("Caught: {message}"),
}
match parse_amount("abc") {
Ok(value) => println!("{value}"),
Err(message) => println!("Caught: {message}"),
} Rust has no exceptions. A fallible operation returns a
Result<T, E> — either Ok(value) or Err(error) — and the compiler forces you to handle both, so an error can never be silently ignored. This is the opposite of PHP's throw-and-maybe-catch model, where an uncaught exception unwinds at runtime.The ? Operator
<?php
// PHP propagates errors by letting exceptions bubble up:
function readNumber(string $input): int {
return parseValue($input) + 1; // if parseValue throws, it propagates
}
function parseValue(string $input): int {
if (!is_numeric($input)) {
throw new InvalidArgumentException("bad: {$input}");
}
return (int) $input;
}
try {
echo readNumber("41"), "\n"; // 42
} catch (InvalidArgumentException $error) {
echo $error->getMessage(), "\n";
} fn parse_value(input: &str) -> Result<i32, std::num::ParseIntError> {
input.parse::<i32>()
}
fn read_number(input: &str) -> Result<i32, std::num::ParseIntError> {
let value = parse_value(input)?; // ? returns early on Err
Ok(value + 1)
}
match read_number("41") {
Ok(value) => println!("{value}"), // 42
Err(error) => println!("{error}"),
} The
? operator is Rust's ergonomic error propagation: parse_value(input)? unwraps an Ok or returns the Err from the enclosing function immediately. It gives you the "let it bubble up" convenience of PHP exceptions while keeping every error path visible in the type signatures.panic! for Unrecoverable Errors
<?php
// An uncaught PHP exception terminates the script with an error:
function mustBePositive(int $value): int {
if ($value <= 0) {
throw new RuntimeException("expected positive, got {$value}");
}
return $value;
}
echo mustBePositive(5), "\n"; // 5 fn must_be_positive(value: i32) -> i32 {
if value <= 0 {
panic!("expected positive, got {value}");
}
value
}
println!("{}", must_be_positive(5)); // 5 For genuinely unrecoverable bugs,
panic! aborts the current thread with a message, much like an uncaught exception ending a PHP script. The distinction Rust draws is deliberate: use Result for expected, recoverable failures and reserve panic! for "this should never happen" invariants.⚠ Gotchas for PHP Devs
Integer Types & Overflow
<?php
// PHP ints are 64-bit and silently become floats on overflow:
$big = PHP_INT_MAX;
echo $big, "\n";
echo $big + 1, "\n"; // becomes a float (9.2233720368548E+18) let big: i32 = 2_000_000_000;
// big + big would panic in debug builds (overflow) — be explicit instead:
let wrapped = big.wrapping_add(big); // wraps around
let checked = big.checked_add(big); // returns Option (None on overflow)
println!("{wrapped}");
println!("{:?}", checked); // None Rust's integers are fixed-width and typed (
i32, u64, …), and overflow is a real concern: it panics in debug builds rather than silently promoting to a float like PHP. You choose the behavior explicitly with methods like wrapping_add, checked_add, or saturating_add.No Implicit Type Coercion
<?php
// PHP freely juggles types in arithmetic and comparison:
echo "5" + 3, "\n"; // 8 (string coerced to int)
echo 5 . 3, "\n"; // 53 (numbers coerced to string)
var_dump(5 == "5"); // true let count: i64 = 5;
let scaled: f64 = 2.0;
// let bad = count * scaled; // compile error — i64 and f64 don't mix
let mixed = count as f64 * scaled; // explicit cast with 'as'
println!("{mixed}"); // 10 Rust performs no implicit numeric coercion — you cannot even multiply an
i64 by an f64 without an explicit as cast, and there is no string/number juggling at all. This eliminates the "5" + 3 surprises of PHP, at the cost of writing the conversions yourself.There Is No null
<?php
// In PHP any variable can be null, and forgetting to check is a common bug:
function lastChar(?string $text): string {
return $text[strlen($text) - 1]; // crashes if $text is null
}
echo lastChar("hello"), "\n"; // o fn last_char(text: Option<&str>) -> Option<char> {
text?.chars().last() // ? short-circuits to None if absent
}
println!("{:?}", last_char(Some("hello"))); // Some('o')
println!("{:?}", last_char(None)); // None Because Rust has no
null, the "forgot to check for null" bug simply cannot occur — absence is modeled as Option and the compiler refuses to let you use the inner value without handling None. The ? operator works on Option too, making the safe path the short one.The Borrow Checker
<?php
// PHP lets you mutate a collection while iterating it (sometimes buggily):
$numbers = [1, 2, 3];
foreach ($numbers as $number) {
// modifying $numbers here is allowed but error-prone
}
$numbers[] = 4;
echo count($numbers), "\n"; // 4 let mut numbers = vec![1, 2, 3];
// This would be rejected — can't push while a borrow from iteration is live:
// for n in &numbers { numbers.push(*n); } // compile error
for n in &numbers {
print!("{n} "); // read-only borrow, fine
}
println!();
numbers.push(4); // OK — the borrow has ended
println!("{}", numbers.len()); // 4 The borrow checker enforces that you cannot mutate a collection while an iteration borrow over it is still alive — a class of aliasing bug PHP allows at runtime. Fighting the borrow checker is the usual early-Rust experience; it is the compiler proving your memory and aliasing are sound before the program ever runs.