1. Functions in Go
Functions are the basic building block in Go. Go functions have many unique features compared to other languages.
1.1. Basic Functions
package main
import "fmt"
// Function cơ bản
func greet(name string) string {
return "Hello, " + name + "!"
}
// Multiple parameters cùng type
func add(a, b int) int {
return a + b
}
// Multiple return values - đặc trưng Go
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Named return values
func swap(a, b string) (first, second string) {
first = b
second = a
return // naked return - trả named values
}
func main() {
fmt.Println(greet("Go")) // "Hello, Go!"
fmt.Println(add(3, 5)) // 8
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result) // 3.333...
a, b := swap("hello", "world")
fmt.Println(a, b) // "world" "hello"
}
1.2. Variadic Functions
// Variadic function - nhận số lượng tham số bất kỳ
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Mix fixed và variadic params
func printf(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
func main() {
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// Spread slice
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
}
1.3. First-class Functions
// Function as variable
var multiply func(int, int) int = func(a, b int) int {
return a * b
}
// Function as parameter (callback)
func apply(a, b int, op func(int, int) int) int {
return op(a, b)
}
// Function as return value (closure)
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
// Function type alias
type MathFunc func(int, int) int
func calculate(a, b int, fn MathFunc) int {
return fn(a, b)
}
func main() {
// Anonymous function (lambda)
double := func(x int) int {
return x * 2
}
fmt.Println(double(5)) // 10
// Callback
fmt.Println(apply(3, 4, multiply)) // 12
fmt.Println(apply(10, 3, func(a, b int) int {
return a - b
})) // 7
// Closure
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
// IIFE (Immediately Invoked Function Expression)
result := func(x int) int {
return x * x
}(5)
fmt.Println(result) // 25
}
1.4. Defer
import "os"
// defer: thực thi khi function return (LIFO order)
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // Đảm bảo file luôn được close
// ... đọc file
return nil
}
func main() {
// Defer chạy theo LIFO (Last In, First Out)
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// Output: 3, 2, 1
// Use case phổ biến: cleanup resources
// defer mutex.Unlock()
// defer rows.Close()
// defer tx.Rollback()
// defer span.End()
}
2. Interfaces
Interfaces in Go are implicit.implicit — type only needs to implement methods, no need to declare "implements". This is a big difference compared to Java/C#.
2.1. Interface definition
// Interface definition
type Shape interface {
Area() float64
Perimeter() float64
}
// Circle implements Shape implicitly
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Rectangle implements Shape implicitly
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Hàm nhận interface
func printShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 10, Height: 3}
printShapeInfo(c) // Area: 78.54, Perimeter: 31.42
printShapeInfo(r) // Area: 30.00, Perimeter: 26.00
// Slice of interfaces
shapes := []Shape{c, r}
for _, s := range shapes {
printShapeInfo(s)
}
}
2.2. Common Interfaces in Go
// io.Reader - interface quan trọng nhất
type Reader interface {
Read(p []byte) (n int, err error)
}
// io.Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
// fmt.Stringer (tương đương toString() trong Java)
type Stringer interface {
String() string
}
// error interface
type error interface {
Error() string
}
// sort.Interface
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
// Implement Stringer
type User struct {
Name string
Age int
}
func (u User) String() string {
return fmt.Sprintf("%s (age %d)", u.Name, u.Age)
}
func main() {
user := User{Name: "Alice", Age: 30}
fmt.Println(user) // "Alice (age 30)" - tự gọi String()
}
2.3. Interface Composition (Embedding)
// Compose interfaces từ các interfaces nhỏ
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Composed interface
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// Best practice: Keep interfaces SMALL
// Go proverb: "The bigger the interface, the weaker the abstraction"
// Thường chỉ 1-3 methods
2.4. Empty Interface & any
// interface{} (Go < 1.18) hoặc any (Go 1.18+) chấp nhận mọi type
func printAnything(v any) {
fmt.Printf("Type: %T, Value: %v\n", v, v)
}
func main() {
printAnything(42) // Type: int, Value: 42
printAnything("hello") // Type: string, Value: hello
printAnything(true) // Type: bool, Value: true
printAnything([]int{1, 2}) // Type: []int, Value: [1 2]
// map[string]any - thường dùng cho JSON
data := map[string]any{
"name": "Alice",
"age": 30,
"roles": []string{"admin", "user"},
}
fmt.Println(data)
}
3. Error Handling
Go has no exceptions (try-catch). Error handling used explicit return values — this is an intentional design choice.
3.1. Basic Error Handling
import (
"errors"
"fmt"
"os"
"strconv"
)
// Hàm trả về (result, error)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// fmt.Errorf với format string
func findUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("invalid user ID: %d", id)
}
// ... lookup user
return &User{Name: "Alice"}, nil
}
func main() {
// Pattern: check error immediately
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result)
// Chaining error checks
f, err := os.Open("config.json")
if err != nil {
fmt.Println("Cannot open file:", err)
return
}
defer f.Close()
// strconv error example
num, err := strconv.Atoi("abc")
if err != nil {
fmt.Println("Parse error:", err)
}
fmt.Println(num)
}
3.2. Custom Errors
// Custom error type
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Sentinel errors (pre-defined errors)
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
)
func getUser(id int) (*User, error) {
if id <= 0 {
return nil, &ValidationError{
Field: "id",
Message: "must be positive",
}
}
// ... user not in DB
return nil, fmt.Errorf("user %d: %w", id, ErrNotFound)
}
3.3. Error Wrapping (Go 1.13+)
import "errors"
func readConfig() error {
f, err := os.Open("config.yaml")
if err != nil {
// %w wraps the original error
return fmt.Errorf("readConfig: %w", err)
}
defer f.Close()
// ... parse
return nil
}
func initApp() error {
if err := readConfig(); err != nil {
return fmt.Errorf("initApp: %w", err)
}
return nil
}
func main() {
err := initApp()
if err != nil {
fmt.Println(err)
// "initApp: readConfig: open config.yaml: no such file or directory"
// errors.Is - check nếu error chain chứa target error
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
// errors.As - extract specific error type từ chain
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Path:", pathErr.Path)
fmt.Println("Op:", pathErr.Op)
}
}
}
3.4. Panic & Recover
// panic: dùng cho unrecoverable errors (thay vì throw)
// recover: bắt panic (thay vì catch)
func riskyOperation() {
// panic dừng execution, unwind stack
panic("something went terribly wrong")
}
// recover chỉ hoạt động trong defer
func safeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
riskyOperation()
fmt.Println("This won't print")
}
func main() {
safeOperation()
fmt.Println("Program continues normally")
}
// ⚠️ Quy tắc:
// - KHÔNG dùng panic cho business logic errors
// - Dùng panic khi: programmer error, unrecoverable state
// - recover thường dùng trong middleware (HTTP handler recovery)
3.5. Error Handling Patterns are commonly used
// Pattern 1: Early return
func processOrder(order *Order) error {
if order == nil {
return errors.New("order is nil")
}
if order.Total <= 0 {
return fmt.Errorf("invalid total: %.2f", order.Total)
}
if err := validateItems(order.Items); err != nil {
return fmt.Errorf("validate items: %w", err)
}
if err := chargePayment(order); err != nil {
return fmt.Errorf("charge payment: %w", err)
}
return nil
}
// Pattern 2: Error type assertion for flow control
func handleError(err error) {
var validErr *ValidationError
if errors.As(err, &validErr) {
// 400 Bad Request
fmt.Printf("Validation: %s - %s\n", validErr.Field, validErr.Message)
return
}
if errors.Is(err, ErrNotFound) {
// 404 Not Found
fmt.Println("Resource not found")
return
}
if errors.Is(err, ErrUnauthorized) {
// 401 Unauthorized
fmt.Println("Please login")
return
}
// 500 Internal Server Error
fmt.Println("Internal error:", err)
}
// Pattern 3: errors.Join (Go 1.20+) - multiple errors
func validateUser(u *User) error {
var errs []error
if u.Name == "" {
errs = append(errs, errors.New("name is required"))
}
if u.Email == "" {
errs = append(errs, errors.New("email is required"))
}
if u.Age < 0 {
errs = append(errs, errors.New("age must be non-negative"))
}
return errors.Join(errs...)
}
4. Structure Embedding (Composition)
// Go dùng composition thay vì inheritance
type BaseModel struct {
ID int
CreatedAt time.Time
UpdatedAt time.Time
}
type User struct {
BaseModel // Embedding - "kế thừa" tất cả fields và methods
Name string
Email string
}
type Post struct {
BaseModel
Title string
Content string
Author User
}
func main() {
user := User{
BaseModel: BaseModel{
ID: 1,
CreatedAt: time.Now(),
},
Name: "Alice",
Email: "[email protected]",
}
// Access embedded fields trực tiếp
fmt.Println(user.ID) // 1 (từ BaseModel)
fmt.Println(user.Name) // "Alice"
fmt.Println(user.CreatedAt) // (từ BaseModel)
}
5. Summary
- Functions: Multiple return values, first-class, closures, defer
- Interfaces: Implicit implementation, small interfaces, composition
- Error handling: Explicit errors, wrapping (%w), errors.Is/As, custom errors
- Composition: Structure embedding instead of inheritance
- Panic/Recover: Only for unrecoverable errors, not for business logic
Next article: Generics, Packages & Modules — type parameters system and code management in Go.