Go vs Rust – A Practical Look at Error Handling Philosophies
In the world of systems and backend programming, Go and Rust stand out as two modern languages with radically different philosophies. One aims for simplicity and pragmatism, the other for safety and precision. A common battleground where their differences become especially clear is error handling. If you’ve ever debated which language “feels” better to work with, a simple division function that avoids divide-by-zero errors can reveal a lot.
Go’s Approach: Errors as Values
Go treats errors like just another return value. It doesn’t have exceptions or built-in pattern matching. Instead, error handling is kept minimal, explicit, and readable.
func div(x, y float64) (float64, error) {
if y == 0 {
return 0, errors.New("division by zero")
}
return x / y, nil
}
This function returns a tuple: a result and an error. If an error occurred, you handle it like this:
if z, err := div(x, y); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", z)
}
The strength of Go’s model lies in how lightweight it is. No custom types, no traits or interfaces unless you need them. Most developers can reason about error flows quickly without needing additional abstraction.
But of course, that comes with trade-offs.
Rust’s Approach: Type-Driven Error Safety
Rust enforces error handling through the type system. A function that can fail must return a Result<T, E>
. That forces developers to acknowledge and explicitly handle the possibility of failure.
fn div(x: f64, y: f64) -> Result<f64, DivError> {
if y == 0.0 {
Err(DivError)
} else {
Ok(x / y)
}
}
This requires defining an error type:
#[derive(Debug)]
struct DivError;
impl fmt::Display for DivError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "division by zero")
}
}
impl std::error::Error for DivError {}
And then using it with a match
statement:
match div(x, y) {
Ok(z) => println!("Result: {}", z),
Err(e) => eprintln!("Error: {}", e),
}
Rust makes sure you never forget to handle the error case, which means fewer runtime surprises. The compiler is your partner, ensuring you're not skipping essential checks.
However, it’s more verbose, especially for trivial tasks. While this verbosity can be mitigated with libraries like thiserror
or anyhow
, it’s still more upfront work than in Go.
Other Key Differences
1. Propagation and ergonomics
- Go: Manual error propagation using
if err != nil
. - Rust:
?
operator for elegant error propagation.
Rust:
fn safe_div(x: f64, y: f64) -> Result<f64, DivError> {
let result = div(x, y)?;
Ok(result)
}
Go has no such operator—you always write the error check.
2. Custom error types
- Go: You usually don’t create custom error types unless necessary.
- Rust: You often define enums for richer error context, especially in larger codebases.
3. Stack traces and debugging
- Go: Lacks native stack traces for errors unless you wrap or log them yourself.
- Rust: Full
Debug
andDisplay
implementations give you rich diagnostics automatically.
4. Safety philosophy
- Rust leans toward preventing bugs at compile time.
- Go accepts that some things may fail at runtime and prefers to keep the language simpler.
Which Is Better?
That’s the wrong question. The right question is: Which one is better for your context?
Choose Go if:
- You want rapid development and ease of use.
- You prioritize clear, linear code and team velocity.
- Your project doesn’t demand complex error modeling.
Choose Rust if:
- You value compile-time guarantees and correctness.
- Your system is performance-sensitive or safety-critical.
- You want structured error propagation and rich diagnostics.
Finally
The comparison of a division function in Go vs Rust isn't just syntax — it's philosophy. Go says: "Keep it simple and trust the developer." Rust says: "Be safe and trust the compiler."
Both are right — just in different ways.
So instead of arguing which one is better, recognize what each one gives you. Whether you choose Go’s pragmatism or Rust’s precision, you're making a decision based on your needs, not ideology. And that, truly, should settle the debate.
Comments ()