What Does It Mean for a Function to Be First-Class in C?
From the earlier reference collection. Read the current software blog.
C functions can be selected at runtime by passing and storing pointers to them. A small callback illustrates what that enables and where C’s syntax differs from languages that treat functions as ordinary values.
A function pointer is a callable value
Calling a function by name is the familiar case: sort_items(items, count) asks the program to run sort_items. C also lets code refer to a function through a pointer. A pointer can be assigned, passed to another function, and stored for later use. The receiving code can call through it without knowing which particular function was chosen.
This is a useful, limited sense of “first-class.” C supports functions as indirect call targets, but a function is not an object that can be copied with arbitrary attached state. A function pointer has a type that describes the function’s parameter and return types; incompatible function-pointer types cannot safely be substituted.
A callback selects behavior
Suppose a program has a list of readings and wants to apply a caller-selected rule to each one. It can define a callback type and accept a pointer of that type:
typedef int (*ReadingRule)(int value); and int count_matches(const int *values, size_t n, ReadingRule rule) { int count = 0; for (size_t i = 0; i < n; ++i) if (rule(values[i])) ++count; return count; }
A separate function such as int is_high(int value) { return value > 80; } can be passed as count_matches(readings, n, is_high). The counting loop remains unchanged when a different rule is needed. The caller supplies the policy; the loop supplies the traversal.
Store the choice when timing matters
The same pointer can live in a structure or array, making it possible to associate operations with entries. A user-interface component might store a function to call when activated. A parser might keep a table mapping token kinds to handler functions. In both cases, storing a pointer postpones the choice of behavior until runtime.
C does not automatically capture surrounding variables in a callback. If the rule needs a threshold that can vary, pass that context separately—for example, provide both a callback pointer and a void *context argument. The callback can interpret the context using a type the caller and callee agree on. This explicit pairing replaces the implicit captured environment offered by some languages.
Keep the type and lifetime clear
A callback API should document its signature, when the callback runs, and whether the context remains valid for that whole period. A synchronous loop can often use a pointer to a local context because the call finishes before the function returns. An asynchronous operation may retain it, so a short-lived local variable would be unsafe.
Function pointers therefore offer flexible dispatch without requiring a language runtime or object system. Their constraints are visible: matching types, explicit context, and careful lifetime management. Those constraints make the design compact, but they also belong in the interface contract.