File I/O
Every program so far has lived entirely in memory. This chapter opens the door to the filesystem — and introduces a resource-management discipline that should already feel familiar from c2-2's malloc/free pairing.
Opening a File
File Modes
The mode string controls both direction and format: "r" read, "w" write (truncating), "a" append — each with a b variant ("rb", "wb", "ab") for binary.
Text Mode vs. Binary Mode
Text mode may translate line endings depending on the platform — on Windows, \n can be translated to/from \r\n on read/write. Binary mode transfers bytes exactly as-is, with no translation at all. This is genuinely platform-dependent behavior, worth knowing explicitly rather than discovering by accident. Rust's std::fs has no equivalent mode distinction at the language level — it's byte-oriented by default, with no automatic translation to opt out of.
Reading & Writing Text
fgets takes a maximum buffer size, exactly the bounds-checking discipline c1-8 covered. Its predecessor, gets(), took no size argument at all — it was so fundamentally unfixable that C11 removed it from the standard entirely.
Reading & Writing Binary Data
fread/fwrite operate on raw bytes: a pointer, the size of each element, and how many elements. Both return the actual number of items transferred — which can genuinely be less than requested, and isn't automatically an error condition to ignore.
Closing a File
Flushes any buffered writes and releases the underlying OS file handle. Forgetting it is the same class of mistake as c2-2's forgotten free — a resource acquired manually that must be released manually, just a file descriptor instead of heap memory.
| Concept | Rust | C |
|---|---|---|
| Text/binary mode distinction | none — byte-oriented by default | explicit — "r" vs "rb", genuinely platform-dependent |
| Closing a file | automatic — Drop closes it when the handle goes out of scope | manual — fclose must be called explicitly |
| Unbounded line reading | not available — read_line always takes a growable buffer | gets() existed, then was removed entirely in C11 |
fopen returns NULL — the same discipline c2-2 established for malloc. A missing file, wrong permissions, or a full disk are all real, common causes; never assume the file opened successfully.
gets() read a line with no way to specify a maximum length at all — genuinely, unfixably unsafe, no matter how carefully called. C11 didn't just discourage it; it removed it from the standard outright. Use fgets, always.
Coding Challenges
Write a program that opens a file for writing, writes three lines of text to it with fprintf, closes it, then reopens the same file for reading and prints each line back out with fgets.
📄 View solutionWrite a program that writes an array of 5 ints to a file in binary mode with fwrite, then reads them back into a different array with fread, printing both arrays to confirm they match.
📄 View solutionExplain specifically why gets() could never be made safe with better documentation or careful usage alone, and why fgets's extra parameter is what actually fixes the underlying problem.
📄 View solutionChapter 6 Quick Reference
fopen(name, mode)— returnsNULLon failure, must always be checked"r"/"w"/"a"plus abvariant for binary — text mode may translate line endings, platform-dependentlyfgets— bounded, safe;gets()— unbounded, removed entirely from C11fread/fwrite— return the actual items transferred, which can be less than requestedfclose— manual, exactly likefree; forgetting it leaks a file descriptor- Next chapter: Function Pointers — syntax, callbacks, and a vtable-style dispatch pattern