Adding Structure to your Rust program using Functions
The slogan for my new book ‘Rust - the good parts!’ says learning to program can be as easy as ‘A’, ‘B’, ‘C’; as “one”, “two”, “three”; as “doh”, “ray”, “mi”; and as “data”, “control” and “structure”. In the last two articles, the concepts of data and sequence control in Rust were explored. We are now in Episode 3, where the structure bit gets unpacked. Functions are used to add robust structure to programs. By the end of this episode you should be able to look at a block of code and recognise exactly where it can be refactored; which piece deserves its own name, input arguments, and output return type; and which pieces belong together under one roof as a module. That is the real promise of this post: not just knowing what a function is, but genuinely appreciating, understanding, and using functions to give your programs the kind of structure that survives being read down the line months from now. Along the way we also consider the two ways Rust lets you own a resource outright or share it safely between several owners, Box<T> and Rc<T>, ideas that sit naturally alongside functions and modules once code stops being one long block and starts being organised into pieces that hand data to one another deliberately. Let’s dive in.
From One File to Many
Previous episodes had considered Sharpe Ratios, a concept from Quantitative Finance. Episode 2 left us with a working comparison of three trading strategies, momentum, mean reversion, and buy and hold, but every piece of that program lives in one long main function: the struct definition, the Sharpe ratio calculation, the categorisation logic, and the loop that finds the winner, all stacked on top of each other in a single file. That’s fine for a program you write in one sitting and never touch again, but the moment you want to reuse the Sharpe ratio calculation somewhere else, test it in isolation, or simply read the code again in six months, a single long function starts to work against you rather than for you.
This episode is about giving that code proper shape. We’ll split the strategy logic into focused functions, each doing one clearly named thing, and move them into their own module so main.rs stays short and readable. Along the way we’ll meet a genuinely important idea that sits right alongside functions and modules: what it means to own a resource properly in Rust, whether that resource is shared market data, a large dataset, or anything else your program is responsible for cleaning up after itself.
You can find the previous articles in this series here:
- Episode 0: Falling in Love, Making a Case for the Rust Programming Language
- Episode 1: Introducing Rust Datatypes
- Episode 2: Control & Structures in Rust
What Episode 3 Covers
- Functions: parameters, return types, and the two ways to return a value
- Borrowing a parameter with
&, and why that’s how data gets passed around without giving up ownership of it - The unit type
(), for functions that exist purely for their side effects - Nested functions, for helpers that only make sense in one place
- Modules,
puband private visibility, and organising code across files - Adding a crate from
crates.iowithcargo add Box<T>, for putting a resource on the heap and owning it outrightRc<T>, for sharing a resource between several owners without duplicating it- Refactoring Episode 2’s Sharpe ratio comparison into a proper module
Functions: parameters and return types
Functions are the holy grail of structured programming and ubiquitous to programming languages. They are literally units of functionality in your program, a packaged bunch of operations you can call and reuse from various places within your program and codebase. Packaging a set of statements in your program, giving it a name optionally having inputs and outputs, than calling it from various locations in your programs, that is what functions are. When a function is called, your program pauses and waits for it to complete. If the function defines a value it returns, that value will be passed back to the program at the point of entry once the function exits. If rust defines a let binding at the point of call, the return value will be bound to that binding definition.
Every Rust function starts with fn, a name, parentheses holding its parameters, an optional return type after ->, and a body in curly braces:
fn annualised_return(daily_mean: f64, trading_days: usize) -> f64 {
daily_mean * trading_days as f64
}
fn main() {
let projected = annualised_return(0.0012, 252);
println!("{projected:.4}");
}
annualised_return takes two parameters, daily_mean and trading_days, each with an explicit type annotation. That’s not optional in Rust the way it sometimes is for let bindings: every parameter needs a stated type, because a function’s signature is meant to be self-documenting, readable and understandable without having to go look at how it’s called. The return type, f64, comes after ->. Inside the body, daily_mean * trading_days as f64 is the last expression, and because it has no trailing semicolon, Rust treats it as the value the function produces and returns it automatically. If you omit -> entirely, a function returns (), Rust’s “nothing” type, which we’ll come back to shortly.
A function can return any type at all, and its parameters can mix types freely:
fn is_profitable(sharpe: f64) -> bool {
sharpe > 0.0
}
fn describe_history(returns: &[f64]) -> &'static str {
if returns.len() < 2 {
return "too little data to judge";
}
"enough data to analyse"
}
fn main() {
println!("{}", is_profitable(1.24)); // true
println!("{}", describe_history(&[0.001])); // too little data to judge
}
is_profitable returns the result of a comparison expression directly: sharpe > 0.0 evaluates to a bool, and that becomes the function’s return value with no extra ceremony. describe_history shows Rust’s other way of returning a value, the return keyword, used here to exit early the moment we know there isn’t enough data to bother analysing. The final line, "enough data to analyse", is reached only when the early return didn’t fire, and it returns by expression, the same way annualised_return did. Idiomatic Rust favours the expression style for a function’s final value, since it keeps the shape of the function obvious at a glance, and reserves return for genuinely early exits like validation checks at the top of a function. That &'static str return type is worth a quick note: the apostrophe marks a lifetime, a concept we’ll properly unpack in a later episode, but for now you can read &'static str as “a reference to text that lives for the entire program,” which is exactly what a string literal like "too little data to judge" already does.
Parameters that borrow: &T
Look back at describe_history above: its parameter is returns: &[f64], not returns: [f64; 6] or returns: Vec<f64>. That leading & means the function borrows its argument instead of owning it. Ownership in Rust is exclusive: whoever creates a value owns it, and at any given moment exactly one owner exists for it. If describe_history took returns: Vec<f64> by value instead, calling it would move the caller’s vector into the function, and the caller would lose access to it the instant the call was made, an awkward trade for a function that only ever wants to look at the data, not keep it.
fn total_return(returns: &[f64]) -> f64 {
returns.iter().sum()
}
fn main() {
let history = vec![0.0041, -0.0018, 0.0026];
let total = total_return(&history); // borrowed, not moved
println!("{:.4} across {} days", total, history.len()); // history still usable here
}
&history at the call site creates the reference; &[f64] in the signature is the type that reference has. Because total_return only ever reads through that reference, history is still fully valid on the very next line, printing its own length after the call has already returned. This is the rule you’ll see behind every & for the rest of this episode: a parameter is borrowed with & when the function only needs to read it, and taken by value, with no & at all, when the function needs to own it outright or store it somewhere that outlives the call. It’s also the direct answer to a question worth asking up front: later in this episode, portfolio::compare(&strategies, &ctx) passes both its arguments by reference precisely so that main keeps owning strategies and ctx after the call returns, rather than handing them away to a function that only ever needs to read them.
Functions that return nothing
Some functions exist purely for what they do, not for what they produce. Printing a report is a good example:
fn print_report(name: &str, sharpe: f64) {
println!("{name}: {sharpe:.3}");
}
fn main() {
print_report("Momentum", 1.24); // Momentum: 1.240
}
print_report has no -> in its signature, so it implicitly returns (), the unit type. You could technically capture that return value in a let binding, but it carries no information at all, and in practice you almost never do; you call a function like this purely for its side effect, printing to the terminal, and never look at what it handed back.
Nested functions
Rust lets you define a function inside another function, visible only within that enclosing scope:
fn average_return(returns: &[f64]) -> f64 {
fn total(returns: &[f64]) -> f64 {
returns.iter().sum()
}
total(returns) / returns.len() as f64
}
fn main() {
let returns = [0.0041, -0.0018, 0.0026, 0.0003, -0.0007, 0.0052];
println!("{:.5}", average_return(&returns));
}
total is defined inside average_return and is completely invisible to the rest of the program; nothing outside average_return needs to know it exists, because it’s purely an implementation detail of how the average is computed. This is a judgement call you’ll make often: when a helper only ever makes sense in one specific context, nesting it documents that intent directly in the code. If two or more functions end up needing the same helper, that’s your signal to move it out to the enclosing scope instead.
Organising code with modules
As a program grows past a handful of functions, you need a way to say “these all belong together.” A module is a named container for functions, types, and constants, declared with the mod keyword:
mod portfolio {
pub struct Strategy {
pub name: String,
pub returns: [f64; 6],
}
pub fn sharpe_ratio(strategy: &Strategy, risk_free_daily: f64, trading_days: usize) -> f64 {
let mean_return = mean(&strategy.returns) - risk_free_daily;
let variance = strategy
.returns
.iter()
.map(|r| (r - mean_return).powi(2))
.sum::<f64>()
/ strategy.returns.len() as f64;
(mean_return / variance.sqrt()) * (trading_days as f64).sqrt()
}
fn mean(returns: &[f64]) -> f64 {
returns.iter().sum::<f64>() / returns.len() as f64
}
}
fn main() {
let m = portfolio::Strategy {
name: "Momentum".to_string(),
returns: [0.0041, -0.0018, 0.0026, 0.0003, -0.0007, 0.0052],
};
println!("{:.3}", portfolio::sharpe_ratio(&m, 0.05 / 252.0, 252));
}
mod portfolio { ... } defines a module named portfolio, and everything inside it, the Strategy struct, sharpe_ratio, and the private mean helper, lives under that name. From outside, you reach into a module with ::, the same operator you’ve already been using with std::cmp::Ordering; it always means “look inside this container.” By default, everything inside a module is private, visible only from within that module. pub in front of an item makes it visible from outside [1]. Notice that mean has no pub: it’s an implementation detail of how sharpe_ratio does its job, exactly like the nested total function earlier, just organised at the module level instead of inside a single function. Strategy’s fields needed pub too, individually, since a pub struct doesn’t automatically make its fields public; each field’s visibility is its own decision.
Typing portfolio::sharpe_ratio(...) everywhere gets tiresome, so use lets you bring a path into scope:
use portfolio::Strategy;
let m = Strategy { name: "Momentum".to_string(), returns: [/* ... */] };
This is purely a shorthand; the module’s structure and visibility rules underneath are completely unchanged.
Inline modules like the one above are fine for small examples, but real projects split modules into their own files [2]. When Rust encounters mod portfolio;, with a semicolon and no body, it looks for src/portfolio.rs and treats that file’s entire contents as the module:
// src/main.rs
mod portfolio;
fn main() {
let m = portfolio::Strategy { /* ... */ };
println!("{:.3}", portfolio::sharpe_ratio(&m, 0.05 / 252.0, 252));
}
// src/portfolio.rs
pub struct Strategy {
pub name: String,
pub returns: [f64; 6],
}
pub fn sharpe_ratio(strategy: &Strategy, risk_free_daily: f64, trading_days: usize) -> f64 {
// ...
}
There’s no mod portfolio { } wrapper inside portfolio.rs itself; the file is the module’s body. Everything marked pub in that file becomes reachable from main.rs via portfolio::.
Bringing in a crate from crates.io
The standard library, std, is always available and never needs adding to Cargo.toml. But sometimes you want something std doesn’t provide, like colour in terminal output. crates.io is the public registry where the Rust community publishes reusable packages, and cargo add is the easiest way to pull one in [5]:
cargo add colored
That edits your Cargo.toml automatically:
[dependencies]
colored = "2"
Now you can colour-code each strategy’s verdict the moment it’s classified:
use colored::Colorize;
fn print_verdict(name: &str, sharpe: f64, category: &str) {
let line = format!("{name}: {sharpe:.3} ({category})");
let coloured = match category {
"poor" => line.red(),
"fair" => line.yellow(),
"good" => line.blue(),
_ => line.green(),
};
println!("{coloured}");
}
use colored::Colorize; brings a trait called Colorize into scope. A trait is essentially a set of methods you can add to an existing type; we’ll cover traits properly in a later episode, but for now it’s enough to know that importing Colorize gives ordinary strings extra methods like .red(), .yellow(), and .green(), which wrap the text in terminal colour codes your terminal knows how to interpret.
Box<T>: owning a resource on the heap
So far, every value we’ve built has lived directly wherever it was created, an f64 on the stack, a Strategy struct sitting inline. Sometimes, though, a resource is genuinely large, or you want to be explicit that a value is being handed off rather than copied around. Box<T> puts a value on the heap and gives you a single, exclusive owner of it [3]:
struct MarketData {
prices: Vec<f64>,
}
fn main() {
let feed = Box::new(MarketData {
prices: vec![101.2, 101.5, 100.9, 102.1],
});
println!("Latest price: {}", feed.prices.last().unwrap());
}
Box::new(...) allocates the MarketData on the heap and hands you back a Box<MarketData> that owns it. Notice that feed.prices.last() works directly, with no need to manually unwrap the box first; Box<T> implements a trait called Deref, which lets it transparently behave like the value it contains for the purposes of field access and method calls. When feed goes out of scope, the box’s destructor runs automatically and the heap memory is freed, exactly the same Drop-driven cleanup you saw with TradeLedger in Episode 2, just applied to a heap allocation instead of a Vec’s internal buffer. If you haven’t met Drop before: it’s the trait Rust runs automatically the instant a value’s owner goes out of scope, giving your type a hook to run cleanup code, close a file, flush a buffer, free memory, without you ever having to remember to call it yourself. Moving a Box moves only the pointer to that heap allocation, not the data itself, so passing a large MarketData around by Box is cheap regardless of how much data it actually holds.
Rc<T>: sharing a resource between several owners
A Box has exactly one owner. But sometimes a resource genuinely needs to be shared: several strategies might all want to read the same market data feed without each one needing its own private copy. Rust’s answer is Rc<T>, short for “reference counted” [4]:
use std::rc::Rc;
fn main() {
let feed = Rc::new(MarketData {
prices: vec![101.2, 101.5, 100.9, 102.1],
});
let momentum_view = Rc::clone(&feed);
let mean_reversion_view = Rc::clone(&feed);
println!("Shared by {} owners", Rc::strong_count(&feed)); // 3
println!("{}", momentum_view.prices.len());
println!("{}", mean_reversion_view.prices.len());
}
Rc::new(...) wraps the MarketData and starts a count at one. Each call to Rc::clone(&feed) doesn’t duplicate the underlying prices vector at all; it simply increments that count and hands back another pointer to the exact same heap allocation. Rc::strong_count(&feed) reports how many owners currently exist, three here: feed, momentum_view, and mean_reversion_view. Only when every one of those owners has gone out of scope, dropping the count to zero, is the underlying MarketData actually freed. This is exactly the deliberate choice Rust asks you to make about any resource-owning type: does it have exactly one owner, expressed with Box or with plain ownership, or does it genuinely need several simultaneous owners, expressed explicitly with Rc? Rust never picks one of these on your behalf; the type you reach for states your intent directly in the code.
Two smaller points round this out. First, there’s no equivalent in Rust of accidentally freeing a heap array with the wrong deallocation function; Box<T> and Vec<T> already know their own size and how to clean themselves up correctly, so the entire category of mismatched allocation and deallocation simply has nothing to attach itself to here. Second, Box::new(...) and Rc::new(...) are each a single, atomic expression: the allocation and the handing-over of ownership happen together, in one step, so there’s no window between “the memory exists” and “something owns it” for anything to go wrong in.
Before reading any further, try this yourself. Take Episode 2’s single main function, the one holding Strategy, sharpe_ratio, category, and the comparison loop all in one place, and split it into a portfolio module of your own: decide which pieces need pub because main.rs calls them directly, and which stay private because they are implementation detail the way mean was earlier in this episode. Then decide whether the market context, the risk-free rate and trading-day count every strategy needs to read, should be owned outright by each strategy or shared between them with Rc<T>. Compare your answer against the refactor below.
Putting it together: the strategy comparison, properly organised
Here’s Episode 2’s comparison, reshaped into a small module with a shared market context:
main.rs is now short and readable: it builds a shared MarketContext, wraps it in an Rc since every strategy needs to read the same risk-free rate and trading-day count, builds the three strategies, and hands both off to portfolio::compare. All the actual calculation, categorisation, and colour logic lives in portfolio.rs, out of sight of anyone just trying to understand what the program does at a glance. Each call to strategy.sharpe_ratio(ctx) borrows the shared Rc<MarketContext> rather than needing its own private copy of the risk-free rate and trading-day count, exactly the sharing Rc exists for.
// src/portfolio.rs
use colored::Colorize;
use std::cmp::Ordering;
use std::rc::Rc;
pub struct MarketContext {
pub risk_free_daily: f64,
pub trading_days: usize,
}
pub struct Strategy {
pub name: String,
pub returns: [f64; 6],
}
impl Strategy {
pub fn new(name: &str, returns: [f64; 6]) -> Self {
Self { name: name.to_string(), returns }
}
pub fn sharpe_ratio(&self, ctx: &Rc<MarketContext>) -> f64 {
let mean = self.returns.iter().sum::<f64>() / self.returns.len() as f64;
let mean = mean - ctx.risk_free_daily;
let variance = self
.returns
.iter()
.map(|r| (r - mean).powi(2))
.sum::<f64>()
/ self.returns.len() as f64;
(mean / variance.sqrt()) * (ctx.trading_days as f64).sqrt()
}
fn category(sharpe: f64) -> &'static str {
match sharpe {
s if s < 0.0 => "poor",
s if s < 1.0 => "fair",
s if s < 2.0 => "good",
_ => "excellent",
}
}
}
pub fn compare(strategies: &[Strategy], ctx: &Rc<MarketContext>) {
let mut best_index = 0;
let mut best_sharpe = f64::MIN;
for (i, strategy) in strategies.iter().enumerate() {
let sharpe = strategy.sharpe_ratio(ctx);
let category = Strategy::category(sharpe);
let line = format!("{:<15} {:>7.3} ({category})", strategy.name, sharpe);
let coloured = match category {
"poor" => line.red(),
"fair" => line.yellow(),
"good" => line.blue(),
_ => line.green(),
};
println!("{coloured}");
if let Some(Ordering::Greater) = sharpe.partial_cmp(&best_sharpe) {
best_sharpe = sharpe;
best_index = i;
}
}
println!("\nBest strategy: {}", strategies[best_index].name);
}
Start with Strategy itself. MarketContext and Strategy are both plain data, a struct with public fields and nothing else, the same shape you saw with the original inline portfolio module earlier in this episode: a named bundle of typed fields, and nothing more, until an impl block gives it behaviour. impl Strategy { ... } is exactly that: it attaches functions to the Strategy type, and inside it, Self is just shorthand for ”Strategy,” so Self { name: ..., returns } and Strategy { name: ..., returns } mean the same thing. Strategy::new is one such function, called an associated function because it belongs to the type itself rather than to any particular Strategy value; you call it with :: on the type, Strategy::new(...), the same pattern used back in Episode 2, rather than with . on something you’d first have to already have. Its whole job is to convert a borrowed &str name into an owned String the struct can hold for as long as it exists.
sharpe_ratio, by contrast, is a method: it takes &self, a borrowed reference to an existing Strategy value, the same & you just met in the parameters section, which lets it read every field of that strategy without taking ownership of it away from wherever it’s stored. It takes one extra parameter too: ctx: &Rc<MarketContext>, a borrow of the shared market context rather than a private copy of it. Inside the method the calculation is unchanged from Episode 2, mean return, shadowed into mean excess return, variance, and the annualised scale, except every reference to the risk-free rate and trading-day count now reaches through ctx instead of a module-level constant. Borrowing ctx here costs nothing extra: every strategy reads the same Rc<MarketContext> pointer, and sharpe_ratio never needs to know, or care, how many other strategies are reading it at the same time.
category stays a private associated function, called as Strategy::category(sharpe) rather than through self, because categorising a Sharpe ratio does not need a whole Strategy in hand, only the number itself; keeping it private and un-exported through pub is the module-level equivalent of the nested total helper from earlier in this episode.
compare is the one function main.rs actually calls, and it is marked pub because it is the module’s whole public interface: everything else, Strategy’s methods included, is reachable only because compare and the public fields of Strategy and MarketContext need to be. Its first parameter, strategies: &[Strategy], borrows a whole slice of strategies rather than taking ownership of the vector main built, for the same reason &self and ctx are borrowed above: compare only needs to read them. Inside it, strategies.iter().enumerate() walks the slice while also counting as it goes, handing back each strategy paired with its position, i, so best_index can remember which one wins without needing to search for it again afterwards. Tracking the best strategy seen so far uses two mut bindings, best_index and best_sharpe, updated as the loop makes its way through the list: sharpe.partial_cmp(&best_sharpe) compares two Sharpe ratios and returns Option<Ordering> rather than a plain Ordering, because a comparison between two floating-point numbers can genuinely fail to produce any order at all if either side happens to be NaN. Matching that result against Some(Ordering::Greater) only updates best_index and best_sharpe when the new Sharpe ratio is strictly and unambiguously larger than anything seen so far; every other outcome, including a comparison that failed to resolve at all, falls through to the next strategy untouched.
// src/main.rs
mod portfolio;
use portfolio::{MarketContext, Strategy};
use std::rc::Rc;
fn main() {
let ctx = Rc::new(MarketContext {
risk_free_daily: 0.05 / 252.0,
trading_days: 252,
});
let strategies = vec![
Strategy::new("Momentum", [0.0041, -0.0018, 0.0026, 0.0003, -0.0007, 0.0052]),
Strategy::new("Mean Reversion", [0.0012, 0.0009, -0.0004, 0.0015, 0.0002, -0.0003]),
Strategy::new("Buy and Hold", [0.0006, 0.0007, 0.0005, 0.0006, 0.0006, 0.0007]),
];
portfolio::compare(&strategies, &ctx);
}
main.rs mirrors that division of labour. Rc::new(MarketContext { ... }) builds the shared context once and wraps it for sharing before a single Strategy exists. vec![Strategy::new(...), ...] builds the three strategies using the vec! macro, which builds a growable Vec<Strategy> already populated with the values you list, the same way vec![0.0041, -0.0018, 0.0026] built the history vector earlier in this episode. The only line that actually crosses the module boundary is portfolio::compare(&strategies, &ctx), and both of its arguments are references: &strategies and &ctx borrow main’s vector and its shared context rather than moving them. That’s the parameter-borrowing rule from earlier in this episode doing real work at the call site: had compare instead taken strategies: Vec<Strategy> by value, this single call would have moved main’s vector into compare and emptied it out of main for good, leaving nothing behind for any later code in main that might want to look at the strategies again. Borrowing means main still owns strategies and ctx after this line runs; compare just gets to look. Every calculation, every match, every coloured println!, stays inside portfolio.rs, invisible to anyone reading main.rs for the first time.
Coming up next
Right now, picking the single best strategy is as far as this program goes, and every strategy still shares one fixed set of market assumptions. A more realistic question isn’t “which one strategy wins” but “how much of my capital should sit in each strategy at once,” since a blend of momentum, mean reversion, and buy and hold can genuinely outperform any single one of them on a risk-adjusted basis. The next episode takes the functions you’ve just learned to write and puts them to work as the objective function and constraints of a real optimisation problem, solved first with nothing but a brute-force search across candidate weightings, in plain, dependency-free Rust. The episode after that swaps the brute-force search for a genuine linear programming solver crate and the full Markowitz machinery, covariance, risk aversion, and the efficient frontier, behind it.
Get the Book
Computer programming doesn’t have to be so hard, does it? It can be as easy as A, B, C. As 1, 2, 3. As “doh,” “ray,” “mi.” As data, control, and structures.
Don’t worry. My new book, Rust - the good parts! has you covered, and guides you through these concepts step by step. You can learn about data and control structures in one week, function structures by week two, and by the end of week four you will have built a complete working system, building a game from scratch.
References
[1] The Rust Programming Language, “Defining Modules to Control Scope and Privacy”, https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
[2] The Rust Programming Language, “Managing Growing Projects with Packages, Crates, and Modules”, https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html
[3] The Rust Standard Library, std::boxed::Box, https://doc.rust-lang.org/std/boxed/struct.Box.html
[4] The Rust Standard Library, std::rc::Rc, https://doc.rust-lang.org/std/rc/struct.Rc.html
[5] The colored crate, https://crates.io/crates/colored
Glossary
Box<T>: a smart pointer that allocates a value on the heap and gives it exactly one owner.- Crate: Rust’s unit of compilation; either a binary crate producing an executable or a library crate producing reusable code, published to
crates.io. Deref: a trait that lets a smart pointer, such asBox<T>orRc<T>, be used as though it were the value it contains, for field access and method calls.- heap: memory allocated dynamically at runtime rather than fixed at compile time, sized however large it currently needs to be; used in this post for values placed there deliberately with
Box::neworRc::newrather than being stored inline wherever they are created. - Module: a named container for functions, types, and constants, declared with
mod, with everything private by default. pub: a visibility modifier that makes an item accessible from outside the module it’s declared in.Rc<T>: a reference-counted smart pointer that allows a value to have several simultaneous owners, freeing the value only once the count reaches zero.'static: a lifetime annotation meaning a reference is valid for the entire running program, most commonly seen on string literals such as"too little data to judge"; covered only by name in this post, with the full mechanics deferred to a later episode.trait: a named set of methods a type can implement, letting unrelated types share common behaviour; used in this post to giveBox<T>andRc<T>theirDerefbehaviour, and to add colour-printing methods like.red()to ordinary strings via thecoloredcrate’sColorizetrait.- Unit type
(): the return type of a function that produces no meaningful value, used implicitly when a function has no->clause.