BasicsImpCodeStory_Functions.html
copyright © James Fawcett
Revised: 05/11/2026
5.0 Prologue
A function names a reusable block of computation. It takes inputs (parameters), performs
work, and optionally returns a result. Functions are the primary unit of abstraction in
imperative languages and the foundation on which all larger structures - methods,
closures, coroutines - are built.
5.1 Declaration
Function syntax varies but the core elements are consistent: a name, a parameter list,
a return type, and a body.
// Rust
fn add(a: i32, b: i32) -> i32 {
a + b // last expression is the return value (no semicolon)
}
// C++
int add(int a, int b) {
return a + b;
}
// C#
int Add(int a, int b) {
return a + b;
}
// Python
def add(a: int, b: int) -> int:
return a + b
In Rust, the body is a block expression. If the last statement has no semicolon it
is implicitly returned. An explicit return statement is used only for
early exit.
5.2 Parameters and Return Types
Pass by value copies the argument. Pass by reference
lets the function observe or modify the caller’s data without copying.
// Rust: &T is shared reference, &mut T is exclusive mutable reference
fn read(s: &String) { /* cannot modify s */ }
fn modify(s: &mut String) { s.push('!'); }
// C++: const ref (read-only), ref (mutable), value (copy)
void read(const std::string& s) {}
void modify(std::string& s) { s += '!'; }
// C#: ref and out parameters
void Increment(ref int x) { x++; }
Default parameters let callers omit trailing arguments (C++, C#, Python).
Rust does not have default parameters; builder patterns or structs with defaults are
idiomatic alternatives.
Multiple return values are supported via tuples in all four languages.
Rust also uses Result<T, E> and Option<T> to encode
fallible and optional results in the type system.
Variadic functions accept a variable number of arguments. C++ uses
templates or C-style ...; Python uses *args and
**kwargs; C# uses params. Rust has no built-in variadic
syntax except for macros.
5.3 Overloading
Function overloading allows multiple functions with the same name
but different parameter types. The compiler selects the correct version at the call site.
C++ and C# support overloading directly. Python does not - a later definition
replaces an earlier one with the same name; functools.singledispatch
provides limited dispatch by type. Rust does not support overloading on free functions
but achieves similar results through trait methods with different receiver types or
generic type parameters.
Overloading resolution in C++ is complex: the compiler ranks candidates by conversion
cost and selects the best match, which can surprise when implicit conversions are
involved.
5.4 Recursion
A recursive function calls itself, directly or through a chain of calls. Each call
pushes a new stack frame; deep recursion risks stack overflow. The base case halts
the recursion.
// Rust
fn factorial(n: u64) -> u64 {
if n == 0 { 1 } else { n * factorial(n - 1) }
}
// Tail-recursive form (optimizer may eliminate stack frame)
fn fact_tail(n: u64, acc: u64) -> u64 {
if n == 0 { acc } else { fact_tail(n - 1, n * acc) }
}
Tail-call optimization (TCO) converts a tail-recursive call into a
loop, avoiding stack growth. C++ compilers often apply it as an optimization but do
not guarantee it. Rust does not guarantee TCO either. Python explicitly does not
implement TCO. C# has no TCO guarantee. When stack depth matters, convert recursion
to an explicit loop or use an iterative algorithm.
Mutual recursion occurs when two or more functions call each other.
In Rust, mutual recursion between free functions works as-is because all functions in
a module are visible to each other regardless of declaration order.
5.5 Higher-Order Functions
A higher-order function takes one or more functions as arguments or
returns a function as its result. They are the foundation of functional-style
composition.
// Rust: map, filter, fold on iterators
let squares: Vec<i32> = (1..=5).map(|x| x * x).collect();
let evens: Vec<i32> = (1..=10).filter(|x| x % 2 == 0).collect();
let sum: i32 = (1..=5).fold(0, |acc, x| acc + x);
// C++: std::transform, std::copy_if, std::accumulate
std::transform(v.begin(), v.end(), out.begin(), [](int x){ return x*x; });
// C#: LINQ
var squares = Enumerable.Range(1,5).Select(x => x * x);
var evens = Enumerable.Range(1,10).Where(x => x % 2 == 0);
// Python
squares = list(map(lambda x: x*x, range(1, 6)))
evens = list(filter(lambda x: x % 2 == 0, range(1, 11)))
total = sum(range(1, 6))
Functions that accept or return other functions rely on closures for capturing context.
The next chapter covers closures in detail.
5.6 Epilogue
Functions decompose programs into named, testable units. Overloading, default parameters,
and higher-order forms add flexibility without sacrificing clarity. The next chapter
extends functions with captured state in the form of lambdas and closures.
5.7 References
Rust Functions - The Book
C++ Functions - cppreference
C# Methods - Microsoft
Python Function Definitions