An error is a value, not an explosion
Files are the first place your program meets the outside world. And the outside world is unreliable: the file may not exist, the disk may be full, permissions may say no. So Go applies everywhere the rule you already know from strconv.Atoi:
data, err := os.ReadFile("items.json")
if err != nil {
// something failed — we REACT here
}
In Go an error is not an explosion — it is a plain returned value. A function does not crash your program; it calmly says "that failed, here is why" and leaves the decision to you. Every file operation returns an error, and you must check every one.
At the lowest level a file lives in three steps: open → use → close. An open file is a held resource, which is why Go has defer — "do this when the function ends, no matter what happens":
f, err := os.Create("items.json")
if err != nil {
return err
}
defer f.Close() // the close is scheduled IMMEDIATELY after opening
Gotcha. Two sins that "work" on your machine and break the program on everyone else's:
- Ignoring the error:
data, _ := os.ReadFile(...)— the file is missing,datais empty, and the program keeps pretending everything is fine, then breaks later, far from the real cause. Check the error where it is born.- Forgetting
defer f.Close(): an unclosed file — unwritten data can sit in a buffer (the file ends up empty or half-written), and the OS keeps the file locked. The habit: wroteos.Createoros.Open— the next line after the error check isdefer f.Close().
Good news: in the next step you will see that for everyday work Go gives you a pair of functions that handle the opening and closing for you — but checking the error stays YOUR job, always.