3 min read

Pointers & Errors in Go with Tests

Table of Contents

Hey there! 👋 Ready to level up your Go skills?

Today we’re diving into pointers and errors - two concepts that might seem scary at first but are actually your best friends in Go programming.

Let’s break it down with some Bitcoin examples (because who doesn’t like pretend cryptocurrency?).

Your Digital Wallet Adventure

Imagine you’re building the next big fintech app (Go is huge in finance, by the way). You need a secure Bitcoin wallet.

Here’s how we’ll approach it:

type Wallet struct {
    balance Bitcoin // Our secret money stash
}

// Deposit adds funds to your wallet
func (w *Wallet) Deposit(amount Bitcoin) {
    w.balance += amount
}

// Check your balance
func (w *Wallet) Balance() Bitcoin {
    return w.balance
}

Wait a minute - what’s with those asterisks? That’s our first pointer lesson!

Pointers: The “Don’t Copy Me” Sign

In Go, when you pass things around, it makes copies by default.

Imagine giving someone a photocopy of your wallet - they can scribble on it all they want, but your real wallet stays the same.

Pointers (those *Wallet things) are like saying “Here’s my actual wallet address, make changes to the real thing.”

// Without pointer (makes a copy)
func (w Wallet) Deposit(amount Bitcoin) // 🚫 Changes won't stick

// With pointer (uses the real deal)
func (w *Wallet) Deposit(amount Bitcoin) // ✅ Changes the actual balance

Making Bitcoin User-Friendly

We could use plain numbers, but where’s the fun in that?

Let’s make our own type:

type Bitcoin int

func (b Bitcoin) String() string {
    return fmt.Sprintf("%d BTC", b)
}

Now our wallet shows “10 BTC” instead of just “10” - much cooler!

Handling Errors Like a Pro

What if someone tries to withdraw more than they have? We don’t want negative Bitcoin! Here’s how we handle that:

var ErrInsufficientFunds = errors.New("cannot withdraw, insufficient funds")

func (w *Wallet) Withdraw(amount Bitcoin) error {
    if amount > w.balance {
        return ErrInsufficientFunds // Nice clear error
    }
    w.balance -= amount
    return nil // nil means no error
}

Testing this is straightforward:

t.Run("withdraw too much", func(t *testing.T) {
    wallet := Wallet{Bitcoin(20)}
    err := wallet.Withdraw(Bitcoin(100))

    assertError(t, err, ErrInsufficientFunds)
    assertBalance(t, wallet, Bitcoin(20))
})

Key Takeaways

  1. Pointers - Use when you need to modify the original value
  2. Errors - Return them when things go wrong, always check them
  3. Custom Types - Make your code more readable and domain-specific
  4. Nil Checks - Always verify pointers aren’t nil before using them

Remember: The Go compiler is like a helpful friend who yells at you when you’re about to do something dangerous with pointers. Listen to it!

Next Up: Maps

Support my open source work 🚀

Let's Connect

I'm open to discussing new opportunities. Feel free to connect with me or send me an email at [email protected]