Operators & Control Flow

Course 1 · Ch 3
Operators & Control Flow
C didn't have a real boolean type until 1999 — and even then, only sort of

Chapter 2 showed the standard being deliberately vague about integer sizes. This chapter shows the same looseness applied to something even more fundamental — what counts as "true."

Arithmetic, Comparison & Logical Operators

The familiar set — + - * / %, == != < > <= >=, && || ! — behaves mostly as expected, with one early trap: dividing two integers truncates, it doesn't round.

int result = 5 / 2; // 2, not 2.5 — integer division truncates toward zero float exact = 5.0 / 2; // 2.5 — one operand being a float forces float division

No True Boolean Before C99

This is the real subject of the chapter. Original C had no boolean type at all. Truth was just an int: 0 means false, any nonzero value means true — including negative numbers. Every if, every while, every logical expression in classic C evaluates to a plain integer, not a distinct boolean value the way Rust's bool always has been.

int is_valid = (5 > 3); // is_valid is an int holding 1, not a bool holding true

<stdbool.h> in Modern C

C99 added bool, true, and false — but even now, they're not a genuinely new primitive the way Rust's bool is. Under the hood, bool is a macro for _Bool (an integer type that can only hold 0 or 1), and true/false are just 1 and 0 in disguise.

#include <stdbool.h> bool is_valid = true;

if / else

Because truth is just "nonzero," C allows any expression as a condition — not just a genuine comparison.

if (x) { // valid: means "if x != 0" // ... }

This flexibility hides a classic, still-common bug: writing = (assignment) where == (comparison) was meant. if (x = 5) compiles cleanly, assigns 5 to x, and the condition is then "is 5 nonzero" — always true.

switch

Cases fall through by default — execution continues into the next case unless an explicit break stops it. Rust's match arms never fall through; each one is fully self-contained.

switch (day) { case 1: printf("Monday\n"); // no break — falls through into case 2! case 2: printf("Tuesday\n"); break; }
ConceptRustC
Boolean typebool — a real primitive, always existed_Bool/bool since C99 — really an int in disguise
Condition requirementmust be a genuine boolany nonzero expression works
match/switch fallthroughnever — each arm is isolatedfalls through by default without break
Compile with warnings on
The = vs == mistake is common enough that -Wall flags it directly. Compiling with warnings enabled from day one catches a real class of bugs the language itself won't stop you from writing.
Forgetting break is a genuinely dangerous default
Unlike Rust's match, C's switch silently falls through into the next case unless every case ends in break — a missing break doesn't warn by default and can execute code that was never meant to run for that case.

Coding Challenges

Challenge 1

Write a program computing 7 / 2 as an int and 7.0 / 2 as a double, printing both. Explain the difference in the output.

📄 View solution
Challenge 2

Write a switch statement over an int day (1-3) that prints a different message for each day, deliberately omitting one break to demonstrate fallthrough, then fix it.

📄 View solution
Challenge 3

Explain why if (x = 5) compiles without error in C, what it actually does, and why the equivalent mistake isn't possible in Rust.

📄 View solution

Chapter 3 Quick Reference

  • Integer division truncates — 5 / 2 is 2, not 2.5
  • Classic C has no boolean type — 0 is false, any nonzero value is true
  • <stdbool.h> (C99+) — bool/true/false, but really _Bool (an int) underneath
  • Any nonzero expression is a valid if condition — not just genuine comparisons
  • = vs == in a condition compiles silently — enable -Wall to catch it
  • switch falls through by default — every case needs an explicit break
  • Next chapter: loops — for/while/do-while and break/continue