0

Series Go Backend: Functions - Multiple return values, Variadic functions

Trong Go, function (hàm) là một "First-class citizen" (công dân hạng nhất). Điều này có nghĩa là hàm không chỉ dùng để đóng gói logic, mà còn có thể được gán cho biến, truyền vào như một tham số, hoặc return ra từ một hàm khác.

Hôm nay chúng ta sẽ tìm hiểu những tính năng làm nên thương hiệu của hàm trong Go, đặc biệt là cơ chế trả về nhiều giá trị – nền tảng của hệ thống xử lý lỗi (Error Handling) cực kỳ đặc trưng của ngôn ngữ này.

1. Cú pháp cơ bản và Rút gọn tham số

Khai báo hàm trong Go sử dụng từ khóa func, theo sau là tên hàm, danh sách tham số, kiểu trả về và nội dung hàm.

Go

// Cú pháp chuẩn
func add(a int, b int) int {
    return a + b
}

// Cú pháp rút gọn: Khi các tham số liên tiếp có CÙNG kiểu dữ liệu
// Bạn chỉ cần khai báo kiểu dữ liệu ở tham số cuối cùng
func multiply(a, b, c int, isValid bool) int {
    if isValid {
        return a * b * c
    }
    return 0
}

2. Multiple Return Values (Trả về nhiều giá trị)

Khác với C++ hay PHP (nơi bạn thường phải dùng tham chiếu pointer, mảng, hoặc tạo Struct/Object để trả về nhiều hơn một giá trị), Go hỗ trợ trả về nhiều giá trị một cách tự nhiên (Natively).

Đây là tính năng được sử dụng nhiều nhất trong Backend Go để xử lý kết quả và lỗi đồng thời:

Go

import "errors"

// Khai báo nhiều kiểu trả về trong cặp ngoặc đơn ()
func divide(a, b float64) (float64, error) {
    if b == 0 {
        // Trả về giá trị 0 và một error
        return 0, errors.New("không thể chia cho 0")
    }
    // Trả về kết quả và nil (không có lỗi)
    return a / b, nil
}

func main() {
    result, err := divide(10, 2)
    
    // Pattern kiểm tra lỗi kinh điển của Go
    if err != nil {
        fmt.Println("Lỗi:", err)
        return
    }
    fmt.Println("Kết quả:", result)
}

Bỏ qua giá trị không mong muốn với Blank Identifier (_):

Nếu một hàm trả về 2 giá trị, nhưng bạn chỉ quan tâm đến giá trị đầu tiên và không muốn dùng biến thứ hai (Go sẽ báo lỗi compile nếu khai báo biến mà không dùng), hãy dùng _.

Go

// Chỉ lấy kết quả, phớt lờ error (Trong thực tế nên hạn chế làm điều này với error)
res, _ := divide(10, 5) 

3. Named Return Values và Naked Return

Go cho phép bạn đặt tên luôn cho các biến trả về ngay trong phần khai báo (signature) của hàm. Các biến này sẽ được tự động khởi tạo với Zero Value ngay khi hàm bắt đầu chạy.

Go

// Khai báo sẵn hai biến trả về: area và perimeter
func getRectangleProps(length, width float64) (area float64, perimeter float64) {
    // Không cần dùng := vì biến đã được khai báo ở signature
    area = length * width
    perimeter = 2 * (length + width)
    
    // Naked return: Tự động trả về các biến đã được khai báo ở trên
    return 
}

Lưu ý thực chiến: Dù tính năng "Naked return" (chỉ viết chữ return) trông khá gọn, nhưng nó làm giảm khả năng đọc code (readability) với những hàm dài hàng chục dòng. Các Senior Go Developer thường ưu tiên viết rõ return area, perimeter ở cuối hàm để người đọc dễ dàng tracking luồng dữ liệu.

4. Variadic Functions (Hàm nhận số lượng tham số tùy ý)

Đôi khi bạn muốn viết một hàm tính tổng nhưng không biết trước người dùng sẽ truyền vào 2, 3, hay 100 con số. Cú pháp ...type của Go sinh ra để giải quyết bài toán này. Hàm fmt.Println mà chúng ta hay dùng chính là một Variadic Function.

Go

// Tham số numbers bên trong hàm sẽ hoạt động như một Slice kiểu []int
func sumAll(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    fmt.Println(sumAll(1, 2))       // Kết quả: 3
    fmt.Println(sumAll(1, 2, 3, 4)) // Kết quả: 10
}

Kỹ thuật giải nén Slice (Unpacking Slice):

Nếu bạn đã có sẵn một Slice các con số và muốn truyền nó vào hàm Variadic, bạn không thể truyền trực tiếp biến Slice đó được (vì tham số mong đợi là nhiều phần tử rời rạc). Bạn phải dùng cú pháp ... để "mở hộp" (unpack) Slice đó ra:

Go

prices := []int{10, 20, 30}
// Lỗi: cannot use prices (variable of type []int) as type int
// total := sumAll(prices) 

// Chuẩn: Thêm ... vào sau biến Slice để bung các phần tử ra
total := sumAll(prices...) 


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.