Go có hai cách khai báo biến (variable): var, dùng được ở mọi nơi, và :=, dạng ngắn chỉ dùng trong hàm và tự suy ra kiểu.
Mỗi biến có một kiểu cố định được quyết định lúc biên dịch. Khi bạn cho giá trị, Go suy ra kiểu từ giá trị đó, nên viết thêm kiểu (var city string = "Lisbon") vẫn hợp lệ nhưng thừa.
var và :=
var | := | |
|---|---|---|
| Dùng ở đâu | cấp package và trong hàm | chỉ trong hàm |
| Kiểu | tùy chọn; bắt buộc khi không có giá trị | luôn được suy ra |
| Giá trị ban đầu | tùy chọn (nếu không thì là giá trị zero) | bắt buộc |
| Khai báo lại | không bao giờ | được, nếu ít nhất một biến bên trái là biến mới |
Lựa chọn chuẩn trong một hàm:
x := valuekhi bạn có giá trị khởi đầu.var x Tkhi bạn muốn giá trị zero, như một cách nói "cố ý để trống":var buf bytes.Buffer,var total int.var x T = valuekhi kiểu được suy ra không phải kiểu bạn muốn, ví dụvar ratio float64 = 1(viếtratio := 1sẽ raint).
Dùng := bên ngoài hàm là lỗi cú pháp:
./main.go:5:1: syntax error: non-declaration statement outside function body
Giá trị zero
Biến khai báo không kèm giá trị không bao giờ chứa rác. Nó giữ giá trị zero của kiểu:
| Kiểu | Giá trị zero |
|---|---|
| số nguyên, số thực | 0 |
bool | false |
string | "" |
| con trỏ, slice, map, channel, hàm, interface | nil |
| mảng, struct | mọi phần tử hoặc trường đều mang giá trị zero |
Các API Go tốt được thiết kế để giá trị zero dùng được ngay: sync.Mutex zero đang ở trạng thái mở khóa, bytes.Buffer zero rỗng và sẵn sàng, còn len, range và append đều chạy được trên slice nil. Thứ cần để ý là map: đọc map nil trả về giá trị zero, nhưng ghi vào nó sẽ gây panic. Hãy tạo map bằng make hoặc literal trước khi ghi dữ liệu vào.
Khai báo nhiều biến
Khối var ( ... ) gom các biến cấp package có liên quan lại với nhau. Trong phép gán nhiều biến như x, y = y, x, mọi biểu thức bên phải được tính xong trước khi gán bất cứ thứ gì, nhờ vậy phép hoán đổi mới chạy đúng.
:= với nhiều tên không khai báo lại gì cả, miễn là ít nhất một tên bên trái là mới. Các tên đã có trong cùng phạm vi chỉ đơn giản được gán giá trị. Nhờ thế bạn dùng lại được err:
f, err := os.Open("a.txt") // declares f and err
g, err := os.Open("b.txt") // declares g, assigns to the existing err
Nếu không có tên nào mới, đó là lỗi:
./main.go:7:4: no new variables on left side of :=
Dùng = thường để gán cho biến đã tồn tại.
Biến không dùng đến là lỗi
Một biến cục bộ được khai báo nhưng không bao giờ được đọc sẽ làm build thất bại:
./main.go:9:2: declared and not used: x
Đây không phải cảnh báo mà bạn tắt đi được. Hãy xóa biến, dùng nó, hoặc khi đang gỡ lỗi thì làm nó im bằng định danh trống: _ = x. Định danh trống còn dùng để bỏ đi các giá trị trả về bạn không cần: _, err := fmt.Println("hi").
Biến cấp package và tham số hàm không bị áp quy tắc này.
Phạm vi và shadowing
Một biến tồn tại từ chỗ khai báo đến hết khối bao quanh nó ({ ... }). Một := trong khối bên trong tạo ra một biến mới, kể cả khi bên ngoài đã có biến cùng tên. Biến bên ngoài bị che khuất (shadowing), chứ không bị thay đổi:
Chương trình in ra inside: 42 rồi outside: 0. := bên trong if đã khai báo cả count lẫn err là biến mới trong khối bên trong, vì ở đó chưa có biến nào trong hai biến này. Code biên dịch không một lỗi nào, và đây là nguồn gốc kinh điển của những bug kiểu "giá trị của mình biến đâu mất".
Cách sửa là khai báo err trước rồi gán bằng =:
var err error
count, err = strconv.Atoi(input)
Mặc định go vet không báo shadowing. Bộ phân tích shadow riêng thì có, và các linter như golangci-lint có thể bật nó lên.
Đặt tên
- Dùng
camelCase:maxRetries,userID. Không dùngmax_retries. - Giữ chữ viết tắt ở cùng một kiểu chữ:
userID,httpClient,parseURL, không phảiuserId. - Tên ngắn cho phạm vi ngắn:
itrong vòng lặp,rcho một reader,errcho lỗi. Tên dài, mô tả rõ hơn cho biến cấp package. - Tên viết hoa chữ cái đầu ở cấp package (
var Timeout) được export sang các package khác. Xem trang package và import.
Biến cấp package
Biến khai báo bên ngoài hàm tồn tại suốt chương trình và được khởi tạo trước khi main chạy, theo thứ tự phụ thuộc. Chúng tiện cho cấu hình và cache, và được mọi goroutine dùng chung. Nếu có nhiều hơn một goroutine ghi vào cùng một biến, hãy bảo vệ nó bằng mutex hoặc dùng sync/atomic, nếu không bạn sẽ gặp data race.
Với những giá trị không bao giờ đổi, nên dùng hằng số. Một const không thể bị sửa nhầm và không tốn gì lúc chạy.
Câu hỏi thường gặp
var và := trong Go khác nhau thế nào?
:= khai báo và khởi tạo biến với kiểu được suy ra, và chỉ dùng được bên trong hàm: count := 10. var dùng được ở mọi nơi (kể cả cấp package), cho phép ghi rõ kiểu, và có thể bỏ giá trị ban đầu để nhận giá trị zero: var count int. Trong hàm, cách viết chuẩn của Go là dùng := khi có giá trị ban đầu và var khi bạn muốn giá trị zero.
Giá trị zero trong Go là gì?
Mọi biến khai báo không kèm giá trị đều nhận giá trị zero của kiểu: 0 cho số, false cho bool, "" cho string, và nil cho con trỏ, slice, map, channel, hàm và interface. Struct nhận giá trị zero cho từng trường. Go không có biến chưa được khởi tạo.
Sửa lỗi "declared and not used" trong Go như thế nào?
Go từ chối biến cục bộ được gán nhưng không bao giờ được đọc. Hãy dùng nó, xóa nó, hoặc khi đang gỡ lỗi thì gán nó cho định danh trống bằng _ = x. Biến cấp package và tham số hàm không bị áp quy tắc này.
Khai báo biến toàn cục trong Go như thế nào?
Dùng var ở cấp package, bên ngoài mọi hàm: var counter int. Dạng ngắn := không được phép ở đó. Tên viết hoa chữ cái đầu (var Counter int) thì package khác thấy được; tên viết thường chỉ thấy trong package của nó. Nếu có goroutine ghi vào biến cấp package, hãy bảo vệ nó bằng mutex.