Function Pointers

Course 2 · Ch 7
Function Pointers
What C++ classes and Rust's dyn Trait do automatically, built here entirely by hand

c2-1 named this chapter directly: "one way C approximates method-like dispatch without a language feature for it at all." Course 2 closes by actually building that mechanism — the same one every object-oriented language's runtime dispatch is quietly built from underneath.

Function Pointer Syntax

int (*operation)(int, int);

A pointer to a function taking two ints and returning int. The parentheses around *operation are mandatory — without them, this parses as something else entirely, a genuine syntax trap worth memorizing rather than re-deriving each time.

Assigning & Calling Through a Function Pointer

int add(int a, int b) { return a + b; } int (*operation)(int, int) = add; // a function name decays to a pointer to itself int result = operation(2, 3); // calling through the pointer looks identical

A function name decaying to a pointer to itself is genuinely the same phenomenon as array-to-pointer decay from c1-6, just applied to functions instead of arrays.

Callbacks

Passing a function pointer so another function can call it later — a real, common C idiom.

void apply_to_all(int *arr, int n, void (*fn)(int)) { for (int i = 0; i < n; i++) { fn(arr[i]); } }

A Simple vtable-Style Dispatch Pattern

The real payoff: store a function pointer alongside data, and let different instances point at different implementations.

typedef struct { float radius; float (*area)(void *self); } Circle; float circle_area(void *self) { Circle *c = (Circle *)self; return 3.14159f * c->radius * c->radius; } Circle c = {5.0f, circle_area}; float a = c.area(&c); // dispatches to circle_area at runtime

A second shape type populates its own struct's function pointer with a different implementation entirely — calling shape->area(shape) dispatches to the correct one, purely because the pointer was set to point there.

What C++ and Rust Formalize

This is not just an analogy — it's the literal mechanism. A C++ class with virtual methods, and Rust's own dyn Trait, both work by storing a table of function pointers alongside the data, dispatched through one level of indirection — exactly what was just built by hand above. The difference is who manages the table: here, the programmer wires it up manually and must get it right; in C++ and Rust, the compiler generates the table automatically and guarantees it's always fully and correctly populated.

ConceptRust dyn TraitC function pointer struct
Who builds the dispatch tablethe compiler, automaticallythe programmer, by hand
Guaranteed fully populated?yes — enforced at compile timeno — nothing checks it at all
Calling an unset entrynot possible — compile errorundefined behavior — a NULL or garbage call
A genuinely real technique, not just a teaching exercise
This hand-rolled pattern is exactly how real, large C codebases implement polymorphism — the Linux kernel's own driver interfaces are built from structs of function pointers just like this one.
Nothing checks that the function pointer was set correctly
Forgetting to populate a struct's function pointer, or assigning the wrong function to it, produces undefined behavior the moment it's called — a NULL call, or a call through a mismatched signature, with no compiler check whatsoever. This is precisely what Rust's dyn Trait and C++'s virtual mechanism guarantee away entirely.

Coding Challenges

Challenge 1

Write two functions, add(int, int) and subtract(int, int), and a single function pointer variable that can be pointed at either one. Call it once through each, printing both results.

📄 View solution
Challenge 2

Write a function apply_to_all(int *arr, int n, void (*fn)(int)) that calls fn on every element of an array, and a print_doubled(int) function to pass as the callback that prints each value doubled.

📄 View solution
Challenge 3

Extend the chapter's Circle vtable-style example with a second shape (e.g. a Rectangle) that has its own area function, both sharing a common calling pattern. Then explain what specifically would happen if a third shape's function pointer were left unset (NULL) and its area were called.

📄 View solution

Chapter 7 Quick Reference — Course 2 Complete

  • return_type (*name)(param_types); — the parentheses around *name are mandatory
  • A function name decays to a pointer to itself — the same phenomenon as c1-6's array decay
  • Callbacks — passing a function pointer so another function can call it later
  • A struct of data plus a function pointer is a hand-rolled vtable — real dispatch, written manually
  • C++'s virtual and Rust's dyn Trait are the exact same mechanism, compiler-managed and guaranteed correct — this chapter's version is neither
  • Course 2 complete. Course 3 covers bit manipulation, hand-built data structures, concurrency, undefined behavior in full depth, debugging tooling, and a real capstone CLI project