Functions

Course 1 · Ch 5
Functions
Why C makes you announce a function before you're allowed to call it

Chapter 1 briefly used main without explaining what makes a function "known" to the compiler. This chapter covers that directly — and sets up the exact problem pointers exist to solve next.

Function Syntax

int add(int a, int b) { return a + b; }

Declarations vs. Definitions

A declaration (or prototype) tells the compiler a function exists and its exact signature — return type, name, parameter types — without providing a body. A definition provides the actual body. C compiles top to bottom in a single pass: calling a function before the compiler has seen either its declaration or its definition is an error. Rust, by contrast, allows calling any function defined anywhere in the same module, in any order — no forward declaration needed.

int add(int a, int b); // declaration — no body int main() { printf("%d\n", add(2, 3)); // legal — the compiler already saw the declaration return 0; } int add(int a, int b) { // the actual definition, further down return a + b; }

Header Files

A .h file conventionally holds declarations, shared across multiple .c files via #include — the mechanism that lets one source file call a function defined in a completely different one. Course 2's Multi-File Projects & Makefiles chapter covers this in full; for now, know that a header is essentially a shared table of "these functions exist, here are their signatures."

Pass-by-Value by Default

Function arguments are copied into the function — modifying a parameter inside the function has no effect on the caller's original variable. This is genuinely worth a three-way comparison: Go is also pass-by-value by default, exactly like C — a rare point of agreement between the two. Rust is the outlier, requiring an explicit choice: pass by value (moves or copies), or pass a reference (&T/&mut T) deliberately.

void try_to_change(int x) { x = 99; // only changes the local copy } int main() { int n = 5; try_to_change(n); // n is still 5 here }

Simulating Pass-by-Reference With Pointers

To let a function genuinely modify the caller's variable, C requires passing a pointer explicitly — the caller's address, not its value. This is exactly what the very next chapter covers in depth; for now, just recognize that "I need the function to change my variable" is the specific problem pointers solve.

ConceptCGoRust
Default argument passingby value (copy)by value (copy)by value — moves or copies
Passing a referenceexplicit pointer (&var)explicit pointer (&var)explicit reference (&var / &mut var)
Forward declaration needed?yes — top-to-bottom compilationnono
Prototype early, or define before use
Either declare every function used across multiple files in a shared header, or simply order definitions so nothing is called before the compiler has seen it. Both solve the same top-to-bottom compilation constraint.
Legacy C could silently assume a missing function returns int
Pre-C99 compilers, faced with a call to a genuinely undeclared function, would silently assume it returned int and accept the code — a real, dangerous default mostly removed in modern C, but worth recognizing if reading older, pre-standard codebases.

Coding Challenges

Challenge 1

Write a function multiply(int a, int b) that returns the product of its two arguments, declared with a prototype above main and defined below it. Call it from main and print the result.

📄 View solution
Challenge 2

Write a function that attempts to double an int parameter by reassigning it inside the function body. Call it from main with a variable set to 10, then print the variable afterward. Explain the output.

📄 View solution
Challenge 3

Explain why Rust doesn't require forward declarations for functions defined later in the same file, while C does — what does this reveal about how each language's compiler actually processes source code?

📄 View solution

Chapter 5 Quick Reference

  • Declaration/prototype — signature only, no body; definition — the actual body
  • C compiles top to bottom — a function must be declared or defined before it's called
  • Header files (.h) — shared declarations across multiple .c files, via #include
  • Arguments are passed by value by default — a real point of agreement with Go, unlike Rust's explicit reference choice
  • Modifying a parameter inside a function never affects the caller's variable — pointers (next chapter) are how C works around this
  • Next chapter: arrays — fixed size, no bounds checking, and the first real "manual control" moment