the return code versus errno
/ errno: "ERR-no" /
Picture a vending machine that, when something goes wrong, lights up a single red 'FAILED' lamp and separately prints a tiny receipt saying exactly what failed - 'no change', 'item stuck', 'out of stock'. The red lamp is quick to read and tells you yes-or-no. The receipt carries the detail. C's classic error-reporting style works just like this: a function's return value is the lamp, and a separate global variable named errno is the receipt.
Here is how the convention actually works. Many C library and system-call functions signal failure through their return value: open() returns -1 on failure, malloc() returns NULL, fopen() returns NULL, read() returns -1. That return value usually only tells you that it failed, not why. To learn why, you read errno - a special variable the failing call sets to a numeric error code, such as ENOENT (no such file) or EACCES (permission denied). The discipline is: first check the return value to see if it failed at all; only if it failed is errno meaningful. A critical, easy-to-miss rule is that a successful call does not reset errno, and unrelated later calls may overwrite it, so you must read errno immediately after a call you already know failed - never check errno alone to decide whether something failed.
Why it matters: this two-part split (a return value that says yes/no, plus errno that says which kind) is the dominant error-reporting style in C and POSIX, and you will see it everywhere. It is powerful but fragile: errno is shared state (per-thread in modern systems, which avoids one whole class of bugs), and people forget that it is only valid right after a known failure. Newer languages replace this pattern with richer return types - a Result or an Option - precisely because a separate global error variable is so easy to misuse.
Correct use: FILE *f = fopen("data.txt", "r"); if (f == NULL) { perror("fopen"); /* perror() turns errno into 'fopen: No such file or directory' */ } - the return value (NULL) is checked first; errno is read (via perror) only because we already know it failed.
Check the return value to know IF it failed; read errno to know WHY.
Never test errno without first confirming failure via the return value. A successful call may leave errno holding a stale value from an earlier failed call; errno is only meaningful immediately after a call you already know failed.