Code Story

Chapter #5 – Functions

declaration, parameters, overloading, recursion, higher-order functions

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