C#
GO

C# to Go

10 lessons

Progress0%
1Introduction2Type System3Classes to Structs4Interfaces5Error Handling6Async to Goroutines7Generics8LINQ to Slices9Testing10Packages and Modules
All Mirror Courses
C#
GO
Error Handling
MirrorLesson 5 of 10
Lesson 5

Error Handling

Error Handling

Introduction

In this lesson, you'll learn about error handling in Go. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
C#
From C#:

In C#, you're familiar with error handling.

GO
In Go:

Go has its own approach to error handling, which we'll explore step by step.

The Go Way

Let's see how Go handles this concept. Here's a typical example:

GO
Go Example
package main

import (
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    data, err := os.ReadFile("config.txt")
    if err != nil {
        fmt.Println("File not found:", err)
        return
    }

    value, err := strconv.Atoi(strings.TrimSpace(string(data)))
    if err != nil {
        fmt.Println("Invalid format:", err)
        return
    }

    fmt.Println("Value:", value)
}

Comparing to C#

Here's how you might have written similar code in C#:

C#
C# (What you know)
try {
    var data = File.ReadAllText("config.txt");
    var value = int.Parse(data.Trim());
    Console.WriteLine($"Value: {value}");
}
catch (FileNotFoundException ex) {
    Console.WriteLine($"File not found: {ex.Message}");
}
catch (FormatException ex) {
    Console.WriteLine($"Invalid format: {ex.Message}");
}
Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

GO
In Go:

Go has no exceptions — errors are returned as values (the last return value by convention)

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

GO
In Go:

if err != nil is the idiomatic error check — everywhere

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

GO
In Go:

No try/catch/finally — handle errors inline at each call site

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

GO
In Go:

The error type is a built-in interface with a single Error() string method

Mirror Card
C#
From C#:

You may be used to different syntax or behavior.

GO
In Go:

Go's defer keyword replaces finally for cleanup

Step-by-Step Breakdown

1. Error as Return Value

Go functions that can fail return (result, error) as their last return value. You must check the error every time — the compiler won't force you, but ignoring errors is considered a serious bug.

C#
C#
int Parse(string s) {
    return int.Parse(s); // throws on failure
}
GO
Go
import "strconv"

// Returns value AND error
n, err := strconv.Atoi("42")
if err != nil {
    // handle the error
    fmt.Println("parse error:", err)
    return
}
// n is safe to use here
Common Pitfall
Go does not warn if you ignore the error return value. Use _ to explicitly discard: n, _ := strconv.Atoi(s). Only do this when you are certain the error is impossible.

2. Creating and Wrapping Errors

Use fmt.Errorf to create errors with context. The %w verb wraps an error so callers can inspect the original cause.

C#
C#
throw new InvalidOperationException(
    $"Failed to load config: {innerEx.Message}", innerEx);
GO
Go
import "fmt"

// Create a simple error
err := fmt.Errorf("invalid age: %d", age)

// Wrap an existing error (preserves cause)
wrapped := fmt.Errorf("loading config: %w", originalErr)

// Unwrap to inspect cause
import "errors"
var target *os.PathError
if errors.As(wrapped, &target) {
    fmt.Println("path:", target.Path)
}
Rule of Thumb
Always add context when wrapping errors: fmt.Errorf("doing X: %w", err). This builds a readable error chain like a stack trace.

3. defer for Cleanup

defer schedules a function call for when the surrounding function returns — equivalent to finally or using/IDisposable.

C#
C#
using var file = File.OpenRead("data.txt");
// file.Dispose() called automatically at end of using block
GO
Go
f, err := os.Open("data.txt")
if err != nil {
    return err
}
defer f.Close()  // runs when function returns, regardless of how

// Continue using f...
Rule of Thumb
Put defer immediately after the successful resource acquisition. Multiple defers run in LIFO order (last in, first out).

4. panic and recover

Go has panic for truly unrecoverable errors and recover to catch them — similar to C# Exception but reserved for programming errors, not expected failures.

C#
C#
// C# — throw for expected and unexpected errors
throw new InvalidOperationException("unexpected state");
GO
Go
// panic — for programmer errors, not expected conditions
func divide(a, b int) int {
    if b == 0 {
        panic("division by zero — this is a bug")
    }
    return a / b
}

// recover — catch panics in a deferred function
defer func() {
    if r := recover(); r != nil {
        fmt.Println("recovered from panic:", r)
    }
}()
Common Pitfall
Do not use panic for normal error handling. It is equivalent to an unhandled exception — use return err for expected failure conditions.

Common Mistakes

When coming from C#, developers often make these mistakes:

  • Go has no exceptions — errors are returned as values (the last return value by convention)
  • if err != nil is the idiomatic error check — everywhere
  • No try/catch/finally — handle errors inline at each call site
Common Pitfall
Don't assume Go works exactly like C#. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • No exceptions — errors are return values, checked with if err != nil
  • Wrap errors with fmt.Errorf("context: %w", err) for traceable chains
  • defer replaces finally and using/IDisposable for cleanup
  • panic/recover exist for unrecoverable programmer errors only
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your C# code in Go to practice these concepts.
PreviousNext