The error Interface
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 52 of 107.
In Go, errors are handled through a built-in interface called error. This interface is remarkably simple—it requires just one method:
type error interface {
Error() string
}Any type that implements an Error() method returning a string automatically satisfies the error interface. This is Go's interface system at work—no explicit declaration needed.
The most common way to create errors is using the errors package:
import "errors"
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}Go functions typically return an error as their last return value. When no error occurs, you return nil. The caller then checks if the error is non-nil before proceeding:
result, err := Divide(10, 0)
if err != nil {
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result)You can also use fmt.Errorf to create formatted error messages:
func GetUser(id int) (string, error) {
if id <= 0 {
return "", fmt.Errorf("invalid user id: %d", id)
}
return "Alice", nil
}This pattern of returning errors rather than throwing exceptions is fundamental to Go. It makes error handling explicit and forces you to consider what happens when things go wrong—a key aspect of writing robust, object-oriented code.
Challenge
EasyLet's build a bank account system that demonstrates Go's error handling pattern. You'll create a simple account that validates operations and returns meaningful errors when things go wrong.
You'll organize your code across two files:
account.go: Create aBankAccountstruct with an exportedOwnerfield (string) and an unexportedbalancefield (float64). Implement the following:NewBankAccount(owner string, initialDeposit float64) (*BankAccount, error)- a constructor that returns an error with the message"initial deposit must be positive"if the initial deposit is zero or negative. Otherwise, create and return the account.Deposit(amount float64) error- adds money to the balance. Return an error with message"deposit amount must be positive"if the amount is zero or negative.Withdraw(amount float64) error- removes money from the balance. Return an error with message"withdrawal amount must be positive"if the amount is zero or negative, or"insufficient funds"if the balance is less than the withdrawal amount.Balance() float64- returns the current balance
main.go: Read account details and transaction amounts from input. Attempt to create an account and perform operations. After each operation, print either the success message or the error message.
The following inputs will be provided:
- Line 1: Owner name
- Line 2: Initial deposit amount
- Line 3: Deposit amount
- Line 4: Withdrawal amount
For each operation, print the result:
- After account creation:
Account created for [Owner] with balance: $[balance]orError: [error message] - After deposit:
Deposited successfully. New balance: $[balance]orError: [error message] - After withdrawal:
Withdrew successfully. New balance: $[balance]orError: [error message]
Format all balance values with two decimal places. If account creation fails, skip the remaining operations.
For example, given Alice, 100, 50, and 200, your output should be:
Account created for Alice with balance: $100.00
Deposited successfully. New balance: $150.00
Error: insufficient fundsAnd given Bob, -50, 25, and 10, your output should be:
Error: initial deposit must be positiveCheat sheet
Go handles errors through the built-in error interface, which requires a single method:
type error interface {
Error() string
}Create errors using the errors package:
import "errors"
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}Functions return errors as the last return value. Return nil when no error occurs:
result, err := Divide(10, 0)
if err != nil {
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result)Use fmt.Errorf for formatted error messages:
func GetUser(id int) (string, error) {
if id <= 0 {
return "", fmt.Errorf("invalid user id: %d", id)
}
return "Alice", nil
}Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read owner name
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Read initial deposit
initialDepositStr, _ := reader.ReadString('\n')
initialDeposit, _ := strconv.ParseFloat(strings.TrimSpace(initialDepositStr), 64)
// Read deposit amount
depositStr, _ := reader.ReadString('\n')
depositAmount, _ := strconv.ParseFloat(strings.TrimSpace(depositStr), 64)
// Read withdrawal amount
withdrawStr, _ := reader.ReadString('\n')
withdrawAmount, _ := strconv.ParseFloat(strings.TrimSpace(withdrawStr), 64)
// TODO: Create a new bank account using NewBankAccount
// If there's an error, print "Error: [error message]" and return
// If successful, print "Account created for [Owner] with balance: $[balance]"
// TODO: Attempt to deposit the deposit amount
// If there's an error, print "Error: [error message]"
// If successful, print "Deposited successfully. New balance: $[balance]"
// TODO: Attempt to withdraw the withdrawal amount
// If there's an error, print "Error: [error message]"
// If successful, print "Withdrew successfully. New balance: $[balance]"
// Remember to format balance values with two decimal places using fmt.Printf("%.2f", balance)
_ = ownerName
_ = initialDeposit
_ = depositAmount
_ = withdrawAmount
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool