If you've been working with software long enough, you know that "perfect code" is a myth. Code fails. Hardware flakes out. The network goes on holiday just when you need it most. In Skink, we don't pretend these things don't happen—we face them head-on. That's what error handling is for. It's not pessimism; it's engineering.
The error type: It's not a failure, it's a notification
When a function might run into a problem, we don't just throw up our hands and crash. We return an error type alongside our data. Think of it as a signal to the caller: "Hey, I couldn't finish what you asked, and here's why."
module main
import "std/errors"
fn divide(a: float, b: float) -> (float, error) {
if b == 0.0 {
// We return a result and an error object
return 0.0, errors.New("division by zero")
}
// nil is the "everything is fine" state
return a / b, nil
}
Checking errors: Don't ignore the warning signs
Whenever you see a function that returns an error, treat it like a flashing dashboard warning light. Check it, or be prepared for the consequences (usually a spectacular crash).
fn main() -> int {
result, err := divide(10.0, 2.0)
if err != nil {
// If we got an error, handle it gracefully
print("Failed: {err.String()}")
return 1
}
print("Result: {result}")
return 0
}
The ? operator: The "let someone else deal with it" shortcut
Sometimes you don't want to handle an error right where it happens. You might want to pass it up the chain to a function that's better equipped to deal with it (or just let the main loop log it and move on). The ? operator is your friend here. It says: "If this returned an error, return it from this function right now. Otherwise, keep going."
fn compute() -> (float, error) {
// If divide fails, compute returns immediately with the error
x := divide(10.0, 2.0)?
y := divide(x, 0.0)?
return x + y, nil
}
It’s a massive time-saver, and it keeps your code from becoming a nesting-doll pile of if err != nil blocks. Just remember: it only works in functions that return an error themselves.
Exercises
- Square Root Logic: Write a sqrt(x: float) -> (float, error) function. It should return an error if the input is less than zero. Math isn't optional, after all.
- The Chain: Create a calculate function that calls your sqrt function and then uses the result in a divide operation. Use ? to handle the errors.
- Friendly Feedback: Instead of just printing the error string, print a user-friendly message that explains why the operation failed. Your future self (or a user) will thank you.
Next
When you're ready to make your code walk and chew gum at the same time, we'll talk about Concurrency.