Đặc tả Ngôn ngữ Lập trình Go

Phiên bản ngôn ngữ go1.26 (12 tháng 1, 2026)

Giới thiệu

Đây là tài liệu tham chiếu cho ngôn ngữ lập trình Go. Để biết thêm thông tin và các tài liệu khác, xem go.dev.

Go là ngôn ngữ đa dụng được thiết kế với lập trình hệ thống trong tâm trí. Ngôn ngữ này có kiểu tĩnh mạnh, được quản lý bộ nhớ tự động (bộ gom rác) và hỗ trợ tường minh cho lập trình đồng thời. Các chương trình được xây dựng từ các gói, với các thuộc tính cho phép quản lý hiệu quả các phụ thuộc.

Cú pháp gọn gàng và dễ phân tích cú pháp, cho phép phân tích dễ dàng bởi các công cụ tự động như môi trường phát triển tích hợp.

Ký hiệu

Cú pháp được đặc tả bằng cách sử dụng một biến thể của Extended Backus-Naur Form (EBNF):

Syntax      = { Production } .
Production  = production_name "=" [ Expression ] "." .
Expression  = Term { "|" Term } .
Term        = Factor { Factor } .
Factor      = production_name | token [ "…" token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .

Các production là các biểu thức được xây dựng từ các term và các toán tử sau đây, theo thứ tự độ ưu tiên tăng dần:

|   alternation
()  grouping
[]  option (0 or 1 times)
{}  repetition (0 to n times)

Tên production viết thường được dùng để định danh các token từ vựng (terminal). Các non-terminal được viết theo dạng CamelCase. Các token từ vựng được đặt trong dấu ngoặc kép "" hoặc dấu backtick ``.

Dạng a … b biểu diễn tập hợp các ký tự từ a đến b như các lựa chọn thay thế. Dấu chấm lửng ngang cũng được sử dụng ở nơi khác trong đặc tả để biểu thị không chính thức các liệt kê hoặc đoạn mã khác nhau mà không được chỉ định thêm. Ký tự (khác với ba ký tự ...) không phải là token của ngôn ngữ Go.

Một liên kết có dạng [Go 1.xx] cho biết rằng một tính năng ngôn ngữ được mô tả (hoặc một khía cạnh nào đó của nó) đã được thay đổi hoặc thêm vào với phiên bản ngôn ngữ 1.xx và do đó yêu cầu tối thiểu phiên bản ngôn ngữ đó để biên dịch. Để biết chi tiết, xem phần được liên kết trong phụ lục.

Biểu diễn mã nguồn

Mã nguồn là văn bản Unicode được mã hóa bằng UTF-8. Văn bản không được chuẩn hóa, vì vậy một code point có dấu đơn lẻ khác với cùng ký tự đó được xây dựng từ việc kết hợp dấu và chữ cái; những ký tự đó được xử lý như hai code point. Để đơn giản, tài liệu này sẽ sử dụng thuật ngữ không có định ngữ ký tự để chỉ một Unicode code point trong văn bản nguồn.

Mỗi code point là riêng biệt; ví dụ, chữ hoa và chữ thường là các ký tự khác nhau.

Hạn chế cài đặt: Để tương thích với các công cụ khác, một trình biên dịch có thể không cho phép ký tự NUL (U+0000) trong văn bản nguồn.

Hạn chế cài đặt: Để tương thích với các công cụ khác, một trình biên dịch có thể bỏ qua dấu thứ tự byte được mã hóa UTF-8 (U+FEFF) nếu nó là Unicode code point đầu tiên trong văn bản nguồn. Dấu thứ tự byte có thể không được phép ở bất kỳ nơi nào khác trong nguồn.

Ký tự

Các thuật ngữ sau đây được sử dụng để biểu thị các danh mục ký tự Unicode cụ thể:

newline        = /* the Unicode code point U+000A */ .
unicode_char   = /* an arbitrary Unicode code point except newline */ .
unicode_letter = /* a Unicode code point categorized as "Letter" */ .
unicode_digit  = /* a Unicode code point categorized as "Number, decimal digit" */ .

Trong Tiêu chuẩn Unicode 8.0, Mục 4.5 "General Category" định nghĩa một tập hợp các danh mục ký tự. Go xử lý tất cả các ký tự trong bất kỳ danh mục Letter nào Lu, Ll, Lt, Lm, hoặc Lo như các chữ cái Unicode, và những ký tự trong danh mục Number Nd như các chữ số Unicode.

Chữ cái và chữ số

Ký tự gạch dưới _ (U+005F) được coi là chữ cái thường.

letter        = unicode_letter | "_" .
decimal_digit = "0" … "9" .
binary_digit  = "0" | "1" .
octal_digit   = "0" … "7" .
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .

Các phần tử từ vựng

Chú thích

Chú thích đóng vai trò là tài liệu của chương trình. Có hai dạng:

  1. Chú thích dòng bắt đầu bằng chuỗi ký tự // và kết thúc ở cuối dòng.
  2. Chú thích tổng quát bắt đầu bằng chuỗi ký tự /* và kết thúc tại chuỗi ký tự đầu tiên tiếp theo là */.

Một chú thích không thể bắt đầu bên trong một rune hoặc chuỗi literal, hay bên trong một chú thích khác. Một chú thích tổng quát không chứa dòng mới nào sẽ hoạt động như một khoảng trắng. Bất kỳ chú thích nào khác đều hoạt động như một dòng mới.

Token

Token tạo nên từ vựng của ngôn ngữ Go. Có bốn lớp: định danh, từ khóa, toán tử và dấu câu / ký tự đặc biệt, và literal. Khoảng trắng, được tạo từ các dấu cách (U+0020), tab ngang (U+0009), ký tự xuống dòng (U+000D), và dòng mới (U+000A), bị bỏ qua trừ khi nó phân tách các token mà nếu không sẽ được kết hợp thành một token duy nhất. Ngoài ra, một dòng mới hoặc kết thúc tệp có thể kích hoạt việc chèn một dấu chấm phẩy. Khi phân tách đầu vào thành các token, token tiếp theo là chuỗi ký tự dài nhất tạo thành một token hợp lệ.

Dấu chấm phẩy

Cú pháp hình thức sử dụng dấu chấm phẩy ";" làm ký tự kết thúc trong một số quy tắc sản xuất. Các chương trình Go có thể bỏ qua phần lớn các dấu chấm phẩy này theo hai quy tắc sau:

  1. Khi đầu vào được phân tách thành các token, một dấu chấm phẩy sẽ được tự động chèn vào luồng token ngay sau token cuối cùng của một dòng nếu token đó là
  2. Để cho phép các câu lệnh phức tạp chiếm một dòng duy nhất, một dấu chấm phẩy có thể được bỏ qua trước dấu đóng ")" hoặc "}".

Để phản ánh cách dùng thông thường, các ví dụ mã trong tài liệu này lược bỏ dấu chấm phẩy theo các quy tắc trên.

Định danh

Định danh đặt tên cho các thực thể trong chương trình như biến và kiểu dữ liệu. Một định danh là một chuỗi gồm một hoặc nhiều chữ cái và chữ số. Ký tự đầu tiên trong một định danh phải là một chữ cái.

identifier = letter { letter | unicode_digit } .
a
_x9
ThisVariableIsExported
αβ

Một số định danh được khai báo trước.

Từ khóa

Các từ khóa sau đây được đặt dành riêng và không thể dùng làm định danh.

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

Toán tử và dấu câu / ký tự đặc biệt

Các chuỗi ký tự sau đây biểu diễn toán tử (bao gồm toán tử gán) và dấu câu / ký tự đặc biệt [Go 1.18]:

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=          ~

Literal số nguyên

Một literal số nguyên là một chuỗi chữ số biểu diễn một hằng số nguyên. Một tiền tố tùy chọn đặt cơ số không phải thập phân: 0b hoặc 0B cho nhị phân, 0, 0o, hoặc 0O cho bát phân, và 0x hoặc 0X cho thập lục phân [Go 1.13]. Một số 0 đơn lẻ được xem là số không thập phân. Trong các literal thập lục phân, các chữ cái a đến fA đến F biểu diễn các giá trị từ 10 đến 15.

Để dễ đọc, một ký tự gạch dưới _ có thể xuất hiện sau tiền tố cơ số hoặc giữa các chữ số liên tiếp; các dấu gạch dưới như vậy không thay đổi giá trị của literal.

int_lit        = decimal_lit | binary_lit | octal_lit | hex_lit .
decimal_lit    = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
binary_lit     = "0" ( "b" | "B" ) [ "_" ] binary_digits .
octal_lit      = "0" [ "o" | "O" ] [ "_" ] octal_digits .
hex_lit        = "0" ( "x" | "X" ) [ "_" ] hex_digits .

decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
binary_digits  = binary_digit { [ "_" ] binary_digit } .
octal_digits   = octal_digit { [ "_" ] octal_digit } .
hex_digits     = hex_digit { [ "_" ] hex_digit } .
42
4_2
0600
0_600
0o600
0O600       // second character is capital letter 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727

_42         // an identifier, not an integer literal
42_         // invalid: _ must separate successive digits
4__2        // invalid: only one _ at a time
0_xBadFace  // invalid: _ must separate successive digits

Literal số thực dấu phẩy động

Một literal số thực dấu phẩy động là biểu diễn thập phân hoặc thập lục phân của một hằng số thực dấu phẩy động.

Một literal số thực dấu phẩy động thập phân bao gồm phần nguyên (các chữ số thập phân), dấu chấm thập phân, phần thập phân (các chữ số thập phân), và phần số mũ (e hoặc E theo sau bởi dấu tùy chọn và các chữ số thập phân). Phần nguyên hoặc phần thập phân có thể được lược bỏ; dấu chấm thập phân hoặc phần số mũ có thể được lược bỏ. Một giá trị số mũ exp chia tỷ lệ phần định trị (phần nguyên và phần thập phân) cho 10exp.

Một literal số thực dấu phẩy động thập lục phân bao gồm tiền tố 0x hoặc 0X, phần nguyên (các chữ số thập lục phân), dấu chấm cơ số, phần thập phân (các chữ số thập lục phân), và phần số mũ (p hoặc P theo sau bởi dấu tùy chọn và các chữ số thập phân). Phần nguyên hoặc phần thập phân có thể được lược bỏ; dấu chấm cơ số cũng có thể được lược bỏ, nhưng phần số mũ là bắt buộc. (Cú pháp này tương khớp với cú pháp trong IEEE 754-2008 §5.12.3.) Một giá trị số mũ exp chia tỷ lệ phần định trị (phần nguyên và phần thập phân) cho 2exp [Go 1.13].

Để dễ đọc, một ký tự gạch dưới _ có thể xuất hiện sau tiền tố cơ số hoặc giữa các chữ số liên tiếp; các dấu gạch dưới như vậy không thay đổi giá trị của literal.

float_lit         = decimal_float_lit | hex_float_lit .

decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
                    decimal_digits decimal_exponent |
                    "." decimal_digits [ decimal_exponent ] .
decimal_exponent  = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .

hex_float_lit     = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
hex_mantissa      = [ "_" ] hex_digits "." [ hex_digits ] |
                    [ "_" ] hex_digits |
                    "." hex_digits .
hex_exponent      = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0

0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (integer subtraction)

0x.p1        // invalid: mantissa has no digits
1p-2         // invalid: p exponent requires hexadecimal mantissa
0x1.5e-2     // invalid: hexadecimal mantissa requires p exponent
1_.5         // invalid: _ must separate successive digits
1._5         // invalid: _ must separate successive digits
1.5_e1       // invalid: _ must separate successive digits
1.5e_1       // invalid: _ must separate successive digits
1.5e1_       // invalid: _ must separate successive digits

Literal ảo (phần ảo)

Một literal ảo (phần ảo) biểu diễn phần ảo của một hằng số phức. Nó bao gồm một literal số nguyên hoặc số thực dấu phẩy động theo sau bởi chữ cái thường i. Giá trị của một literal ảo (phần ảo) là giá trị của literal số nguyên hoặc số thực dấu phẩy động tương ứng nhân với đơn vị ảo i [Go 1.13]

imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

Để tương thích ngược, phần nguyên của một literal ảo (phần ảo) chỉ gồm các chữ số thập phân (và có thể có các dấu gạch dưới) được xem là số nguyên thập phân, ngay cả khi nó bắt đầu bằng số 0 đứng đầu.

0i
0123i         // == 123i for backward-compatibility
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

Literal rune

Một literal rune biểu diễn một hằng rune, một giá trị số nguyên xác định một điểm mã Unicode. Một literal rune được biểu diễn dưới dạng một hoặc nhiều ký tự được đặt trong dấu ngoặc đơn, như trong 'x' hoặc '\n'. Bên trong dấu ngoặc, bất kỳ ký tự nào cũng có thể xuất hiện ngoại trừ dòng mới và dấu ngoặc đơn không được thoát. Một ký tự trong ngoặc đơn biểu diễn giá trị Unicode của chính ký tự đó, trong khi các chuỗi nhiều ký tự bắt đầu bằng dấu gạch chéo ngược mã hóa các giá trị theo nhiều định dạng khác nhau.

Dạng đơn giản nhất biểu diễn ký tự đơn trong ngoặc; vì văn bản nguồn Go là các ký tự Unicode được mã hóa theo UTF-8, nhiều byte được mã hóa UTF-8 có thể biểu diễn một giá trị số nguyên duy nhất. Chẳng hạn, literal 'a' chứa một byte đơn biểu diễn literal a, Unicode U+0061, giá trị 0x61, trong khi 'ä' chứa hai byte (0xc3 0xa4) biểu diễn literal a-dieresis, U+00E4, giá trị 0xe4.

Một số chuỗi thoát dấu gạch chéo ngược cho phép các giá trị tùy ý được mã hóa dưới dạng văn bản ASCII. Có bốn cách để biểu diễn giá trị số nguyên dưới dạng hằng số: \x theo sau bởi chính xác hai chữ số thập lục phân; \u theo sau bởi chính xác bốn chữ số thập lục phân; \U theo sau bởi chính xác tám chữ số thập lục phân, và một dấu gạch chéo ngược đơn \ theo sau bởi chính xác ba chữ số bát phân. Trong mỗi trường hợp, giá trị của literal là giá trị được biểu diễn bởi các chữ số trong cơ số tương ứng.

Mặc dù tất cả các cách biểu diễn này đều cho ra một số nguyên, chúng có các phạm vi hợp lệ khác nhau. Chuỗi thoát bát phân phải biểu diễn giá trị từ 0 đến 255 bao gồm hai đầu. Chuỗi thoát thập lục phân thỏa mãn điều kiện này theo cấu trúc. Các chuỗi thoát \u\U biểu diễn các điểm mã Unicode nên bên trong chúng một số giá trị là bất hợp lệ, đặc biệt là những giá trị trên 0x10FFFF và các nửa surrogate.

Sau một dấu gạch chéo ngược, một số chuỗi thoát ký tự đơn biểu diễn các giá trị đặc biệt:

\a   U+0007 alert or bell
\b   U+0008 backspace
\f   U+000C form feed
\n   U+000A line feed or newline
\r   U+000D carriage return
\t   U+0009 horizontal tab
\v   U+000B vertical tab
\\   U+005C backslash
\'   U+0027 single quote  (valid escape only within rune literals)
\"   U+0022 double quote  (valid escape only within string literals)

Một ký tự không được nhận dạng theo sau dấu gạch chéo ngược trong một literal rune là bất hợp lệ.

rune_lit         = "'" ( unicode_value | byte_value ) "'" .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\''         // rune literal containing single quote character
'aa'         // illegal: too many characters
'\k'         // illegal: k is not recognized after a backslash
'\xa'        // illegal: too few hexadecimal digits
'\0'         // illegal: too few octal digits
'\400'       // illegal: octal value over 255
'\uDFFF'     // illegal: surrogate half
'\U00110000' // illegal: invalid Unicode code point

Literal chuỗi

Một literal chuỗi biểu diễn một hằng chuỗi thu được từ việc nối một chuỗi các ký tự. Có hai dạng: chuỗi thô literal và chuỗi có diễn giải literal.

Chuỗi thô literal là các chuỗi ký tự nằm giữa các dấu ngoặc ngược, như trong `foo`. Bên trong dấu ngoặc, bất kỳ ký tự nào cũng có thể xuất hiện ngoại trừ dấu ngoặc ngược. Giá trị của một chuỗi thô literal là chuỗi bao gồm các ký tự không được thông dịch (được mã hóa ngầm định theo UTF-8) nằm giữa các dấu ngoặc; đặc biệt, dấu gạch chéo ngược không có ý nghĩa đặc biệt và chuỗi có thể chứa các dòng mới. Các ký tự xuống dòng ('\r') bên trong chuỗi thô literal bị loại bỏ khỏi giá trị chuỗi thô.

Chuỗi có diễn giải literal là các chuỗi ký tự nằm giữa các dấu ngoặc kép, như trong "bar". Bên trong dấu ngoặc, bất kỳ ký tự nào cũng có thể xuất hiện ngoại trừ dòng mới và dấu ngoặc kép không được thoát. Văn bản giữa các dấu ngoặc tạo thành giá trị của literal, với các chuỗi thoát dấu gạch chéo ngược được thông dịch như chúng được trong literal rune (ngoại trừ \' là bất hợp lệ và \" là hợp lệ), với các hạn chế tương tự. Chuỗi thoát bát phân ba chữ số (\nnn) và chuỗi thoát thập lục phân hai chữ số (\xnn) biểu diễn các byte riêng lẻ của chuỗi kết quả; tất cả các chuỗi thoát khác biểu diễn mã hóa UTF-8 (có thể nhiều byte) của các ký tự riêng lẻ. Vì vậy bên trong một literal chuỗi \377\xFF biểu diễn một byte đơn với giá trị 0xFF=255, trong khi ÿ, \u00FF, \U000000FF\xc3\xbf biểu diễn hai byte 0xc3 0xbf của mã hóa UTF-8 của ký tự U+00FF.

string_lit             = raw_string_lit | interpreted_string_lit .
raw_string_lit         = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc`                // same as "abc"
`\n
\n`                  // same as "\\n\n\\n"
"\n"
"\""                 // same as `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"             // illegal: surrogate half
"\U00110000"         // illegal: invalid Unicode code point

Tất cả các ví dụ này đều biểu diễn cùng một chuỗi:

"日本語"                                 // UTF-8 input text
`日本語`                                 // UTF-8 input text as a raw literal
"\u65e5\u672c\u8a9e"                    // the explicit Unicode code points
"\U000065e5\U0000672c\U00008a9e"        // the explicit Unicode code points
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // the explicit UTF-8 bytes

Nếu mã nguồn biểu diễn một ký tự dưới dạng hai điểm mã, chẳng hạn như một dạng kết hợp bao gồm dấu trọng âm và một chữ cái, kết quả sẽ là lỗi nếu được đặt trong một literal rune (nó không phải là một điểm mã duy nhất), và sẽ xuất hiện dưới dạng hai điểm mã nếu được đặt trong một literal chuỗi.

Hằng số

Có các hằng số boolean, hằng số rune, hằng số số nguyên, hằng số số thực dấu phẩy động, hằng số số phức, và hằng số chuỗi. Hằng số rune, số nguyên, số thực dấu phẩy động, và số phức được gọi chung là hằng số số học.

Một giá trị hằng số được biểu diễn bởi một literal rune, số nguyên, số thực dấu phẩy động, số ảo, hoặc chuỗi, một định danh biểu thị một hằng số, một biểu thức hằng số, một phép chuyển đổi kiểu có kết quả là hằng số, hoặc giá trị kết quả của một số hàm dựng sẵn như min hoặc max được áp dụng với các đối số hằng số, unsafe.Sizeof được áp dụng với một số giá trị nhất định, cap hoặc len được áp dụng với một số biểu thức, realimag được áp dụng với hằng số số phức và complex được áp dụng với các hằng số số học. Các giá trị boolean true (đúng) và false (sai) được biểu diễn bởi các hằng số được khai báo trước truefalse. Định danh được khai báo trước iota biểu thị một hằng số số nguyên.

Nhìn chung, các hằng số số phức là một dạng biểu thức hằng số và được thảo luận trong phần đó.

Các hằng số số học biểu diễn các giá trị chính xác với độ chính xác tùy ý và không bị tràn số. Do đó, không có hằng số nào biểu thị giá trị số không âm IEEE 754, vô cực, và các giá trị không phải số (NaN).

Hằng số có thể có kiểu tường minh hoặc không có kiểu tường minh. Các literal hằng số, true, false, iota, và một số biểu thức hằng số chỉ chứa các toán hạng hằng số không có kiểu tường minh đều không có kiểu tường minh.

Một hằng số có thể được gán kiểu tường minh thông qua một khai báo hằng số hoặc phép chuyển đổi kiểu, hoặc ngầm định khi được sử dụng trong khai báo biến hoặc một câu lệnh gán hoặc làm toán hạng trong một biểu thức. Đây là lỗi nếu giá trị hằng số không thể được biểu diễn như một giá trị của kiểu tương ứng. Nếu kiểu là một tham số kiểu, hằng số được chuyển đổi thành một giá trị không phải hằng số của tham số kiểu.

Một hằng số không có kiểu tường minh có một kiểu mặc định, đó là kiểu mà hằng số được ngầm chuyển đổi sang trong các ngữ cảnh yêu cầu một giá trị có kiểu tường minh, ví dụ như trong một khai báo biến ngắn như i := 0 khi không có kiểu tường minh nào được chỉ định. Kiểu mặc định của một hằng số không có kiểu tường minh là bool, rune, int, float64, complex128, hoặc string tương ứng, tùy thuộc vào việc đó là hằng số boolean, rune, số nguyên, số thực dấu phẩy động, số phức, hay chuỗi.

Giới hạn triển khai: Mặc dù các hằng số số học có độ chính xác tùy ý trong ngôn ngữ, một trình biên dịch có thể triển khai chúng bằng cách sử dụng biểu diễn nội bộ với độ chính xác hạn chế. Tuy nhiên, mọi triển khai phải:

Các yêu cầu này áp dụng cho cả literal hằng số và kết quả của việc đánh giá biểu thức hằng số.

Biến

Một biến là một vị trí lưu trữ để chứa một giá trị. Tập hợp các giá trị được phép được xác định bởi kiểu của biến.

Một khai báo biến hoặc, đối với các tham số và kết quả của hàm, chữ ký của một khai báo hàm hoặc literal hàm dành riêng vùng lưu trữ cho một biến được đặt tên. Gọi hàm dựng sẵn new hoặc lấy địa chỉ của một composite literal sẽ cấp phát vùng lưu trữ cho một biến tại thời điểm chạy. Biến ẩn danh như vậy được tham chiếu thông qua một phép hướng dẫn con trỏ (có thể ngầm định).

Các biến có cấu trúc của các kiểu mảng, slice, và struct có các phần tử và trường có thể được lấy địa chỉ riêng lẻ. Mỗi phần tử như vậy hoạt động như một biến.

Kiểu tĩnh (hay chỉ là kiểu) của một biến là kiểu được khai báo trong khai báo của nó, kiểu được cung cấp trong lời gọi new hoặc composite literal, hoặc kiểu của một phần tử của biến có cấu trúc. Các biến thuộc kiểu interface cũng có một kiểu động riêng biệt, đó là kiểu (không phải interface) của giá trị được gán cho biến tại thời điểm chạy (trừ khi giá trị là định danh được khai báo trước nil, không có kiểu). Kiểu động có thể thay đổi trong quá trình thực thi nhưng các giá trị được lưu trữ trong các biến interface luôn có thể gán cho kiểu tĩnh của biến.

var x interface{}  // x is nil and has static type interface{}
var v *T           // v has value nil, static type *T
x = 42             // x has value 42 and dynamic type int
x = v              // x has value (*T)(nil) and dynamic type *T

Giá trị của một biến được lấy ra bằng cách tham chiếu biến đó trong một biểu thức; đó là giá trị gần nhất được gán cho biến. Nếu một biến chưa được gán giá trị, giá trị của nó là giá trị không (zero value) cho kiểu của nó.

Kiểu

Một kiểu xác định một tập hợp các giá trị cùng với các phép toán và phương thức đặc thù cho những giá trị đó. Một kiểu có thể được biểu thị bằng một tên kiểu, nếu có, và phải được theo sau bởi đối số kiểu nếu kiểu đó là generic. Một kiểu cũng có thể được chỉ định bằng một literal kiểu, kết hợp một kiểu từ các kiểu hiện có.

Type     = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit  = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
           SliceType | MapType | ChannelType .

Ngôn ngữ khai báo trước một số tên kiểu nhất định. Các tên khác được giới thiệu qua khai báo kiểu hoặc danh sách tham số kiểu. Kiểu tổng hợp—mảng, struct, con trỏ, hàm, interface, slice, map, và channel—có thể được xây dựng bằng cách sử dụng các literal kiểu.

Các kiểu được khai báo trước, kiểu đã định nghĩa, và tham số kiểu được gọi là kiểu được đặt tên. Một bí danh (alias) biểu thị một kiểu được đặt tên nếu kiểu được khai báo trong khai báo bí danh đó là một kiểu được đặt tên.

Kiểu boolean

Một kiểu boolean biểu diễn tập hợp các giá trị boolean true (đúng) và false (sai) được biểu thị bởi các hằng số được khai báo trước truefalse. Kiểu boolean được khai báo trước là bool; đây là một kiểu đã định nghĩa.

Kiểu số học

Một kiểu số nguyên, số thực dấu phẩy động, hoặc số phức biểu diễn tập hợp các giá trị số nguyên, số thực dấu phẩy động, hoặc số phức tương ứng. Chúng được gọi chung là kiểu số học. Các kiểu số học được khai báo trước không phụ thuộc vào kiến trúc là:

uint8       the set of all unsigned  8-bit integers (0 to 255)
uint16      the set of all unsigned 16-bit integers (0 to 65535)
uint32      the set of all unsigned 32-bit integers (0 to 4294967295)
uint64      the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8        the set of all signed  8-bit integers (-128 to 127)
int16       the set of all signed 16-bit integers (-32768 to 32767)
int32       the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64       the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

float32     the set of all IEEE 754 32-bit floating-point numbers
float64     the set of all IEEE 754 64-bit floating-point numbers

complex64   the set of all complex numbers with float32 real and imaginary parts
complex128  the set of all complex numbers with float64 real and imaginary parts

byte        alias for uint8
rune        alias for int32

Giá trị của một số nguyên n-bit có độ rộng n bit và được biểu diễn bằng cách sử dụng số học bù hai.

Ngoài ra còn có một tập hợp các kiểu số nguyên được khai báo trước với kích thước phụ thuộc vào triển khai:

uint     either 32 or 64 bits
int      same size as uint
uintptr  an unsigned integer large enough to store the uninterpreted bits of a pointer value

Để tránh các vấn đề về tính di động, tất cả các kiểu số học đều là kiểu đã định nghĩa và do đó phân biệt với nhau, ngoại trừ byte, là một bí danh cho uint8, và rune, là một bí danh cho int32. Cần có các phép chuyển đổi tường minh khi các kiểu số học khác nhau được trộn lẫn trong một biểu thức hoặc phép gán. Ví dụ, int32int không phải là cùng một kiểu mặc dù chúng có thể có cùng kích thước trên một kiến trúc cụ thể.

Kiểu chuỗi

Một kiểu chuỗi biểu diễn tập hợp các giá trị chuỗi. Một giá trị chuỗi là một dãy (có thể rỗng) các byte. Số byte được gọi là độ dài của chuỗi và không bao giờ âm. Chuỗi là bất biến: một khi đã được tạo, không thể thay đổi nội dung của một chuỗi. Kiểu chuỗi được khai báo trước là string; đây là một kiểu đã định nghĩa.

Độ dài của một chuỗi s có thể được tìm ra bằng cách sử dụng hàm dựng sẵn len. Độ dài là hằng số tại thời điểm biên dịch nếu chuỗi là một hằng số. Các byte của chuỗi có thể được truy cập bằng chỉ số số nguyên từ 0 đến len(s)-1. Không được phép lấy địa chỉ của phần tử như vậy; nếu s[i] là byte thứ i của một chuỗi, &s[i] là không hợp lệ.

Kiểu mảng

Một mảng là một dãy các phần tử được đánh số của một kiểu duy nhất, gọi là kiểu phần tử. Số phần tử được gọi là độ dài của mảng và không bao giờ âm.

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

Độ dài là một phần của kiểu mảng; nó phải ước tính ra một hằng số không âm có thể biểu diễn bằng một giá trị của kiểu int. Độ dài của mảng a có thể được tìm ra bằng cách sử dụng hàm dựng sẵn len. Các phần tử có thể được lấy địa chỉ bằng chỉ số số nguyên từ 0 đến len(a)-1. Kiểu mảng luôn là một chiều nhưng có thể được kết hợp để tạo thành các kiểu đa chiều.

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // same as [2]([2]([2]float64))

Một kiểu mảng T không được có phần tử thuộc kiểu T, hoặc thuộc kiểu chứa T như một thành phần, trực tiếp hay gián tiếp, nếu các kiểu chứa đó chỉ là kiểu mảng hoặc struct.

// invalid array types
type (
	T1 [10]T1                 // element type of T1 is T1
	T2 [10]struct{ f T2 }     // T2 contains T2 as component of a struct
	T3 [10]T4                 // T3 contains T3 as component of a struct in T4
	T4 struct{ f T3 }         // T4 contains T4 as component of array T3 in a struct
)

// valid array types
type (
	T5 [10]*T5                // T5 contains T5 as component of a pointer
	T6 [10]func() T6          // T6 contains T6 as component of a function type
	T7 [10]struct{ f []T7 }   // T7 contains T7 as component of a slice in a struct
)

Kiểu slice

Một slice là một bộ mô tả cho một đoạn liên tiếp của một mảng nền và cung cấp quyền truy cập vào một dãy các phần tử được đánh số từ mảng đó. Một kiểu slice biểu thị tập hợp tất cả các slice của các mảng thuộc kiểu phần tử của nó. Số phần tử được gọi là độ dài của slice và không bao giờ âm. Giá trị của một slice chưa được khởi tạo là nil.

SliceType = "[" "]" ElementType .

Độ dài của một slice s có thể được tìm ra bằng hàm dựng sẵn len; khác với mảng, nó có thể thay đổi trong quá trình thực thi. Các phần tử có thể được lấy địa chỉ bằng chỉ số số nguyên từ 0 đến len(s)-1. Chỉ số slice của một phần tử nhất định có thể nhỏ hơn chỉ số của cùng phần tử đó trong mảng nền.

Một slice, sau khi được khởi tạo, luôn liên kết với một mảng nền chứa các phần tử của nó. Do đó, một slice chia sẻ vùng lưu trữ với mảng của nó và với các slice khác của cùng mảng; ngược lại, các mảng khác nhau luôn biểu diễn các vùng lưu trữ khác nhau.

Mảng nền của một slice có thể mở rộng ra ngoài phần cuối của slice. Dung lượng là số đo cho mức độ mở rộng đó: đó là tổng của độ dài của slice và độ dài của mảng vượt ra ngoài slice; một slice có độ dài tối đa bằng dung lượng đó có thể được tạo bằng cách slicing một slice mới từ slice gốc. Dung lượng của một slice a có thể được tìm ra bằng cách sử dụng hàm dựng sẵn cap(a).

Một giá trị slice mới, đã được khởi tạo cho một kiểu phần tử nhất định T có thể được tạo bằng hàm dựng sẵn make, nhận vào một kiểu slice và các tham số chỉ định độ dài và tùy chọn dung lượng. Một slice được tạo bằng make luôn cấp phát một mảng mới, ẩn mà giá trị slice trả về tham chiếu đến. Tức là, việc thực thi

make([]T, length, capacity)

tạo ra cùng một slice như việc cấp phát một mảng và slicing nó, vì vậy hai biểu thức này là tương đương:

make([]int, 50, 100)
new([100]int)[0:50]

Giống như mảng, slice luôn là một chiều nhưng có thể được kết hợp để xây dựng các đối tượng nhiều chiều. Với các mảng của mảng, các mảng bên trong, theo cấu trúc, luôn có cùng độ dài; tuy nhiên với các slice của slice (hoặc mảng của slice), các độ dài bên trong có thể thay đổi động. Hơn nữa, các slice bên trong phải được khởi tạo riêng lẻ.

Kiểu struct

Một struct là một dãy các phần tử được đặt tên, gọi là các trường, mỗi trường có tên và một kiểu. Tên trường có thể được chỉ định tường minh (IdentifierList) hoặc ngầm định (EmbeddedField). Trong một struct, các tên trường không phải blank phải duy nhất.

StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName [ TypeArgs ] .
Tag           = string_lit .
// An empty struct.
struct {}

// A struct with 6 fields.
struct {
	x, y int
	u float32
	_ float32  // padding
	A *[]int
	F func()
}

Một trường được khai báo với kiểu nhưng không có tên trường tường minh được gọi là trường nhúng. Một trường nhúng phải được chỉ định là một tên kiểu T hoặc là con trỏ đến một tên kiểu không phải interface *T, và T bản thân không được là kiểu con trỏ hoặc tham số kiểu. Tên kiểu không đủ điều kiện đóng vai trò là tên trường.

// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
	T1        // field name is T1
	*T2       // field name is T2
	P.T3      // field name is T3
	*P.T4     // field name is T4
	x, y int  // field names are x and y
}

Khai báo sau đây là bất hợp lệ vì tên trường phải là duy nhất trong một kiểu struct:

struct {
	T     // conflicts with embedded field *T and *P.T
	*T    // conflicts with embedded field T and *P.T
	*P.T  // conflicts with embedded field T and *T
}

Một trường hoặc phương thức f của một trường nhúng trong một struct x được gọi là được thăng cấp nếu x.f là một bộ chọn hợp lệ biểu thị trường hoặc phương thức f đó.

Các trường được thăng cấp hoạt động như các trường thông thường của một struct ngoại trừ chúng không thể được sử dụng như tên trường trong composite literal của struct.

Cho một kiểu struct S và một tên kiểu T, các phương thức được thăng cấp được bao gồm trong tập phương thức của struct như sau:

Một khai báo trường có thể được theo sau bởi một string literal tag tùy chọn, trở thành một thuộc tính cho tất cả các trường trong khai báo trường tương ứng. Một chuỗi tag rỗng tương đương với không có tag. Các tag được hiển thị thông qua một giao diện reflection và tham gia vào nhận dạng kiểu cho các struct nhưng bị bỏ qua trong các trường hợp khác.

struct {
	x, y float64 ""  // an empty tag string is like an absent tag
	name string  "any string is permitted as a tag"
	_    [4]byte "ceci n'est pas un champ de structure"
}

// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
	microsec  uint64 `protobuf:"1"`
	serverIP6 uint64 `protobuf:"2"`
}

Một kiểu struct T không được chứa một trường thuộc kiểu T, hoặc thuộc kiểu chứa T như một thành phần, trực tiếp hay gián tiếp, nếu các kiểu chứa đó chỉ là kiểu mảng hoặc struct.

// invalid struct types
type (
	T1 struct{ T1 }            // T1 contains a field of T1
	T2 struct{ f [10]T2 }      // T2 contains T2 as component of an array
	T3 struct{ T4 }            // T3 contains T3 as component of an array in struct T4
	T4 struct{ f [10]T3 }      // T4 contains T4 as component of struct T3 in an array
)

// valid struct types
type (
	T5 struct{ f *T5 }         // T5 contains T5 as component of a pointer
	T6 struct{ f func() T6 }   // T6 contains T6 as component of a function type
	T7 struct{ f [10][]T7 }    // T7 contains T7 as component of a slice in an array
)

Kiểu con trỏ

Một kiểu con trỏ biểu thị tập hợp tất cả các con trỏ đến biến của một kiểu nhất định, gọi là kiểu cơ sở của con trỏ. Giá trị của một con trỏ chưa được khởi tạo là nil.

PointerType = "*" BaseType .
BaseType    = Type .
*Point
*[4]int

Kiểu hàm

Một kiểu hàm biểu thị tập hợp tất cả các hàm với cùng kiểu tham số và kết quả. Giá trị của một biến chưa được khởi tạo thuộc kiểu hàm là nil.

FunctionType  = "func" Signature .
Signature     = Parameters [ Result ] .
Result        = Parameters | Type .
Parameters    = "(" [ ParameterList [ "," ] ] ")" .
ParameterList = ParameterDecl { "," ParameterDecl } .
ParameterDecl = [ IdentifierList ] [ "..." ] Type .

Trong một danh sách các tham số hoặc kết quả, các tên (IdentifierList) phải đều có hoặc đều vắng mặt. Nếu có, mỗi tên đại diện cho một mục (tham số hoặc kết quả) của kiểu được chỉ định và tất cả các tên không phải blank trong chữ ký phải duy nhất. Nếu vắng mặt, mỗi kiểu đại diện cho một mục của kiểu đó. Danh sách tham số và kết quả luôn được đặt trong dấu ngoặc đơn ngoại trừ khi có chính xác một kết quả không được đặt tên, nó có thể được viết dưới dạng một kiểu không có dấu ngoặc đơn.

Tham số cuối cùng trong chữ ký hàm có thể có kiểu có tiền tố .... Một hàm với tham số như vậy được gọi là variadic và có thể được gọi với không hoặc nhiều đối số cho tham số đó.

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

Kiểu interface

Một kiểu interface định nghĩa một tập hợp kiểu. Một biến thuộc kiểu interface có thể lưu trữ một giá trị của bất kỳ kiểu nào nằm trong tập hợp kiểu của interface. Kiểu như vậy được gọi là triển khai interface. Giá trị của một biến chưa được khởi tạo thuộc kiểu interface là nil.

InterfaceType  = "interface" "{" { InterfaceElem ";" } "}" .
InterfaceElem  = MethodElem | TypeElem .
MethodElem     = MethodName Signature .
MethodName     = identifier .
TypeElem       = TypeTerm { "|" TypeTerm } .
TypeTerm       = Type | UnderlyingType .
UnderlyingType = "~" Type .

Một kiểu interface được chỉ định bởi một danh sách các phần tử interface. Một phần tử interface là một phương thức hoặc một phần tử kiểu, trong đó một phần tử kiểu là hợp của một hoặc nhiều phần tử kiểu (type terms). Một phần tử kiểu (type term) là một kiểu đơn hoặc một kiểu nền đơn.

Interface cơ bản

Ở dạng cơ bản nhất, một interface chỉ định một danh sách (có thể rỗng) các phương thức. Tập hợp kiểu được định nghĩa bởi interface như vậy là tập hợp các kiểu triển khai tất cả các phương thức đó, và tập phương thức tương ứng bao gồm chính xác các phương thức được chỉ định bởi interface. Các interface có tập hợp kiểu có thể được định nghĩa hoàn toàn bằng danh sách phương thức được gọi là interface cơ bản.

// A simple File interface.
interface {
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Close() error
}

Tên của mỗi phương thức được chỉ định tường minh phải duy nhất và không phải blank.

interface {
	String() string
	String() string  // illegal: String not unique
	_(x int)         // illegal: method must have non-blank name
}

Nhiều hơn một kiểu có thể triển khai một interface. Ví dụ, nếu hai kiểu S1S2 có tập phương thức

func (p T) Read(p []byte) (n int, err error)
func (p T) Write(p []byte) (n int, err error)
func (p T) Close() error

(trong đó T đứng cho S1 hoặc S2) thì interface File được triển khai bởi cả S1S2, bất kể S1S2 có hoặc chia sẻ những phương thức nào khác.

Mọi kiểu là thành viên của tập hợp kiểu của một interface đều triển khai interface đó. Bất kỳ kiểu nhất định nào cũng có thể triển khai nhiều interface khác nhau. Ví dụ, tất cả các kiểu đều triển khai interface rỗng biểu diễn tập hợp tất cả các kiểu (không phải interface):

interface{}

Để tiện lợi, kiểu được khai báo trước any là một bí danh cho interface rỗng. [Go 1.18]

Tương tự, xem xét đặc tả interface này, xuất hiện trong một khai báo kiểu để định nghĩa một interface có tên Locker:

type Locker interface {
	Lock()
	Unlock()
}

Nếu S1S2 cũng triển khai

func (p T) Lock() { … }
func (p T) Unlock() { … }

chúng triển khai interface Locker cũng như interface File.

Interface nhúng

Ở dạng tổng quát hơn một chút, một interface T có thể sử dụng một tên kiểu interface (có thể đủ điều kiện) E như một phần tử interface. Điều này được gọi là nhúng interface E vào T [Go 1.14]. Tập hợp kiểu của Tgiao của các tập hợp kiểu được định nghĩa bởi các phương thức được khai báo tường minh của T và các tập hợp kiểu của các interface được nhúng trong T. Nói cách khác, tập hợp kiểu của T là tập hợp tất cả các kiểu triển khai tất cả các phương thức được khai báo tường minh của T và cũng tất cả các phương thức của E [Go 1.18].

type Reader interface {
	Read(p []byte) (n int, err error)
	Close() error
}

type Writer interface {
	Write(p []byte) (n int, err error)
	Close() error
}

// ReadWriter's methods are Read, Write, and Close.
type ReadWriter interface {
	Reader  // includes methods of Reader in ReadWriter's method set
	Writer  // includes methods of Writer in ReadWriter's method set
}

Khi nhúng các interface, các phương thức có cùng tên phải có chữ ký giống hệt nhau.

type ReadCloser interface {
	Reader   // includes methods of Reader in ReadCloser's method set
	Close()  // illegal: signatures of Reader.Close and Close are different
}

Interface tổng quát

Ở dạng tổng quát nhất, một phần tử interface cũng có thể là một phần tử kiểu tùy ý T, hoặc một phần tử có dạng ~T chỉ định kiểu nền T, hoặc một hợp của các phần tử t1|t2|…|tn [Go 1.18]. Cùng với các đặc tả phương thức, các phần tử này cho phép định nghĩa chính xác tập hợp kiểu của một interface như sau:

Cách diễn đạt "tập hợp tất cả các kiểu không phải interface" đề cập không chỉ đến tất cả các kiểu (không phải interface) được khai báo trong chương trình hiện tại, mà đến tất cả các kiểu có thể có trong tất cả các chương trình có thể có, và do đó là vô hạn. Tương tự, cho tập hợp tất cả các kiểu không phải interface triển khai một phương thức cụ thể, giao của các tập phương thức của những kiểu đó sẽ chứa chính xác phương thức đó, ngay cả khi tất cả các kiểu trong chương trình hiện tại luôn ghép phương thức đó với một phương thức khác.

Theo cấu trúc, tập hợp kiểu của một interface không bao giờ chứa kiểu interface.

// An interface representing only the type int.
interface {
	int
}

// An interface representing all types with underlying type int.
interface {
	~int
}

// An interface representing all types with underlying type int that implement the String method.
interface {
	~int
	String() string
}

// An interface representing an empty type set: there is no type that is both an int and a string.
interface {
	int
	string
}

Trong một phần tử có dạng ~T, kiểu nền của T phải chính là nó, và T không được là một interface.

type MyInt int

interface {
	~[]byte  // the underlying type of []byte is itself
	~MyInt   // illegal: the underlying type of MyInt is not MyInt
	~error   // illegal: error is an interface
}

Các phần tử hợp biểu thị hợp của các tập hợp kiểu:

// The Float interface represents all floating-point types
// (including any named types whose underlying types are
// either float32 or float64).
type Float interface {
	~float32 | ~float64
}

Kiểu T trong một phần tử có dạng T hoặc ~T không được là một tham số kiểu, và các tập hợp kiểu của tất cả các phần tử không phải interface phải từng đôi một không giao nhau (giao từng đôi của các tập hợp kiểu phải rỗng). Cho một tham số kiểu P:

interface {
	P                // illegal: P is a type parameter
	int | ~P         // illegal: P is a type parameter
	~int | MyInt     // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt)
	float32 | Float  // overlapping type sets but Float is an interface
}

Giới hạn triển khai: Một hợp (với nhiều hơn một phần tử) không được chứa định danh được khai báo trước comparable hoặc các interface chỉ định phương thức, hoặc nhúng comparable hoặc các interface chỉ định phương thức.

Các interface không phải cơ bản chỉ có thể được dùng làm ràng buộc kiểu, hoặc làm phần tử của các interface khác được dùng làm ràng buộc. Chúng không thể là kiểu của các giá trị hoặc biến, hoặc là các thành phần của các kiểu không phải interface khác.

var x Float                     // illegal: Float is not a basic interface

var x interface{} = Float(nil)  // illegal

type Floatish struct {
	f Float                 // illegal
}

Một kiểu interface T không được nhúng một phần tử kiểu mà là, chứa, hoặc nhúng T, trực tiếp hay gián tiếp.

// illegal: Bad may not embed itself
type Bad interface {
	Bad
}

// illegal: Bad1 may not embed itself using Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

// illegal: Bad3 may not embed a union containing Bad3
type Bad3 interface {
	~int | ~string | Bad3
}

// illegal: Bad4 may not embed an array containing Bad4 as element type
type Bad4 interface {
	[10]Bad4
}

Triển khai một interface

Một kiểu T triển khai một interface I nếu

Một giá trị thuộc kiểu T triển khai một interface nếu T triển khai interface đó.

Kiểu map

Một map là một nhóm các phần tử không có thứ tự của một kiểu, gọi là kiểu phần tử, được đánh chỉ mục bằng một tập hợp các khóa duy nhất của một kiểu khác, gọi là kiểu khóa. Giá trị của một map chưa được khởi tạo là nil.

MapType = "map" "[" KeyType "]" ElementType .
KeyType = Type .

Các toán tử so sánh ==!= phải được định nghĩa đầy đủ cho các toán hạng của kiểu khóa; do đó kiểu khóa không được là hàm, map, hoặc slice. Nếu kiểu khóa là kiểu interface, các toán tử so sánh này phải được định nghĩa cho các giá trị khóa động; thất bại sẽ gây ra một panic tại thời điểm chạy.

map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}

Số phần tử map được gọi là độ dài của nó. Đối với một map m, nó có thể được tìm ra bằng cách sử dụng hàm dựng sẵn len và có thể thay đổi trong quá trình thực thi. Các phần tử có thể được thêm vào trong quá trình thực thi bằng cách sử dụng phép gán và được lấy ra bằng biểu thức chỉ mục; chúng có thể được xóa bằng hàm dựng sẵn deleteclear.

Một giá trị map mới, rỗng được tạo bằng hàm dựng sẵn make, nhận vào kiểu map và một gợi ý dung lượng tùy chọn làm đối số:

make(map[string]int)
make(map[string]int, 100)

Dung lượng ban đầu không giới hạn kích thước của nó: map phát triển để chứa số lượng mục được lưu trữ trong chúng, ngoại trừ các map nil. Một map nil tương đương với một map rỗng ngoại trừ không có phần tử nào có thể được thêm vào.

Kiểu channel

Một channel cung cấp cơ chế cho các hàm thực thi đồng thời để giao tiếp bằng cách gửinhận các giá trị của một kiểu phần tử được chỉ định. Giá trị của một channel chưa được khởi tạo là nil.

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

Toán tử <- tùy chọn chỉ định hướng của channel, gửi hoặc nhận. Nếu hướng được chỉ định, channel là có hướng, ngược lại thì hai chiều. Một channel có thể bị ràng buộc chỉ để gửi hoặc chỉ để nhận bằng phép gán hoặc phép chuyển đổi kiểu tường minh.

chan T          // can be used to send and receive values of type T
chan<- float64  // can only be used to send float64s
<-chan int      // can only be used to receive ints

Toán tử <- kết hợp với chan ngoài cùng bên trái có thể:

chan<- chan int    // same as chan<- (chan int)
chan<- <-chan int  // same as chan<- (<-chan int)
<-chan <-chan int  // same as <-chan (<-chan int)
chan (<-chan int)

Một giá trị channel mới, đã được khởi tạo có thể được tạo bằng hàm dựng sẵn make, nhận vào kiểu channel và một dung lượng tùy chọn làm đối số:

make(chan int, 100)

Dung lượng, tính bằng số phần tử, đặt kích thước của bộ đệm trong channel. Nếu dung lượng bằng không hoặc vắng mặt, channel không có bộ đệm và việc giao tiếp chỉ thành công khi cả người gửi và người nhận đều sẵn sàng. Ngược lại, channel có bộ đệm và việc giao tiếp thành công mà không bị chặn nếu bộ đệm không đầy (gửi) hoặc không rỗng (nhận). Một channel nil không bao giờ sẵn sàng để giao tiếp.

Một channel có thể được đóng bằng hàm dựng sẵn close. Dạng gán đa giá trị của toán tử nhận báo cáo liệu một giá trị nhận được có được gửi trước khi channel được đóng hay không.

Một channel duy nhất có thể được sử dụng trong câu lệnh gửi, phép toán nhận, và các lời gọi đến các hàm dựng sẵn caplen bởi bất kỳ số lượng goroutine nào mà không cần đồng bộ hóa thêm. Channel hoạt động như hàng đợi vào trước ra trước (FIFO). Ví dụ, nếu một goroutine gửi các giá trị trên một channel và một goroutine thứ hai nhận chúng, các giá trị được nhận theo thứ tự được gửi.

Thuộc tính của kiểu và giá trị

Biểu diễn của giá trị

Giá trị của các kiểu được khai báo trước (xem bên dưới đối với các interface anyerror), mảng, và struct là độc lập: Mỗi giá trị như vậy chứa một bản sao hoàn chỉnh của tất cả dữ liệu của nó, và biến của các kiểu đó lưu trữ toàn bộ giá trị. Ví dụ, một biến mảng cung cấp bộ nhớ lưu trữ (các biến) cho tất cả phần tử của mảng. Các giá trị không tương ứng là đặc trưng cho kiểu của giá trị; chúng không bao giờ là nil.

Giá trị con trỏ, hàm, slice, map và channel khác nil chứa các tham chiếu đến dữ liệu nền có thể được chia sẻ bởi nhiều giá trị:

Một giá trị interface có thể độc lập hoặc chứa các tham chiếu đến dữ liệu nền tùy thuộc vào kiểu động của interface. Định danh được khai báo trước nil là giá trị không cho các kiểu mà giá trị của chúng có thể chứa tham chiếu.

Khi nhiều giá trị chia sẻ dữ liệu nền, thay đổi một giá trị có thể thay đổi giá trị khác. Ví dụ, thay đổi một phần tử của một slice sẽ thay đổi phần tử đó trong mảng nền cho tất cả các slice chia sẻ mảng đó.

Kiểu nền

Mỗi kiểu T có một kiểu nền: Nếu T là một trong các kiểu boolean, số học, hay chuỗi được khai báo trước, hoặc là một literal kiểu, thì kiểu nền tương ứng là chính T. Ngược lại, kiểu nền của T là kiểu nền của kiểu mà T tham chiếu đến trong khai báo của nó. Đối với một tham số kiểu thì kiểu nền là ràng buộc kiểu của nó, luôn luôn là một interface.

type (
	A1 = string
	A2 = A1
)

type (
	B1 string
	B2 B1
	B3 []B1
	B4 B3
)

func f[P any](x P) { … }

Kiểu nền của string, A1, A2, B1, và B2string. Kiểu nền của []B1, B3, và B4[]B1. Kiểu nền của Pinterface{}.

Đồng nhất kiểu

Hai kiểu hoặc là đồng nhất ("giống nhau") hoặc là khác nhau.

Một kiểu có tên luôn luôn khác với bất kỳ kiểu nào khác. Ngược lại, hai kiểu là đồng nhất nếu các literal kiểu nền của chúng tương đương về mặt cấu trúc; nghĩa là, chúng có cùng cấu trúc literal và các thành phần tương ứng có các kiểu đồng nhất. Cụ thể:

Cho các khai báo sau

type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string

	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1

	C0 = B0
	D0[P1, P2 any] struct{ x P1; y P2 }
	E0 = D0[int, string]
)

các kiểu sau là đồng nhất:

A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5

B0 and C0
D0[int, string] and E0
[]int and []int
struct{ a, b *B5 } and struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5

B0B1 khác nhau vì chúng là các kiểu mới được tạo ra bởi các định nghĩa kiểu riêng biệt; func(int, float64) *B0func(x int, y float64) *[]string khác nhau vì B0 khác với []string; và P1P2 khác nhau vì chúng là các tham số kiểu khác nhau. D0[int, string]struct{ x int; y string } là khác nhau vì cái trước là một kiểu được định nghĩa đã được khởi tạo trong khi cái sau là một literal kiểu (nhưng chúng vẫn có thể gán cho nhau).

Khả năng gán

Một giá trị x có kiểu Vcó thể gán cho một biến có kiểu T ("x có thể gán cho T") nếu một trong các điều kiện sau được thỏa mãn:

Ngoài ra, nếu kiểu V của x hoặc T là tham số kiểu, x có thể gán cho một biến của kiểu T nếu một trong các điều kiện sau được thỏa mãn:

Khả năng biểu diễn

Một hằng số xcó thể biểu diễn bằng một giá trị của kiểu T, trong đó T không phải là một tham số kiểu, nếu một trong các điều kiện sau được thỏa mãn:

Nếu T là một tham số kiểu, x có thể biểu diễn bằng một giá trị của kiểu T nếu x có thể biểu diễn bằng một giá trị của mỗi kiểu trong tập hợp kiểu của T.

x                   T           x is representable by a value of T because

'a'                 byte        97 is in the set of byte values
97                  rune        rune is an alias for int32, and 97 is in the set of 32-bit integers
"foo"               string      "foo" is in the set of string values
1024                int16       1024 is in the set of 16-bit integers
42.0                byte        42 is in the set of unsigned 8-bit integers
1e10                uint64      10000000000 is in the set of unsigned 64-bit integers
2.718281828459045   float32     2.718281828459045 rounds to 2.7182817 which is in the set of float32 values
-1e-1000            float64     -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0
0i                  int         0 is an integer value
(42 + 0i)           float32     42.0 (with zero imaginary part) is in the set of float32 values
x                   T           x is not representable by a value of T because

0                   bool        0 is not in the set of boolean values
'a'                 string      'a' is a rune, it is not in the set of string values
1024                byte        1024 is not in the set of unsigned 8-bit integers
-1                  uint16      -1 is not in the set of unsigned 16-bit integers
1.1                 int         1.1 is not an integer value
42i                 float32     (0 + 42i) is not in the set of float32 values
1e1000              float64     1e1000 overflows to IEEE +Inf after rounding

Tập hợp phương thức

Tập hợp phương thức của một kiểu xác định các phương thức có thể được gọi trên một toán hạng của kiểu đó. Mỗi kiểu đều có một tập hợp phương thức (có thể rỗng) gắn với nó:

Các quy tắc bổ sung áp dụng cho struct (và con trỏ đến struct) chứa các trường được nhúng, như được mô tả trong phần về kiểu struct. Bất kỳ kiểu nào khác đều có tập hợp phương thức rỗng.

Trong một tập hợp phương thức, mỗi phương thức phải có tên phương thức duy nhất không phải là định danh rỗng.

Khối

Một khối là một dãy khai báo và câu lệnh (có thể rỗng) nằm trong cặp dấu ngoặc nhọn.

Block         = "{" StatementList "}" .
StatementList = { Statement ";" } .

Ngoài các khối tường minh trong mã nguồn, còn có các khối ẩn:

  1. Khối vũ trụ bao gồm tất cả văn bản nguồn Go.
  2. Mỗi gói có một khối gói chứa tất cả văn bản nguồn Go của gói đó.
  3. Mỗi tệp có một khối tệp chứa tất cả văn bản nguồn Go trong tệp đó.
  4. Mỗi câu lệnh "if", "for", và "switch" được xem như nằm trong khối ẩn riêng của nó.
  5. Mỗi mệnh đề trong câu lệnh "switch" hoặc "select" hoạt động như một khối ẩn.

Các khối lồng nhau và ảnh hưởng đến phạm vi.

Khai báo và phạm vi

Một khai báo liên kết một định danh không phải định danh rỗng với một hằng số, kiểu, tham số kiểu, biến, hàm, nhãn, hoặc gói. Mỗi định danh trong một chương trình phải được khai báo. Không được khai báo cùng một định danh hai lần trong cùng một khối, và không được khai báo một định danh ở cả khối tệp lẫn khối gói.

Định danh rỗng có thể được dùng như bất kỳ định danh nào khác trong một khai báo, nhưng nó không tạo ra ràng buộc và do đó không được khai báo. Trong khối gói, định danh init chỉ có thể được dùng cho các khai báo hàm init, và giống như định danh rỗng, nó không tạo ra ràng buộc mới.

Declaration  = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .

Phạm vi của một định danh được khai báo là phần văn bản nguồn mà trong đó định danh đó biểu thị hằng số, kiểu, biến, hàm, nhãn, hoặc gói được chỉ định.

Go được xác định phạm vi theo từ vựng sử dụng khối:

  1. Phạm vi của một định danh được khai báo trước là khối vũ trụ.
  2. Phạm vi của một định danh biểu thị một hằng số, kiểu, biến, hoặc hàm (nhưng không phải phương thức) được khai báo ở cấp cao nhất (bên ngoài bất kỳ hàm nào) là khối gói.
  3. Phạm vi của tên gói của một gói được import là khối tệp của tệp chứa khai báo import.
  4. Phạm vi của một định danh biểu thị receiver của phương thức, tham số hàm, hoặc biến kết quả là thân hàm.
  5. Phạm vi của một định danh biểu thị tham số kiểu của một hàm hoặc được khai báo bởi receiver của phương thức bắt đầu sau tên của hàm và kết thúc ở cuối thân hàm.
  6. Phạm vi của một định danh biểu thị tham số kiểu của một kiểu bắt đầu sau tên của kiểu và kết thúc ở cuối TypeSpec.
  7. Phạm vi của một định danh hằng số hoặc biến được khai báo bên trong một hàm bắt đầu ở cuối ConstSpec hoặc VarSpec (ShortVarDecl đối với khai báo biến ngắn) và kết thúc ở cuối khối trong cùng chứa nó.
  8. Phạm vi của một định danh kiểu được khai báo bên trong một hàm bắt đầu tại định danh trong TypeSpec và kết thúc ở cuối khối trong cùng chứa nó.

Một định danh được khai báo trong một khối có thể được khai báo lại trong một khối bên trong. Khi định danh của khai báo bên trong đang trong phạm vi, nó biểu thị thực thể được khai báo bởi khai báo bên trong.

Mệnh đề package không phải là một khai báo; tên gói không xuất hiện trong bất kỳ phạm vi nào. Mục đích của nó là để xác định các tệp thuộc cùng một gói và để chỉ định tên gói mặc định cho các khai báo import.

Phạm vi nhãn

Nhãn được khai báo bởi các câu lệnh có nhãn và được dùng trong các câu lệnh "break", "continue", và "goto". Việc định nghĩa một nhãn không bao giờ được dùng là bất hợp lệ. Trái với các định danh khác, nhãn không có phạm vi theo khối và không xung đột với các định danh không phải nhãn. Phạm vi của một nhãn là thân của hàm mà trong đó nó được khai báo và không bao gồm thân của bất kỳ hàm lồng nhau nào.

Định danh rỗng

Định danh rỗng được biểu diễn bằng ký tự gạch dưới _. Nó đóng vai trò là một trình giữ chỗ ẩn danh thay cho một định danh thông thường (không rỗng) và có ý nghĩa đặc biệt trong khai báo, như một toán hạng, và trong câu lệnh gán.

Định danh được khai báo trước

Các định danh sau được khai báo ngầm trong khối vũ trụ [Go 1.18] [Go 1.21]:

Types:
	any bool byte comparable
	complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr

Constants:
	true false iota

Zero value:
	nil

Functions:
	append cap clear close complex copy delete imag len
	make max min new panic print println real recover

Định danh được xuất

Một định danh có thể được xuất để cho phép truy cập vào nó từ một gói khác. Một định danh được xuất nếu cả hai điều kiện sau đây đều đúng:

  1. ký tự đầu tiên của tên định danh là một chữ cái viết hoa Unicode (danh mục ký tự Unicode Lu); và
  2. định danh đó được khai báo trong khối gói hoặc là một tên trường hoặc tên phương thức.

Tất cả các định danh khác đều không được xuất.

Tính duy nhất của định danh

Cho một tập hợp các định danh, một định danh được gọi là duy nhất nếu nó khác với mọi định danh khác trong tập hợp đó. Hai định danh khác nhau nếu chúng được viết khác nhau, hoặc nếu chúng xuất hiện trong các gói khác nhau và không được xuất. Ngược lại, chúng là giống nhau.

Khai báo hằng số

Một khai báo hằng số liên kết một danh sách các định danh (tên của các hằng số) với các giá trị của một danh sách biểu thức hằng. Số lượng định danh phải bằng số lượng biểu thức, và định danh thứ n ở bên trái được liên kết với giá trị của biểu thức thứ n ở bên phải.

ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .

IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .

Nếu có kiểu, tất cả các hằng số lấy kiểu được chỉ định, và các biểu thức phải có thể gán cho kiểu đó, và kiểu đó không được là tham số kiểu. Nếu kiểu bị bỏ qua, các hằng số lấy kiểu riêng lẻ của các biểu thức tương ứng. Nếu các giá trị biểu thức là các hằng số không có kiểu, các hằng số được khai báo vẫn không có kiểu và các định danh hằng số biểu thị các giá trị hằng số. Ví dụ, nếu biểu thức là một literal dấu phẩy động, định danh hằng số biểu thị một hằng số dấu phẩy động, ngay cả khi phần thập phân của literal bằng không.

const Pi float64 = 3.14159265358979323846
const zero = 0.0         // untyped floating-point constant
const (
	size int64 = 1024
	eof        = -1  // untyped integer constant
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", untyped integer and string constants
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

Trong một danh sách khai báo const có ngoặc đơn, danh sách biểu thức có thể được bỏ qua ở bất kỳ ConstSpec nào ngoại trừ cái đầu tiên. Danh sách rỗng như vậy tương đương với việc thay thế văn bản của danh sách biểu thức không rỗng đầu tiên trước đó và kiểu của nó nếu có. Do đó, việc bỏ qua danh sách biểu thức tương đương với việc lặp lại danh sách trước đó. Số lượng định danh phải bằng số lượng biểu thức trong danh sách trước đó. Cùng với bộ sinh hằng số iota, cơ chế này cho phép khai báo nhẹ nhàng các giá trị tuần tự:

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // this constant is not exported
)

Iota

Trong một khai báo hằng số, định danh được khai báo trước iota biểu thị các hằng số nguyên không có kiểu liên tiếp. Giá trị của nó là chỉ số của ConstSpec tương ứng ConstSpec trong khai báo hằng số đó, bắt đầu từ không. Nó có thể được dùng để xây dựng một tập hợp các hằng số liên quan:

const (
	c0 = iota  // c0 == 0
	c1 = iota  // c1 == 1
	c2 = iota  // c2 == 2
)

const (
	a = 1 << iota  // a == 1  (iota == 0)
	b = 1 << iota  // b == 2  (iota == 1)
	c = 3          // c == 3  (iota == 2, unused)
	d = 1 << iota  // d == 8  (iota == 3)
)

const (
	u         = iota * 42  // u == 0     (untyped integer constant)
	v float64 = iota * 42  // v == 42.0  (float64 constant)
	w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0
const y = iota  // y == 0

Theo định nghĩa, nhiều lần dùng iota trong cùng một ConstSpec đều có cùng giá trị:

const (
	bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
	bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
	_, _                                  //                        (iota == 2, unused)
	bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
)

Ví dụ cuối này khai thác sự lặp lại ngầm của danh sách biểu thức không rỗng cuối cùng.

Khai báo kiểu

Một khai báo kiểu liên kết một định danh, tên kiểu, với một kiểu. Khai báo kiểu có hai dạng: khai báo alias và định nghĩa kiểu.

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

Khai báo alias

Một khai báo alias liên kết một định danh với kiểu cho trước [Go 1.9].

AliasDecl = identifier [ TypeParameters ] "=" Type .

Trong phạm vi của định danh đó, nó đóng vai trò là một alias cho kiểu cho trước.

type (
	nodeList = []*Node  // nodeList and []*Node are identical types
	Polar    = polar    // Polar and polar denote identical types
)

Nếu khai báo alias chỉ định tham số kiểu [Go 1.24], tên kiểu biểu thị một alias chung. Các alias chung phải được khởi tạo khi chúng được sử dụng.

type set[P comparable] = map[P]bool

Trong một khai báo alias, kiểu cho trước không thể là tham số kiểu được khai báo trong cùng khai báo đó.

type A[P any] = P   // illegal: P is a type parameter declared in the declaration of A

func f[P any]() {
	type A = P  // ok: T is a type parameter declared by the enclosing function
}

Định nghĩa kiểu

Một định nghĩa kiểu tạo ra một kiểu mới, khác biệt với cùng kiểu nền và các phép toán như kiểu cho trước và liên kết một định danh, tên kiểu, với nó.

TypeDef = identifier [ TypeParameters ] Type .

Kiểu mới được gọi là kiểu được định nghĩa. Nó khác với bất kỳ kiểu nào khác, kể cả kiểu mà nó được tạo ra từ đó.

type (
	Point struct{ x, y float64 }  // Point and struct{ x, y float64 } are different types
	polar Point                   // polar and Point denote different types
)

type TreeNode struct {
	left, right *TreeNode
	value any
}

type Block interface {
	BlockSize() int
	Encrypt(src, dst []byte)
	Decrypt(src, dst []byte)
}

Một kiểu được định nghĩa có thể có các phương thức gắn với nó. Nó không kế thừa bất kỳ phương thức nào được liên kết với kiểu cho trước, nhưng tập hợp phương thức của kiểu interface hoặc của các phần tử của kiểu tổng hợp vẫn không thay đổi:

// A Mutex is a data type with two methods, Lock and Unlock.
type Mutex struct         { /* Mutex fields */ }
func (m *Mutex) Lock()    { /* Lock implementation */ }
func (m *Mutex) Unlock()  { /* Unlock implementation */ }

// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex

// The method set of PtrMutex's underlying type *Mutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex

// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct {
	Mutex
}

// MyBlock is an interface type that has the same method set as Block.
type MyBlock Block

Các định nghĩa kiểu có thể được dùng để định nghĩa các kiểu boolean, số học, hoặc chuỗi khác nhau và gắn các phương thức với chúng:

type TimeZone int

const (
	EST TimeZone = -(5 + iota)
	CST
	MST
	PST
)

func (tz TimeZone) String() string {
	return fmt.Sprintf("GMT%+dh", tz)
}

Nếu định nghĩa kiểu chỉ định tham số kiểu, tên kiểu biểu thị một kiểu chung. Các kiểu chung phải được khởi tạo khi chúng được sử dụng.

type List[T any] struct {
	next  *List[T]
	value T
}

Trong một định nghĩa kiểu, kiểu cho trước không thể là tham số kiểu.

type T[P any] P    // illegal: P is a type parameter

func f[P any]() {
	type L P   // illegal: P is a type parameter declared by the enclosing function
}

Một kiểu chung cũng có thể có các phương thức gắn với nó. Trong trường hợp này, các receiver của phương thức phải khai báo cùng số lượng tham số kiểu như có trong định nghĩa kiểu chung.

// The method Len returns the number of elements in the linked list l.
func (l *List[T]) Len() int  { … }

Khai báo tham số kiểu

Một danh sách tham số kiểu khai báo các tham số kiểu của một hàm chung hoặc khai báo kiểu chung. Danh sách tham số kiểu trông giống như một danh sách tham số hàm thông thường ngoại trừ tên tham số kiểu phải đều có mặt và danh sách được bao bởi dấu ngoặc vuông thay vì dấu ngoặc đơn [Go 1.18].

TypeParameters = "[" TypeParamList [ "," ] "]" .
TypeParamList  = TypeParamDecl { "," TypeParamDecl } .
TypeParamDecl  = IdentifierList TypeConstraint .

Tất cả các tên không rỗng trong danh sách phải là duy nhất. Mỗi tên khai báo một tham số kiểu, là một kiểu có tên mới và khác biệt hoạt động như một trình giữ chỗ cho một kiểu (chưa biết) trong khai báo. Tham số kiểu được thay thế bằng một tham số kiểu thực khi khởi tạo hàm chung hoặc kiểu chung.

[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]

Cũng như mỗi tham số hàm thông thường có kiểu tham số, mỗi tham số kiểu có một (meta-)kiểu tương ứng được gọi là ràng buộc kiểu.

Một sự mơ hồ khi phân tích phát sinh khi danh sách tham số kiểu cho một kiểu chung khai báo một tham số kiểu duy nhất P với ràng buộc C sao cho văn bản P C tạo thành một biểu thức hợp lệ:

type T[P *C] …
type T[P (C)] …
type T[P *C|Q] …
…

Trong những trường hợp hiếm gặp này, danh sách tham số kiểu không thể phân biệt với một biểu thức và khai báo kiểu được phân tích như một khai báo kiểu mảng. Để giải quyết sự mơ hồ, nhúng ràng buộc vào một interface hoặc dùng dấu phẩy ở cuối:

type T[P interface{*C}] …
type T[P *C,] …

Tham số kiểu cũng có thể được khai báo bởi đặc tả receiver của một khai báo phương thức được liên kết với một kiểu chung.

Ràng buộc kiểu

Một ràng buộc kiểu là một interface định nghĩa tập hợp các tham số kiểu thực được phép cho tham số kiểu tương ứng và kiểm soát các phép toán được hỗ trợ bởi các giá trị của tham số kiểu đó [Go 1.18].

TypeConstraint = TypeElem .

Nếu ràng buộc là một interface literal dạng interface{E} trong đó E là một phần tử kiểu được nhúng (không phải phương thức), trong danh sách tham số kiểu phần bao bọc interface{ … } có thể được bỏ qua cho tiện lợi:

[T []P]                      // = [T interface{[]P}]
[T ~int]                     // = [T interface{~int}]
[T int|string]               // = [T interface{int|string}]
type Constraint ~int         // illegal: ~int is not in a type parameter list

Kiểu interface comparable được khai báo trước biểu thị tập hợp tất cả các kiểu không phải interface có thể so sánh nghiêm ngặt [Go 1.18].

Mặc dù các interface không phải tham số kiểu là có thể so sánh, chúng không có thể so sánh nghiêm ngặt và do đó chúng không hiện thực hóa comparable. Tuy nhiên, chúng thỏa mãn comparable.

int                          // implements comparable (int is strictly comparable)
[]byte                       // does not implement comparable (slices cannot be compared)
interface{}                  // does not implement comparable (see above)
interface{ ~int | ~string }  // type parameter only: implements comparable (int, string types are strictly comparable)
interface{ comparable }      // type parameter only: implements comparable (comparable implements itself)
interface{ ~int | ~[]byte }  // type parameter only: does not implement comparable (slices are not comparable)
interface{ ~struct{ any } }  // type parameter only: does not implement comparable (field any is not strictly comparable)

Interface comparable và các interface (trực tiếp hoặc gián tiếp) nhúng comparable chỉ có thể được dùng như các ràng buộc kiểu. Chúng không thể là kiểu của các giá trị hay biến, hoặc là thành phần của các kiểu không phải interface khác.

Thỏa mãn ràng buộc kiểu

Một tham số kiểu thực T thỏa mãn một ràng buộc kiểu C nếu T là một phần tử của tập hợp kiểu được định nghĩa bởi C; nói cách khác, nếu T hiện thực hóa C. Là một ngoại lệ, một ràng buộc kiểu có thể so sánh nghiêm ngặt cũng có thể được thỏa mãn bởi một tham số kiểu thực có thể so sánh (không nhất thiết phải có thể so sánh nghiêm ngặt) [Go 1.20]. Cụ thể hơn:

Một kiểu T thỏa mãn ràng buộc C nếu

type argument      type constraint                // constraint satisfaction

int                interface{ ~int }              // satisfied: int implements interface{ ~int }
string             comparable                     // satisfied: string implements comparable (string is strictly comparable)
[]byte             comparable                     // not satisfied: slices are not comparable
any                interface{ comparable; int }   // not satisfied: any does not implement interface{ int }
any                comparable                     // satisfied: any is comparable and implements the basic interface any
struct{f any}      comparable                     // satisfied: struct{f any} is comparable and implements the basic interface any
any                interface{ comparable; m() }   // not satisfied: any does not implement the basic interface interface{ m() }
interface{ m() }   interface{ comparable; m() }   // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }

Do ngoại lệ trong quy tắc thỏa mãn ràng buộc, việc so sánh các toán hạng có kiểu tham số kiểu có thể gây panic lúc chạy (ngay cả khi các tham số kiểu có thể so sánh luôn luôn có thể so sánh nghiêm ngặt).

Khai báo biến

Một khai báo biến tạo ra một hoặc nhiều biến, liên kết các định danh tương ứng với chúng, và gán cho mỗi biến một kiểu và một giá trị khởi tạo.

VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
	i       int
	u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]  // map lookup; only interested in "found"

Nếu một danh sách biểu thức được cho, các biến được khởi tạo với các biểu thức theo các quy tắc của câu lệnh gán. Ngược lại, mỗi biến được khởi tạo về giá trị không của nó.

Nếu có kiểu, mỗi biến được gán kiểu đó. Ngược lại, mỗi biến được gán kiểu của giá trị khởi tạo tương ứng trong phép gán. Nếu giá trị đó là một hằng số không có kiểu, nó trước tiên được chuyển đổi ngầm sang kiểu mặc định của nó; nếu là một giá trị boolean không có kiểu, nó trước tiên được chuyển đổi ngầm sang kiểu bool. Định danh được khai báo trước nil không thể được dùng để khởi tạo một biến không có kiểu tường minh.

var d = math.Sin(0.5)  // d is float64
var i = 42             // i is int
var t, ok = x.(T)      // t is T, ok is bool
var n = nil            // illegal

Giới hạn cài đặt: Một trình biên dịch có thể làm cho việc khai báo một biến bên trong một thân hàm là bất hợp lệ nếu biến đó không bao giờ được dùng.

Khai báo biến ngắn

Một khai báo biến ngắn sử dụng cú pháp:

ShortVarDecl = IdentifierList ":=" ExpressionList .

Đây là cách viết tắt của một khai báo biến thông thường với các biểu thức khởi tạo nhưng không có kiểu:

"var" IdentifierList "=" ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe()  // os.Pipe() returns a connected pair of Files and an error, if any
_, y, _ := coord(p)   // coord() returns three values; only interested in y coordinate

Không giống như khai báo biến thông thường, một khai báo biến ngắn có thể khai báo lại các biến miễn là chúng đã được khai báo trước đó trong cùng một khối (hoặc danh sách tham số nếu khối là thân hàm) với cùng kiểu, và ít nhất một trong các biến không phải định danh rỗng là mới. Kết quả là, khai báo lại chỉ có thể xuất hiện trong một khai báo ngắn đa biến. Khai báo lại không tạo ra biến mới; nó chỉ gán một giá trị mới cho biến gốc. Tên biến không rỗng ở bên trái của := phải là duy nhất.

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // redeclares offset
x, y, x := 1, 2, 3                        // illegal: x repeated on left side of :=

Khai báo biến ngắn chỉ có thể xuất hiện bên trong các hàm. Trong một số ngữ cảnh như các bộ khởi tạo cho các câu lệnh "if", "for", hoặc "switch", chúng có thể được dùng để khai báo các biến tạm cục bộ.

Khai báo hàm

Một khai báo hàm liên kết một định danh, tên hàm, với một hàm.

FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .

Nếu chữ ký của hàm khai báo các tham số kết quả, danh sách câu lệnh của thân hàm phải kết thúc bằng một câu lệnh kết thúc.

func IndexRune(s string, r rune) int {
	for i, c := range s {
		if c == r {
			return i
		}
	}
	// invalid: missing return statement
}

Nếu khai báo hàm chỉ định tham số kiểu, tên hàm biểu thị một hàm chung. Một hàm chung phải được khởi tạo trước khi nó có thể được gọi hoặc dùng như một giá trị.

func min[T ~int|~float64](x, y T) T {
	if x < y {
		return x
	}
	return y
}

Một khai báo hàm không có tham số kiểu có thể bỏ qua thân hàm. Khai báo như vậy cung cấp chữ ký cho một hàm được cài đặt bên ngoài Go, chẳng hạn như một thủ tục assembly.

func flushICache(begin, end uintptr)  // implemented externally

Khai báo phương thức

Một phương thức là một hàmreceiver. Một khai báo phương thức liên kết một định danh, tên phương thức, với một phương thức, và liên kết phương thức với kiểu cơ sở của receiver.

MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver   = Parameters .

Receiver được chỉ định thông qua một phần tham số bổ sung đứng trước tên phương thức. Phần tham số đó phải khai báo một tham số duy nhất không phải variadic, là receiver. Kiểu của nó phải là một kiểu được định nghĩa T hoặc là con trỏ đến một kiểu được định nghĩa T, có thể theo sau bởi danh sách tên tham số kiểu [P1, P2, …] được bao trong dấu ngoặc vuông. T được gọi là kiểu cơ sở của receiver. Kiểu cơ sở receiver không thể là kiểu con trỏ hay interface và nó phải được định nghĩa trong cùng gói với phương thức. Phương thức được gọi là được liên kết với kiểu cơ sở receiver của nó và tên phương thức chỉ có thể nhìn thấy trong các selector cho kiểu T hoặc *T.

Một định danh receiver không phải định danh rỗng phải là duy nhất trong chữ ký phương thức. Nếu giá trị của receiver không được tham chiếu bên trong thân phương thức, định danh của nó có thể được bỏ qua trong khai báo. Điều tương tự áp dụng nói chung cho các tham số của hàm và phương thức.

Đối với một kiểu cơ sở, các tên không rỗng của các phương thức được liên kết với nó phải là duy nhất. Nếu kiểu cơ sở là một kiểu struct, các tên phương thức và trường không rỗng phải khác biệt nhau.

Cho kiểu được định nghĩa Point thì các khai báo

func (p *Point) Length() float64 {
	return math.Sqrt(p.x * p.x + p.y * p.y)
}

func (p *Point) Scale(factor float64) {
	p.x *= factor
	p.y *= factor
}

liên kết các phương thức LengthScale, với kiểu receiver *Point, với kiểu cơ sở Point.

Nếu kiểu cơ sở receiver là một kiểu chung, thì đặc tả receiver phải khai báo các tham số kiểu tương ứng để phương thức có thể sử dụng. Điều này làm cho các tham số kiểu của receiver trở nên khả dụng với phương thức. Về mặt cú pháp, khai báo tham số kiểu này trông giống như một khởi tạo của kiểu cơ sở receiver: các tham số kiểu thực phải là các định danh biểu thị các tham số kiểu đang được khai báo, một cho mỗi tham số kiểu của kiểu cơ sở receiver. Tên tham số kiểu không cần phải khớp với tên tham số tương ứng trong định nghĩa kiểu cơ sở receiver, và tất cả các tên tham số không rỗng phải là duy nhất trong phần tham số receiver và chữ ký phương thức. Các ràng buộc tham số kiểu của receiver được ngầm hiểu từ định nghĩa kiểu cơ sở receiver: các tham số kiểu tương ứng có các ràng buộc tương ứng.

type Pair[A, B any] struct {
	a A
	b B
}

func (p Pair[A, B]) Swap() Pair[B, A]  { … }  // receiver declares A, B
func (p Pair[First, _]) First() First  { … }  // receiver declares First, corresponds to A in Pair

Nếu kiểu receiver được biểu thị bằng (con trỏ đến) một alias, alias đó không được là chung và không được biểu thị một kiểu chung đã được khởi tạo, dù trực tiếp hay gián tiếp qua một alias khác, và bất kể các mức độ gián tiếp con trỏ.

type GPoint[P any] = Point
type HPoint        = *GPoint[int]
type IPair         = Pair[int, int]

func (*GPoint[P]) Draw(P)   { … }  // illegal: alias must not be generic
func (HPoint) Draw(P)       { … }  // illegal: alias must not denote instantiated type GPoint[int]
func (*IPair) Second() int  { … }  // illegal: alias must not denote instantiated type Pair[int, int]

Biểu thức

Một biểu thức xác định phép tính một giá trị bằng cách áp dụng các toán tử và hàm lên các toán hạng.

Toán hạng

Toán hạng biểu thị các giá trị cơ bản trong một biểu thức. Một toán hạng có thể là một literal, một định danh không phải blank (có thể đầy đủ) biểu thị một hằng số, biến, hoặc hàm, hoặc một biểu thức được đặt trong ngoặc đơn.

Operand     = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .

Tên toán hạng biểu thị một hàm generic có thể được theo sau bởi danh sách các đối số kiểu; kết quả toán hạng là một hàm đã được khởi tạo.

Định danh blank chỉ có thể xuất hiện như một toán hạng ở phía bên trái của một câu lệnh gán.

Giới hạn thực thi: Trình biên dịch không cần báo lỗi nếu kiểu của một toán hạng là một tham số kiểu với một tập hợp kiểu rỗng. Các hàm với tham số kiểu như vậy không thể được khởi tạo; bất kỳ nỗ lực nào cũng sẽ dẫn đến lỗi tại vị trí khởi tạo.

Định danh đầy đủ

Một định danh đầy đủ là một định danh được bổ nghĩa bởi tiền tố tên gói. Cả tên gói lẫn định danh đều không được là blank.

QualifiedIdent = PackageName "." identifier .

Một định danh đầy đủ truy cập một định danh trong một gói khác, gói đó phải được import. Định danh phải được xuất và khai báo trong khối gói của gói đó.

math.Sin // denotes the Sin function in package math

Composite literal

Composite literal tạo ra các giá trị mới cho struct, array, slice và map mỗi lần chúng được đánh giá. Chúng bao gồm kiểu của literal theo sau là một danh sách các phần tử được bao bởi dấu ngoặc nhọn (có thể rỗng). Mỗi phần tử có thể tùy chọn được đứng trước bởi một khóa tương ứng.

CompositeLit = LiteralType LiteralValue .
LiteralType  = StructType | ArrayType | "[" "..." "]" ElementType |
               SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList  = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key          = FieldName | Expression | LiteralValue .
FieldName    = identifier .
Element      = Expression | LiteralValue .

Trừ khi LiteralType là một tham số kiểu, kiểu cơ bản của nó phải là kiểu struct, array, slice hoặc map (cú pháp thực thi ràng buộc này trừ khi kiểu được cho dưới dạng TypeName). Nếu LiteralType là tham số kiểu, tất cả các kiểu trong tập hợp kiểu của nó phải có cùng kiểu cơ bản, và kiểu đó phải là một kiểu composite literal hợp lệ.

Các kiểu của phần tử và khóa phải có thể gán cho các trường, phần tử và kiểu khóa tương ứng của LiteralType; không có chuyển đổi bổ sung nào. Khóa được hiểu là tên trường đối với struct literal, một chỉ số đối với array và slice literal, và một khóa đối với map literal. Đây là lỗi khi chỉ định nhiều phần tử có cùng tên trường hoặc giá trị khóa hằng số. Một literal có thể bỏ qua danh sách phần tử; một literal như vậy được đánh giá theo giá trị zero cho kiểu của nó.

Sự mơ hồ trong phân tích xuất hiện khi một composite literal sử dụng dạng TypeName của LiteralType xuất hiện như một toán hạng giữa từ khóa và dấu ngoặc nhọn mở của khối của câu lệnh "if", "for" hay "switch", và composite literal không được bao trong ngoặc đơn, ngoặc vuông hay dấu ngoặc nhọn. Trong trường hợp hiếm gặp này, dấu ngoặc nhọn mở của literal bị phân tích nhầm là dấu bắt đầu khối câu lệnh. Để giải quyết sự mơ hồ, composite literal phải xuất hiện trong ngoặc đơn.

if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }

Struct literal

Đối với struct literal không có khóa, danh sách phần tử phải chứa một phần tử cho mỗi trường của struct theo thứ tự khai báo của các trường.

Đối với struct literal có khóa, các quy tắc sau áp dụng:

Cho các khai báo

type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

người ta có thể viết

origin := Point3D{}                            // zero value for Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}}  // zero value for line.q.x

Array và slice literal

Đối với array và slice literal, các quy tắc sau áp dụng:

Lấy địa chỉ của một composite literal tạo ra một con trỏ đến một biến duy nhất được khởi tạo với giá trị của literal.

var pointer *Point3D = &Point3D{y: 1000}

Lưu ý rằng giá trị zero cho kiểu slice hoặc map không giống với một giá trị đã khởi tạo nhưng rỗng của cùng kiểu đó. Do đó, lấy địa chỉ của một slice hoặc map composite literal rỗng không có hiệu ứng tương tự như cấp phát một giá trị slice hoặc map mới với new.

p1 := &[]int{}    // p1 points to an initialized, empty slice with value []int{} and length 0
p2 := new([]int)  // p2 points to an uninitialized slice with value nil and length 0

Độ dài của một array literal là độ dài được chỉ định trong kiểu literal. Nếu ít phần tử hơn độ dài được cung cấp trong literal, các phần tử còn thiếu được đặt thành giá trị zero cho kiểu phần tử array. Đây là lỗi khi cung cấp các phần tử với giá trị chỉ số nằm ngoài phạm vi chỉ số của array. Ký hiệu ... chỉ định độ dài array bằng chỉ số phần tử lớn nhất cộng một.

buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

Một slice literal mô tả toàn bộ array literal cơ bản. Do đó độ dài và dung lượng của một slice literal là chỉ số phần tử lớn nhất cộng một. Một slice literal có dạng

[]T{x1, x2, … xn}

và là viết tắt cho một thao tác cắt lát áp dụng lên một array:

tmp := [n]T{x1, x2, … xn}
tmp[0 : n]

Map literal

Đối với map literal, mỗi phần tử phải có một khóa. Đối với các khóa map không phải hằng số, xem phần về thứ tự đánh giá.

Lược bỏ kiểu phần tử

Trong một composite literal của kiểu array, slice hoặc map T, các phần tử hoặc khóa map mà bản thân chúng là composite literal có thể lược bỏ kiểu literal tương ứng nếu nó giống với kiểu phần tử hoặc kiểu khóa của T. Tương tự, các phần tử hoặc khóa là địa chỉ của composite literal có thể lược bỏ &T khi kiểu phần tử hoặc kiểu khóa là *T.

[...]Point{{1.5, -3.5}, {0, 0}}     // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}          // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}}         // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}}    // same as map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"}    // same as map[Point]string{Point{0, 0}: "orig"}

type PPoint *Point
[2]*Point{{1.5, -3.5}, {}}          // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}}          // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

Các ví dụ về array, slice và map literal hợp lệ:

// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}

// the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}

// frequencies in Hz for equal-tempered scale (A4 = 440Hz)
noteFrequency := map[string]float32{
	"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
	"G0": 24.50, "A0": 27.50, "B0": 30.87,
}

Function literal

Một function literal đại diện cho một hàm ẩn danh. Function literal không thể khai báo tham số kiểu.

FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }

Một function literal có thể được gán cho một biến hoặc được gọi trực tiếp.

f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)

Function literal là closure: chúng có thể tham chiếu đến các biến được định nghĩa trong hàm bao quanh. Các biến đó sau đó được chia sẻ giữa hàm bao quanh và function literal, và chúng tồn tại miễn là chúng còn có thể truy cập được.

Biểu thức chính

Biểu thức chính là các toán hạng cho biểu thức một ngôi và hai ngôi.

PrimaryExpr   = Operand |
                Conversion |
                MethodExpr |
                PrimaryExpr Selector |
                PrimaryExpr Index |
                PrimaryExpr Slice |
                PrimaryExpr TypeAssertion |
                PrimaryExpr Arguments .

Selector      = "." identifier .
Index         = "[" Expression [ "," ] "]" .
Slice         = "[" [ Expression ] ":" [ Expression ] "]" |
                "[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion = "." "(" Type ")" .
Arguments     = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()

Selector

Đối với một biểu thức chính x không phải là tên gói, biểu thức selector

x.f

biểu thị trường hoặc phương thức f của giá trị x (hoặc đôi khi *x; xem bên dưới). Định danh f được gọi là selector (trường hoặc phương thức); nó không được là định danh blank. Kiểu của biểu thức selector là kiểu của f. Nếu x là tên gói, xem phần về định danh đầy đủ.

Một selector f có thể biểu thị một trường hoặc phương thức f của kiểu T, hoặc nó có thể tham chiếu đến một trường hoặc phương thức f của một trường nhúng lồng nhau của T. Số lượng trường nhúng được đi qua để đến f được gọi là độ sâu của nó trong T. Độ sâu của trường hoặc phương thức f được khai báo trong T là không. Độ sâu của trường hoặc phương thức f được khai báo trong một trường nhúng A trong T là độ sâu của f trong A cộng một.

Các quy tắc sau áp dụng cho selector:

  1. Đối với một giá trị x của kiểu T hoặc *T trong đó T không phải là kiểu con trỏ hoặc interface, x.f biểu thị trường hoặc phương thức ở độ sâu nông nhất trong T mà có f như vậy. Nếu không có đúng một f với độ sâu nông nhất, biểu thức selector là bất hợp lệ.
  2. Đối với một giá trị x của kiểu I trong đó I là kiểu interface, x.f biểu thị phương thức thực tế có tên f của giá trị động của x. Nếu không có phương thức nào có tên f trong tập hợp phương thức của I, biểu thức selector là bất hợp lệ.
  3. Là một ngoại lệ, nếu kiểu của x là kiểu con trỏ được định nghĩa(*x).f là biểu thức selector hợp lệ biểu thị một trường (nhưng không phải phương thức), x.f là viết tắt của (*x).f.
  4. Trong tất cả các trường hợp khác, x.f là bất hợp lệ.
  5. Nếu x là kiểu con trỏ và có giá trị nilx.f biểu thị một trường struct, gán cho hoặc đánh giá x.f gây ra một panic lúc chạy.
  6. Nếu x là kiểu interface và có giá trị nil, gọi hoặc đánh giá phương thức x.f gây ra một panic lúc chạy.

Ví dụ, cho các khai báo:

type T0 struct {
	x int
}

func (*T0) M0()

type T1 struct {
	y int
}

func (T1) M1()

type T2 struct {
	z int
	T1
	*T0
}

func (*T2) M2()

type Q *T2

var t T2     // with t.T0 != nil
var p *T2    // with p != nil and (*p).T0 != nil
var q Q = p

người ta có thể viết:

t.z          // t.z
t.y          // t.T1.y
t.x          // (*t.T0).x

p.z          // (*p).z
p.y          // (*p).T1.y
p.x          // (*(*p).T0).x

q.x          // (*(*q).T0).x        (*q).x is a valid field selector

p.M0()       // ((*p).T0).M0()      M0 expects *T0 receiver
p.M1()       // ((*p).T1).M1()      M1 expects T1 receiver
p.M2()       // p.M2()              M2 expects *T2 receiver
t.M2()       // (&t).M2()           M2 expects *T2 receiver, see section on Calls

nhưng những điều sau là không hợp lệ:

q.M0()       // (*q).M0 is valid but not a field selector

Biểu thức phương thức

Nếu M thuộc tập hợp phương thức của kiểu T, T.M là một hàm có thể được gọi như một hàm thông thường với cùng các đối số như M được thêm vào trước bởi một đối số bổ sung là receiver của phương thức.

MethodExpr   = ReceiverType "." MethodName .
ReceiverType = Type .

Xem xét một kiểu struct T với hai phương thức, Mv, có receiver kiểu T, và Mp, có receiver kiểu *T.

type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T

Biểu thức

T.Mv

tạo ra một hàm tương đương với Mv nhưng với một receiver rõ ràng là đối số đầu tiên của nó; nó có chữ ký

func(tv T, a int) int

Hàm đó có thể được gọi bình thường với một receiver rõ ràng, vì vậy năm lần gọi này là tương đương:

t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

Tương tự, biểu thức

(*T).Mp

tạo ra một giá trị hàm đại diện cho Mp với chữ ký

func(tp *T, f float32) float32

Đối với một phương thức với value receiver, người ta có thể dẫn xuất một hàm với một pointer receiver rõ ràng, vì vậy

(*T).Mv

tạo ra một giá trị hàm đại diện cho Mv với chữ ký

func(tv *T, a int) int

Một hàm như vậy gián tiếp qua receiver để tạo ra một giá trị để truyền như receiver cho phương thức cơ bản; phương thức không ghi đè giá trị mà địa chỉ được truyền vào trong lời gọi hàm.

Trường hợp cuối cùng, một hàm value-receiver cho một phương thức pointer-receiver, là bất hợp lệ vì các phương thức pointer-receiver không thuộc tập hợp phương thức của kiểu giá trị.

Các giá trị hàm được dẫn xuất từ phương thức được gọi với cú pháp gọi hàm; receiver được cung cấp như đối số đầu tiên của lời gọi. Nghĩa là, cho f := T.Mv, Câu lệnh

Câu lệnh điều khiển việc thực thi.

Statement  = Declaration | LabeledStmt | SimpleStmt |
             GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
             FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
             DeferStmt .

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .

Câu lệnh kết thúc

Một câu lệnh kết thúc làm gián đoạn luồng điều khiển thông thường trong một khối. Các câu lệnh sau đây là câu lệnh kết thúc:

  1. Một câu lệnh "return" hoặc "goto".
  2. Một lời gọi đến hàm dựng sẵn panic.
  3. Một khối trong đó danh sách câu lệnh kết thúc bằng một câu lệnh kết thúc.
  4. Một câu lệnh "if" trong đó:
    • nhánh "else" có mặt, và
    • cả hai nhánh đều là câu lệnh kết thúc.
  5. Một câu lệnh "for" trong đó:
    • không có câu lệnh "break" nào tham chiếu đến câu lệnh "for" đó, và
    • điều kiện vòng lặp vắng mặt, và
    • câu lệnh "for" không sử dụng mệnh đề range.
  6. Một câu lệnh "switch" trong đó:
    • không có câu lệnh "break" nào tham chiếu đến câu lệnh "switch" đó,
    • có một trường hợp default, và
    • danh sách câu lệnh trong mỗi case, bao gồm cả default, kết thúc bằng một câu lệnh kết thúc, hoặc một câu lệnh "fallthrough" có thể có nhãn.
  7. Một câu lệnh "select" trong đó:
    • không có câu lệnh "break" nào tham chiếu đến câu lệnh "select" đó, và
    • danh sách câu lệnh trong mỗi case, bao gồm cả default nếu có, kết thúc bằng một câu lệnh kết thúc.
  8. Một câu lệnh có nhãn gán nhãn cho một câu lệnh kết thúc.

Tất cả các câu lệnh khác không phải là câu lệnh kết thúc.

Một danh sách câu lệnh kết thúc bằng một câu lệnh kết thúc nếu danh sách không rỗng và câu lệnh không rỗng cuối cùng của nó là câu lệnh kết thúc.

Câu lệnh rỗng

Câu lệnh rỗng không làm gì cả.

EmptyStmt = .

Câu lệnh có nhãn

Một câu lệnh có nhãn có thể là đích của câu lệnh goto, break hoặc continue.

LabeledStmt = Label ":" Statement .
Label       = identifier .
Error: log.Panic("error encountered")

Câu lệnh biểu thức

Ngoại trừ một số hàm dựng sẵn nhất định, các lời gọi hàm và phương thức cũng như thao tác nhận có thể xuất hiện trong ngữ cảnh câu lệnh. Các câu lệnh như vậy có thể được đặt trong ngoặc đơn.

ExpressionStmt = Expression .

Các hàm dựng sẵn sau đây không được phép trong ngữ cảnh câu lệnh:

append cap complex imag len make new real
unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // illegal if len is the built-in function

Câu lệnh gửi

Một câu lệnh gửi gửi một giá trị trên một channel. Biểu thức channel phải thuộc kiểu channel, hướng của channel phải cho phép thao tác gửi, và kiểu của giá trị được gửi phải có thể gán cho kiểu phần tử của channel.

SendStmt = Channel "<-" Expression .
Channel  = Expression .

Cả biểu thức channel lẫn biểu thức giá trị đều được định lượng trước khi quá trình giao tiếp bắt đầu. Giao tiếp bị chặn cho đến khi việc gửi có thể tiến hành. Một lần gửi trên channel không có bộ đệm có thể tiến hành nếu có một bên nhận sẵn sàng. Một lần gửi trên channel có bộ đệm có thể tiến hành nếu còn chỗ trống trong bộ đệm. Một lần gửi trên channel đã đóng sẽ gây ra panic lúc chạy. Một lần gửi trên channel nil bị chặn mãi mãi.

ch <- 3  // send value 3 to channel ch

Nếu kiểu của biểu thức channel là một tham số kiểu, tất cả các kiểu trong tập hợp kiểu của nó phải là kiểu channel cho phép thao tác gửi, tất cả phải có cùng kiểu phần tử, và kiểu của giá trị được gửi phải có thể gán cho kiểu phần tử đó.

Câu lệnh tăng/giảm

Các câu lệnh "++" và "--" tăng hoặc giảm toán hạng của chúng theo hằng số không có kiểu 1. Như với một phép gán, toán hạng phải có địa chỉ hoặc là một biểu thức chỉ mục map.

IncDecStmt = Expression ( "++" | "--" ) .

Các câu lệnh gán sau đây tương đương về mặt ngữ nghĩa:

IncDec statement    Assignment
x++                 x += 1
x--                 x -= 1

Câu lệnh gán

Một phép gán thay thế giá trị hiện tại được lưu trong một biến bằng một giá trị mới được xác định bởi một biểu thức. Một câu lệnh gán có thể gán một giá trị đơn cho một biến đơn, hoặc nhiều giá trị cho một số lượng biến tương ứng.

Assignment = ExpressionList assign_op ExpressionList .

assign_op  = [ add_op | mul_op ] "=" .

Mỗi toán hạng ở vế trái phải có địa chỉ, là một biểu thức chỉ mục map, hoặc (chỉ với phép gán =) là định danh rỗng. Các toán hạng có thể được đặt trong ngoặc đơn.

x = 1
*p = f()
a[i] = 23
(k) = <-ch  // same as: k = <-ch

Một phép gán có toán tử x op= y trong đó op là một toán tử số học nhị phân tương đương với x = x op (y) nhưng chỉ định lượng x một lần. Cấu trúc op= là một token đơn. Trong các phép gán có toán tử, cả danh sách biểu thức vế trái lẫn vế phải đều phải chứa đúng một biểu thức đơn trị, và biểu thức vế trái không được là định danh rỗng.

a[i] <<= 2
i &^= 1<<n

Một phép gán bộ giá trị gán từng phần tử của một thao tác đa trị cho một danh sách biến. Có hai dạng. Trong dạng thứ nhất, toán hạng vế phải là một biểu thức đa trị đơn chẳng hạn như một lời gọi hàm, một thao tác channel hoặc map, hoặc một khẳng định kiểu. Số lượng toán hạng ở vế trái phải khớp với số lượng giá trị. Ví dụ, nếu f là một hàm trả về hai giá trị,

x, y = f()

gán giá trị đầu tiên cho x và giá trị thứ hai cho y. Trong dạng thứ hai, số lượng toán hạng vế trái phải bằng số lượng biểu thức vế phải, mỗi biểu thức phải đơn trị, và biểu thức thứ n ở vế phải được gán cho toán hạng thứ n ở vế trái:

one, two, three = '一', '二', '三'

Định danh rỗng cung cấp một cách để bỏ qua các giá trị vế phải trong một phép gán:

_ = x       // evaluate x but ignore it
x, _ = f()  // evaluate f() but ignore second result value

Phép gán được thực hiện qua hai giai đoạn. Đầu tiên, các toán hạng của biểu thức chỉ mụcphép giải tham chiếu con trỏ (bao gồm cả phép giải tham chiếu con trỏ ngầm trong bộ chọn) ở vế trái và các biểu thức ở vế phải đều được định lượng theo thứ tự thông thường. Thứ hai, các phép gán được thực hiện theo thứ tự từ trái sang phải.

a, b = b, a  // exchange a and b

x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2  // set i = 1, x[0] = 2

i = 0
x[i], i = 2, 1  // set x[0] = 2, i = 1

x[0], x[0] = 1, 2  // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)

x[1], x[3] = 4, 5  // set x[1] = 4, then panic setting x[3] = 5.

type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7  // set x[2] = 6, then panic setting p.x = 7

i = 2
x = []int{3, 5, 7}
for i, x[i] = range x {  // set i, x[2] = 0, x[0]
	break
}
// after this loop, i == 0 and x is []int{3, 5, 3}

Trong các phép gán, mỗi giá trị phải có thể gán cho kiểu của toán hạng mà nó được gán vào, với các trường hợp đặc biệt sau:

  1. Bất kỳ giá trị có kiểu nào cũng có thể được gán cho định danh rỗng.
  2. Nếu một hằng số không có kiểu được gán cho một biến thuộc kiểu interface hoặc định danh rỗng, hằng số đó trước tiên sẽ được chuyển đổi ngầm sang kiểu mặc định của nó.
  3. Nếu một giá trị boolean không có kiểu được gán cho một biến thuộc kiểu interface hoặc định danh rỗng, nó trước tiên sẽ được chuyển đổi ngầm sang kiểu bool.

Khi một giá trị được gán cho một biến, chỉ có dữ liệu được lưu trong biến mới được thay thế. Nếu giá trị chứa một tham chiếu, phép gán sao chép tham chiếu nhưng không tạo bản sao của dữ liệu được tham chiếu (chẳng hạn như mảng nền của một slice).

var s1 = []int{1, 2, 3}
var s2 = s1                    // s2 stores the slice descriptor of s1
s1 = s1[:1]                    // s1's length is 1 but it still shares its underlying array with s2
s2[0] = 42                     // setting s2[0] changes s1[0] as well
fmt.Println(s1, s2)            // prints [42] [42 2 3]

var m1 = make(map[string]int)
var m2 = m1                    // m2 stores the map descriptor of m1
m1["foo"] = 42                 // setting m1["foo"] changes m2["foo"] as well
fmt.Println(m2["foo"])         // prints 42

Câu lệnh if

Câu lệnh "if" xác định việc thực thi có điều kiện của hai nhánh theo giá trị của một biểu thức boolean. Nếu biểu thức định lượng thành true, nhánh "if" được thực thi, ngược lại, nếu có mặt, nhánh "else" được thực thi.

IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {
	x = max
}

Biểu thức có thể được đứng trước bởi một câu lệnh đơn giản, câu lệnh đó được thực thi trước khi biểu thức được định lượng.

if x := f(); x < y {
	return x
} else if x > z {
	return z
} else {
	return y
}

Câu lệnh switch

Câu lệnh "switch" cung cấp khả năng thực thi theo nhiều hướng. Một biểu thức hoặc kiểu được so sánh với các "case" bên trong "switch" để xác định nhánh nào sẽ được thực thi.

SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .

Có hai dạng: switch biểu thức và switch kiểu. Trong switch biểu thức, các case chứa các biểu thức được so sánh với giá trị của biểu thức switch. Trong switch kiểu, các case chứa các kiểu được so sánh với kiểu của một biểu thức switch được chú thích đặc biệt. Biểu thức switch được định lượng đúng một lần trong một câu lệnh switch.

Switch biểu thức

Trong switch biểu thức, biểu thức switch được định lượng và các biểu thức case, không nhất thiết phải là hằng số, được định lượng từ trái sang phải và từ trên xuống dưới; biểu thức đầu tiên bằng với biểu thức switch sẽ kích hoạt thực thi các câu lệnh của case tương ứng; các case khác bị bỏ qua. Nếu không có case nào khớp và có case "default", các câu lệnh của nó được thực thi. Có thể có nhiều nhất một case default và nó có thể xuất hiện ở bất kỳ đâu trong câu lệnh "switch". Một biểu thức switch vắng mặt tương đương với giá trị boolean true.

ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .

Nếu biểu thức switch định lượng thành một hằng số không có kiểu, nó trước tiên được chuyển đổi ngầm sang kiểu mặc định của nó. Giá trị không có kiểu được khai báo trước nil không thể được dùng làm biểu thức switch. Kiểu của biểu thức switch phải có thể so sánh.

Nếu một biểu thức case không có kiểu, nó trước tiên được chuyển đổi ngầm sang kiểu của biểu thức switch. Với mỗi biểu thức case x (có thể đã được chuyển đổi) và giá trị t của biểu thức switch, x == t phải là một phép so sánh hợp lệ.

Nói cách khác, biểu thức switch được xử lý như thể nó được dùng để khai báo và khởi tạo một biến tạm thời t không có kiểu tường minh; chính giá trị của t đó được kiểm tra bằng với từng biểu thức case x.

Trong một mệnh đề case hoặc default, câu lệnh không rỗng cuối cùng có thể là một câu lệnh "fallthrough" (có thể có nhãn) để chỉ ra rằng luồng điều khiển nên chuyển từ cuối mệnh đề này sang câu lệnh đầu tiên của mệnh đề tiếp theo. Ngược lại, luồng điều khiển chuyển đến cuối câu lệnh "switch". Câu lệnh "fallthrough" có thể xuất hiện là câu lệnh cuối cùng của tất cả các mệnh đề ngoại trừ mệnh đề cuối cùng của một switch biểu thức.

Biểu thức switch có thể được đứng trước bởi một câu lệnh đơn giản, câu lệnh đó được thực thi trước khi biểu thức được định lượng.

switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}

switch x := f(); {  // missing switch expression means "true"
case x < 0: return -x
default: return x
}

switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}

Hạn chế triển khai: Trình biên dịch có thể không cho phép nhiều biểu thức case định lượng thành cùng một hằng số. Ví dụ, các trình biên dịch hiện tại không cho phép các hằng số nguyên, số thực dấu chấm động, hoặc chuỗi trùng lặp trong các biểu thức case.

Switch kiểu

Một switch kiểu so sánh các kiểu thay vì các giá trị. Ngoài ra nó tương tự như switch biểu thức. Nó được đánh dấu bằng một biểu thức switch đặc biệt có dạng của một khẳng định kiểu sử dụng từ khóa type thay vì một kiểu thực tế:

switch x.(type) {
// cases
}

Khi đó các case khớp các kiểu thực tế T với kiểu động của biểu thức x. Như với các khẳng định kiểu, x phải thuộc kiểu interface, nhưng không phải là tham số kiểu, và mỗi kiểu không phải interface T được liệt kê trong một case phải triển khai kiểu của x. Các kiểu được liệt kê trong các case của một switch kiểu phải đều khác nhau.

TypeSwitchStmt  = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause  = TypeSwitchCase ":" StatementList .
TypeSwitchCase  = "case" TypeList | "default" .

TypeSwitchGuard có thể bao gồm một khai báo biến ngắn. Khi dạng đó được sử dụng, biến được khai báo ở cuối TypeSwitchCase trong khối ngầm của mỗi mệnh đề. Trong các mệnh đề có case liệt kê đúng một kiểu, biến có kiểu đó; ngược lại, biến có kiểu của biểu thức trong TypeSwitchGuard.

Thay vì một kiểu, một case có thể sử dụng định danh được khai báo trước nil; case đó được chọn khi biểu thức trong TypeSwitchGuard là một giá trị interface nil. Có thể có nhiều nhất một case nil.

Cho biểu thức x thuộc kiểu interface{}, switch kiểu sau:

switch i := x.(type) {
case nil:
	printString("x is nil")                // type of i is type of x (interface{})
case int:
	printInt(i)                            // type of i is int
case float64:
	printFloat64(i)                        // type of i is float64
case func(int) float64:
	printFunction(i)                       // type of i is func(int) float64
case bool, string:
	printString("type is bool or string")  // type of i is type of x (interface{})
default:
	printString("don't know the type")     // type of i is type of x (interface{})
}

có thể được viết lại thành:

v := x  // x is evaluated exactly once
if v == nil {
	i := v                                 // type of i is type of x (interface{})
	printString("x is nil")
} else if i, isInt := v.(int); isInt {
	printInt(i)                            // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {
	printFloat64(i)                        // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {
	printFunction(i)                       // type of i is func(int) float64
} else {
	_, isBool := v.(bool)
	_, isString := v.(string)
	if isBool || isString {
		i := v                         // type of i is type of x (interface{})
		printString("type is bool or string")
	} else {
		i := v                         // type of i is type of x (interface{})
		printString("don't know the type")
	}
}

Một tham số kiểu hoặc một kiểu generic có thể được sử dụng làm kiểu trong một case. Nếu khi khởi tạo, kiểu đó trùng lặp với một mục khác trong switch, case khớp đầu tiên được chọn.

func f[P any](x any) int {
	switch x.(type) {
	case P:
		return 0
	case string:
		return 1
	case []P:
		return 2
	case []byte:
		return 3
	default:
		return 4
	}
}

var v1 = f[string]("foo")   // v1 == 0
var v2 = f[byte]([]byte{})  // v2 == 2

Bộ bảo vệ switch kiểu có thể được đứng trước bởi một câu lệnh đơn giản, câu lệnh đó được thực thi trước khi bộ bảo vệ được định lượng.

Câu lệnh "fallthrough" không được phép trong switch kiểu.

Câu lệnh for

Câu lệnh "for" chỉ định việc thực thi lặp lại của một khối. Có ba dạng: Việc lặp có thể được kiểm soát bởi một điều kiện đơn, một mệnh đề "for", hoặc một mệnh đề "range".

ForStmt   = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .

Câu lệnh for với điều kiện đơn

Ở dạng đơn giản nhất, câu lệnh "for" chỉ định việc thực thi lặp lại của một khối miễn là một điều kiện boolean định lượng thành true. Điều kiện được định lượng trước mỗi lần lặp. Nếu điều kiện vắng mặt, nó tương đương với giá trị boolean true.

for a < b {
	a *= 2
}

Câu lệnh for với mệnh đề for

Câu lệnh "for" với ForClause cũng được kiểm soát bởi điều kiện của nó, nhưng ngoài ra nó có thể chỉ định một câu lệnh init và một câu lệnh post, chẳng hạn như một phép gán, một câu lệnh tăng hoặc giảm. Câu lệnh init có thể là một khai báo biến ngắn, nhưng câu lệnh post thì không được.

ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt  = SimpleStmt .
PostStmt  = SimpleStmt .
for i := 0; i < 10; i++ {
	f(i)
}

Nếu không rỗng, câu lệnh init được thực thi một lần trước khi định lượng điều kiện cho lần lặp đầu tiên; câu lệnh post được thực thi sau mỗi lần thực thi khối (và chỉ khi khối đã được thực thi). Bất kỳ phần tử nào của ForClause có thể rỗng nhưng các dấu chấm phẩy là bắt buộc trừ khi chỉ có một điều kiện. Nếu điều kiện vắng mặt, nó tương đương với giá trị boolean true.

for cond { S() }    is the same as    for ; cond ; { S() }
for      { S() }    is the same as    for true     { S() }

Mỗi lần lặp có biến (hoặc các biến) được khai báo riêng [Go 1.22]. Biến được sử dụng trong lần lặp đầu tiên được khai báo bởi câu lệnh init. Biến được sử dụng trong mỗi lần lặp tiếp theo được khai báo ngầm trước khi thực thi câu lệnh post và được khởi tạo thành giá trị của biến của lần lặp trước tại thời điểm đó.

var prints []func()
for i := 0; i < 5; i++ {
	prints = append(prints, func() { println(i) })
	i++
}
for _, p := range prints {
	p()
}

in ra

1
3
5

Trước [Go 1.22], các lần lặp dùng chung một tập hợp biến thay vì có các biến riêng của chúng. Trong trường hợp đó, ví dụ trên in ra

6
6
6

Câu lệnh for với mệnh đề range

Câu lệnh "for" với mệnh đề "range" lặp qua tất cả các mục của một mảng, slice, chuỗi hoặc map, các giá trị được nhận trên một channel, các giá trị nguyên từ không đến một giới hạn trên [Go 1.22], hoặc các giá trị được truyền cho hàm yield của một hàm iterator [Go 1.23]. Với mỗi mục, nó gán các giá trị lặp cho các biến lặp tương ứng nếu có, rồi thực thi khối.

RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .

Biểu thức ở vế phải trong mệnh đề "range" được gọi là biểu thức range, có thể là một mảng, con trỏ đến mảng, slice, chuỗi, map, channel cho phép thao tác nhận, một số nguyên, hoặc một hàm với chữ ký cụ thể (xem bên dưới). Như với một phép gán, nếu có, các toán hạng ở vế trái phải có địa chỉ hoặc là biểu thức chỉ mục map; chúng biểu thị các biến lặp. Nếu biểu thức range là một hàm, số lượng biến lặp tối đa phụ thuộc vào chữ ký hàm. Nếu biểu thức range là một channel hoặc số nguyên, cho phép nhiều nhất một biến lặp; ngược lại có thể có đến hai biến. Nếu biến lặp cuối cùng là định danh rỗng, mệnh đề range tương đương với mệnh đề tương tự không có định danh đó.

Biểu thức range x được định lượng trước khi bắt đầu vòng lặp, với một ngoại lệ: nếu có nhiều nhất một biến lặp và x hoặc len(x)hằng số, biểu thức range không được định lượng.

Các lời gọi hàm ở vế trái được định lượng một lần mỗi lần lặp. Với mỗi lần lặp, các giá trị lặp được tạo ra như sau nếu các biến lặp tương ứng có mặt:

Range expression                                       1st value                2nd value

array or slice      a  [n]E, *[n]E, or []E             index    i  int          a[i]       E
string              s  string type                     index    i  int          see below  rune
map                 m  map[K]V                         key      k  K            m[k]       V
channel             c  chan E, <-chan E                element  e  E
integer value       n  integer type, or untyped int    value    i  see below
function, 0 values  f  func(func() bool)
function, 1 value   f  func(func(V) bool)              value    v  V
function, 2 values  f  func(func(K, V) bool)           key      k  K            v          V
  1. Với một mảng, con trỏ đến mảng, hoặc giá trị slice a, các giá trị lặp chỉ mục được tạo ra theo thứ tự tăng dần, bắt đầu từ chỉ mục phần tử 0. Nếu có nhiều nhất một biến lặp, vòng lặp range tạo ra các giá trị lặp từ 0 đến len(a)-1 và không lập chỉ mục vào mảng hoặc slice đó. Với một slice nil, số lần lặp là 0.
  2. Với một giá trị chuỗi, mệnh đề "range" lặp qua các điểm mã Unicode trong chuỗi bắt đầu từ chỉ mục byte 0. Ở các lần lặp kế tiếp, giá trị chỉ mục sẽ là chỉ mục của byte đầu tiên của các điểm mã được mã hóa UTF-8 liên tiếp trong chuỗi, và giá trị thứ hai, thuộc kiểu rune, sẽ là giá trị của điểm mã tương ứng. Nếu khi lặp gặp phải một chuỗi UTF-8 không hợp lệ, giá trị thứ hai sẽ là 0xFFFD, ký tự thay thế Unicode, và lần lặp tiếp theo sẽ tiến một byte trong chuỗi.
  3. Thứ tự lặp trên các map không được xác định và không được đảm bảo giống nhau từ lần lặp này sang lần lặp tiếp theo. Nếu một mục map chưa được duyệt đến bị xóa trong quá trình lặp, giá trị lặp tương ứng sẽ không được tạo ra. Nếu một mục map được tạo ra trong quá trình lặp, mục đó có thể được tạo ra trong quá trình lặp hoặc có thể bị bỏ qua. Lựa chọn này có thể khác nhau đối với từng mục được tạo ra và từ lần lặp này sang lần lặp tiếp theo. Nếu map là nil, số lần lặp là 0.
  4. Với các channel, các giá trị lặp được tạo ra là các giá trị liên tiếp được gửi trên channel cho đến khi channel được đóng. Nếu channel là nil, biểu thức range bị chặn mãi mãi.
  5. Với một giá trị nguyên n, trong đó n thuộc kiểu nguyên hoặc là một hằng số nguyên không có kiểu, các giá trị lặp từ 0 đến n-1 được tạo ra theo thứ tự tăng dần. Nếu n thuộc kiểu nguyên, các giá trị lặp có cùng kiểu đó. Ngược lại, kiểu của n được xác định như thể nó được gán cho biến lặp. Cụ thể: nếu biến lặp đã tồn tại trước, kiểu của các giá trị lặp là kiểu của biến lặp, phải thuộc kiểu nguyên. Ngược lại, nếu biến lặp được khai báo bởi mệnh đề "range" hoặc vắng mặt, kiểu của các giá trị lặp là kiểu mặc định cho n. Nếu n <= 0, vòng lặp không chạy bất kỳ lần lặp nào.
  6. Với một hàm f, việc lặp tiến hành bằng cách gọi f với một hàm yield tổng hợp mới làm đối số của nó. Nếu yield được gọi trước khi f trả về, các đối số cho yield trở thành các giá trị lặp để thực thi thân vòng lặp một lần. Sau mỗi lần lặp vòng lặp kế tiếp, yield trả về true và có thể được gọi lại để tiếp tục vòng lặp. Miễn là thân vòng lặp không kết thúc, mệnh đề "range" sẽ tiếp tục tạo ra các giá trị lặp theo cách này cho mỗi lời gọi yield cho đến khi f trả về. Nếu thân vòng lặp kết thúc (chẳng hạn bởi một câu lệnh break), yield trả về false và không được gọi lại.

Các biến lặp có thể được khai báo bởi mệnh đề "range" bằng dạng khai báo biến ngắn (:=). Trong trường hợp này, phạm vi của chúng là khối của câu lệnh "for" và mỗi lần lặp có các biến mới riêng [Go 1.22] (xem thêm câu lệnh "for" với ForClause). Các biến có kiểu của các giá trị lặp tương ứng của chúng.

Nếu các biến lặp không được khai báo tường minh bởi mệnh đề "range", chúng phải đã tồn tại trước. Trong trường hợp này, các giá trị lặp được gán cho các biến tương ứng như trong một câu lệnh gán.

var testdata *struct {
	a *[7]int
}
for i, _ := range testdata.a {
	// testdata.a is never evaluated; len(testdata.a) is constant
	// i ranges from 0 to 6
	f(i)
}

var a [10]string
for i, s := range a {
	// type of i is int
	// type of s is string
	// s == a[i]
	g(i, s)
}

var key string
var val interface{}  // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
	h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
	doWork(w)
}

// empty a channel
for range ch {}

// call f(0), f(1), ... f(9)
for i := range 10 {
	// type of i is int (default type for untyped constant 10)
	f(i)
}

// invalid: 256 cannot be assigned to uint8
var u uint8
for u = range 256 {
}

// invalid: 1e3 is a floating-point constant
for range 1e3 {
}

// fibo generates the Fibonacci sequence
fibo := func(yield func(x int) bool) {
	f0, f1 := 0, 1
	for yield(f0) {
		f0, f1 = f1, f0+f1
	}
}

// print the Fibonacci numbers below 1000:
for x := range fibo {
	if x >= 1000 {
		break
	}
	fmt.Printf("%d ", x)
}
// output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

// iteration support for a recursive tree data structure
type Tree[K cmp.Ordered, V any] struct {
	left, right *Tree[K, V]
	key         K
	value       V
}

func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool {
	return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield)
}

func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) {
	t.walk(yield)
}

// walk tree t in-order
var t Tree[string, int]
for k, v := range t.Walk {
	// process k, v
}

Nếu kiểu của biểu thức range là một tham số kiểu, tất cả các kiểu trong tập hợp kiểu của nó phải có cùng kiểu nền và biểu thức range phải hợp lệ với kiểu đó, hoặc, nếu tập hợp kiểu chứa các kiểu channel, nó chỉ được chứa các kiểu channel với các kiểu phần tử giống nhau, và tất cả các kiểu channel phải cho phép thao tác nhận.

Câu lệnh go

Câu lệnh "go" bắt đầu thực thi một lời gọi hàm như một luồng điều khiển đồng thời độc lập, hay goroutine, trong cùng một không gian địa chỉ.

GoStmt = "go" Expression .

Biểu thức phải là một lời gọi hàm hoặc phương thức; nó không thể được đặt trong ngoặc đơn. Các lời gọi hàm dựng sẵn bị hạn chế như đối với câu lệnh biểu thức.

Giá trị hàm và các tham số được định lượng như thông thường trong goroutine đang gọi, nhưng khác với một lời gọi thông thường, việc thực thi chương trình không chờ hàm được gọi hoàn thành. Thay vào đó, hàm bắt đầu thực thi độc lập trong một goroutine mới. Khi hàm kết thúc, goroutine của nó cũng kết thúc. Nếu hàm có bất kỳ giá trị trả về nào, chúng bị loại bỏ khi hàm hoàn thành.

go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

Câu lệnh select

Câu lệnh "select" chọn trong số một tập hợp các thao tác gửi hoặc nhận có thể thực hiện được. Nó trông tương tự như câu lệnh "switch" nhưng với tất cả các case đều tham chiếu đến các thao tác giao tiếp.

SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase   = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt   = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr   = Expression .

Một case với RecvStmt có thể gán kết quả của một RecvExpr cho một hoặc hai biến, các biến đó có thể được khai báo bằng khai báo biến ngắn. RecvExpr phải là một thao tác nhận (có thể được đặt trong ngoặc đơn). Có thể có nhiều nhất một case default và nó có thể xuất hiện ở bất kỳ đâu trong danh sách các case.

Việc thực thi một câu lệnh "select" tiến hành qua nhiều bước:

  1. Với tất cả các case trong câu lệnh, các toán hạng channel của các thao tác nhận và các biểu thức channel và vế phải của các câu lệnh gửi được định lượng đúng một lần, theo thứ tự nguồn, khi vào câu lệnh "select". Kết quả là một tập hợp các channel để nhận từ hoặc gửi đến, và các giá trị tương ứng để gửi. Bất kỳ tác dụng phụ nào trong quá trình định lượng đó sẽ xảy ra bất kể thao tác giao tiếp nào (nếu có) được chọn để tiến hành. Các biểu thức ở vế trái của một RecvStmt với khai báo biến ngắn hoặc phép gán chưa được định lượng.
  2. Nếu một hoặc nhiều thao tác giao tiếp có thể tiến hành, một thao tác duy nhất có thể tiến hành được chọn thông qua lựa chọn ngẫu nhiên đồng nhất. Ngược lại, nếu có một case default, case đó được chọn. Nếu không có case default, câu lệnh "select" bị chặn cho đến khi ít nhất một trong các thao tác giao tiếp có thể tiến hành.
  3. Trừ khi case được chọn là case default, thao tác giao tiếp tương ứng được thực thi.
  4. Nếu case được chọn là một RecvStmt với khai báo biến ngắn hoặc một phép gán, các biểu thức vế trái được định lượng và giá trị (hoặc các giá trị) nhận được được gán.
  5. Danh sách câu lệnh của case được chọn được thực thi.

Vì giao tiếp trên các channel nil không bao giờ có thể tiến hành, một select chỉ có các channel nil và không có case default sẽ bị chặn mãi mãi.

var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
	print("received ", i1, " from c1\n")
case c2 <- i2:
	print("sent ", i2, " to c2\n")
case i3, ok := (<-c3):  // same as: i3, ok := <-c3
	if ok {
		print("received ", i3, " from c3\n")
	} else {
		print("c3 is closed\n")
	}
case a[f()] = <-c4:
	// same as:
	// case t := <-c4
	//	a[f()] = t
default:
	print("no communication\n")
}

for {  // send random sequence of bits to c
	select {
	case c <- 0:  // note: no statement, no fallthrough, no folding of cases
	case c <- 1:
	}
}

select {}  // block forever

Câu lệnh return

Câu lệnh "return" trong một hàm F kết thúc việc thực thi của F, và tùy chọn cung cấp một hoặc nhiều giá trị kết quả. Bất kỳ hàm nào được defer bởi F đều được thực thi trước khi F trả về cho người gọi của nó.

ReturnStmt = "return" [ ExpressionList ] .

Trong một hàm không có kiểu kết quả, câu lệnh "return" không được chỉ định bất kỳ giá trị kết quả nào.

func noResult() {
	return
}

Có ba cách để trả về giá trị từ một hàm có kiểu kết quả:

  1. Giá trị hoặc các giá trị trả về có thể được liệt kê tường minh trong câu lệnh "return". Mỗi biểu thức phải đơn trị và có thể gán cho phần tử tương ứng của kiểu kết quả hàm.
    func simpleF() int {
    	return 2
    }
    
    func complexF1() (re float64, im float64) {
    	return -7.0, -4.0
    }
    
  2. Danh sách biểu thức trong câu lệnh "return" có thể là một lời gọi đơn đến một hàm đa trị. Hiệu quả là như thể mỗi giá trị trả về từ hàm đó được gán cho một biến tạm thời với kiểu của giá trị tương ứng, theo sau là một câu lệnh "return" liệt kê các biến này, lúc đó các quy tắc của trường hợp trước áp dụng.
    func complexF2() (re float64, im float64) {
    	return complexF1()
    }
    
  3. Danh sách biểu thức có thể rỗng nếu kiểu kết quả của hàm chỉ định tên cho các tham số kết quả của nó. Các tham số kết quả hoạt động như các biến cục bộ thông thường và hàm có thể gán giá trị cho chúng khi cần thiết. Câu lệnh "return" trả về các giá trị của các biến này.
    func complexF3() (re float64, im float64) {
    	re = 7.0
    	im = 4.0
    	return
    }
    
    func (devnull) Write(p []byte) (n int, _ error) {
    	n = len(p)
    	return
    }
    

Bất kể chúng được khai báo như thế nào, tất cả các giá trị kết quả đều được khởi tạo thành giá trị không của kiểu của chúng khi vào hàm. Câu lệnh "return" chỉ định các kết quả sẽ thiết lập các tham số kết quả trước khi bất kỳ hàm defer nào được thực thi.

Hạn chế triển khai: Trình biên dịch có thể không cho phép một danh sách biểu thức rỗng trong câu lệnh "return" nếu một thực thể khác (hằng số, kiểu, hoặc biến) với cùng tên với một tham số kết quả đang nằm trong phạm vi tại vị trí return.

func f(n int) (res int, err error) {
	if _, err := f(n-1); err != nil {
		return  // invalid return statement: err is shadowed
	}
	return
}

Câu lệnh break

Câu lệnh "break" kết thúc việc thực thi của câu lệnh "for", "switch", hoặc "select" trong cùng nằm trong cùng một hàm.

BreakStmt = "break" [ Label ] .

Nếu có một nhãn, nó phải là nhãn của một câu lệnh "for", "switch", hoặc "select" bao ngoài, và đó là câu lệnh mà việc thực thi của nó bị kết thúc.

OuterLoop:
	for i = 0; i < n; i++ {
		for j = 0; j < m; j++ {
			switch a[i][j] {
			case nil:
				state = Error
				break OuterLoop
			case item:
				state = Found
				break OuterLoop
			}
		}
	}

Câu lệnh continue

Câu lệnh "continue" bắt đầu lần lặp tiếp theo của vòng lặp "for" bao nằm trong cùng bằng cách chuyển điều khiển đến cuối khối vòng lặp. Vòng lặp "for" phải nằm trong cùng một hàm.

ContinueStmt = "continue" [ Label ] .

Nếu có một nhãn, nó phải là nhãn của một câu lệnh "for" bao ngoài, và đó là câu lệnh mà việc thực thi của nó tiến lên.

RowLoop:
	for y, row := range rows {
		for x, data := range row {
			if data == endOfRow {
				continue RowLoop
			}
			row[x] = data + bias(x, y)
		}
	}

Câu lệnh goto

Câu lệnh "goto" chuyển điều khiển đến câu lệnh có nhãn tương ứng trong cùng một hàm.

GotoStmt = "goto" Label .
goto Error

Thực thi câu lệnh "goto" không được làm bất kỳ biến nào đi vào phạm vi mà chưa nằm trong phạm vi tại điểm goto. Ví dụ, ví dụ này:

	goto L  // BAD
	v := 3
L:

là sai vì bước nhảy đến nhãn L bỏ qua việc tạo v.

Câu lệnh "goto" bên ngoài một khối không thể nhảy đến một nhãn bên trong khối đó. Ví dụ, ví dụ này:

if n%2 == 1 {
	goto L1
}
for n > 0 {
	f()
	n--
L1:
	f()
	n--
}

là sai vì nhãn L1 nằm bên trong khối của câu lệnh "for" nhưng goto thì không.

Câu lệnh fallthrough

Câu lệnh "fallthrough" chuyển điều khiển đến câu lệnh đầu tiên của mệnh đề case tiếp theo trong một câu lệnh "switch" biểu thức. Nó chỉ có thể được sử dụng là câu lệnh không rỗng cuối cùng trong một mệnh đề như vậy.

FallthroughStmt = "fallthrough" .

Câu lệnh defer

Câu lệnh "defer" gọi một hàm mà việc thực thi của nó được hoãn lại đến thời điểm hàm bao quanh trả về, hoặc vì hàm bao quanh đã thực thi một câu lệnh return, đến cuối thân hàm của nó, hoặc vì goroutine tương ứng đang panic.

DeferStmt = "defer" Expression .

Biểu thức phải là một lời gọi hàm hoặc phương thức; nó không thể được đặt trong ngoặc đơn. Các lời gọi hàm dựng sẵn bị hạn chế như đối với câu lệnh biểu thức.

Mỗi lần câu lệnh "defer" thực thi, giá trị hàm và các tham số cho lời gọi được định lượng như thông thường và lưu lại mới nhưng hàm thực tế không được gọi. Thay vào đó, các hàm defer được gọi ngay trước khi hàm bao quanh trả về, theo thứ tự ngược lại với thứ tự chúng được defer. Tức là, nếu hàm bao quanh trả về thông qua một câu lệnh return tường minh, các hàm defer được thực thi sau khi bất kỳ tham số kết quả nào được thiết lập bởi câu lệnh return đó nhưng trước khi hàm trả về cho người gọi của nó. Nếu một giá trị hàm defer định lượng thành nil, việc thực thi sẽ panic khi hàm được gọi, không phải khi câu lệnh "defer" được thực thi.

Ví dụ, nếu hàm defer là một hàm literal và hàm bao quanh có các tham số kết quả được đặt tên nằm trong phạm vi trong literal đó, hàm defer có thể truy cập và sửa đổi các tham số kết quả trước khi chúng được trả về. Nếu hàm defer có bất kỳ giá trị trả về nào, chúng bị loại bỏ khi hàm hoàn thành. (Xem thêm phần về xử lý panic.)

lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f returns 42
func f() (result int) {
	defer func() {
		// result is accessed after it was set to 6 by the return statement
		result *= 7
	}()
	return 6
}

Các hàm dựng sẵn

Các hàm dựng sẵn được khai báo trước. Chúng được gọi như bất kỳ hàm nào khác nhưng một số trong chúng chấp nhận một kiểu thay vì một biểu thức làm đối số đầu tiên.

Các hàm dựng sẵn không có kiểu Go chuẩn, vì vậy chúng chỉ có thể xuất hiện trong biểu thức gọi hàm; chúng không thể được dùng như các giá trị hàm.

append vào và copy slice

Các hàm dựng sẵn appendcopy hỗ trợ các thao tác slice thông dụng. Đối với cả hai hàm, kết quả không phụ thuộc vào việc vùng nhớ được tham chiếu bởi các đối số có chồng lấp hay không.

Hàm biến đổi tham số append nối thêm không hoặc nhiều giá trị x vào một slice s có kiểu S và trả về slice kết quả, cũng có kiểu S. Các giá trị x được truyền vào một tham số có kiểu ...E trong đó E là kiểu phần tử của S và các quy tắc truyền tham số tương ứng được áp dụng. Như một trường hợp đặc biệt, append cũng chấp nhận một slice có kiểu có thể gán cho kiểu []byte với đối số thứ hai có kiểu string theo sau bởi .... Dạng này nối thêm các byte của chuỗi.

append(s S, x ...E) S  // E is the element type of S

Nếu S là một tham số kiểu, tất cả các kiểu trong tập kiểu của nó phải có cùng kiểu slice cơ sở []E.

Nếu dung lượng của s không đủ lớn để chứa thêm các giá trị, append cấp phát một mảng cơ sở mới đủ lớn để chứa cả các phần tử slice hiện có lẫn các giá trị bổ sung. Ngược lại, append tái sử dụng mảng cơ sở hiện có.

s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 is []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 is []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 is []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                             t is []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // append string contents      b is []byte{'b', 'a', 'r' }

Hàm copy copy các phần tử slice từ nguồn src đến đích dst và trả về số lượng phần tử đã được copy. Cả hai đối số phải có kiểu phần tử giống hệt E và phải có thể gán cho một slice có kiểu []E. Số lượng phần tử được copy là giá trị nhỏ nhất trong len(src)len(dst). Như một trường hợp đặc biệt, copy cũng chấp nhận một đối số đích có thể gán cho kiểu []byte với một đối số nguồn có kiểu string. Dạng này copy các byte từ chuỗi vào slice byte.

copy(dst, src []T) int
copy(dst []byte, src string) int

Nếu kiểu của một hoặc cả hai đối số là tham số kiểu, tất cả các kiểu trong tập kiểu tương ứng của chúng phải có cùng kiểu slice cơ sở []E.

Ví dụ:

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s is []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s is []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b is []byte("Hello")

Clear

Hàm dựng sẵn clear nhận một đối số có kiểu map, slice, hoặc tham số kiểu, và xóa hoặc đặt về không tất cả các phần tử [Go 1.21].

Call        Argument type     Result

clear(m)    map[K]T           deletes all entries, resulting in an
                              empty map (len(m) == 0)

clear(s)    []T               sets all elements up to the length of
                              s to the zero value of T

clear(t)    type parameter    see below

Nếu kiểu của đối số truyền vào cleartham số kiểu, tất cả các kiểu trong tập kiểu của nó phải là map hoặc slice, và clear thực hiện thao tác tương ứng với đối số kiểu thực tế.

Nếu map hoặc slice là nil, clear là một thao tác không làm gì.

Close

Đối với một channel ch, hàm dựng sẵn close(ch) ghi nhận rằng sẽ không còn giá trị nào được gửi trên channel đó nữa. Đây là lỗi nếu ch là channel chỉ nhận. Gửi vào hoặc close một channel đã đóng gây ra một panic lúc chạy. Closing channel nil cũng gây ra một panic lúc chạy. Sau khi gọi close, và sau khi tất cả các giá trị đã được gửi trước đó được nhận, các thao tác nhận sẽ trả về giá trị không của kiểu channel mà không bị chặn. Thao tác nhận đa giá trị trả về một giá trị đã nhận kèm theo chỉ báo về việc channel có bị đóng hay không.

Nếu kiểu của đối số truyền vào closetham số kiểu, tất cả các kiểu trong tập kiểu của nó phải là channel. Đây là lỗi nếu bất kỳ channel nào trong số đó là channel chỉ nhận.

Thao tác với số phức

Ba hàm dùng để lắp ráp và phân tách số phức. Hàm dựng sẵn complex tạo ra một giá trị phức từ phần thực và phần ảo dấu phẩy động, trong khi realimag trích xuất phần thực và phần ảo của một giá trị phức.

complex(realPart, imaginaryPart floatT) complexT
real(complexT) floatT
imag(complexT) floatT

Kiểu của các đối số và giá trị trả về tương ứng nhau. Đối với complex, hai đối số phải có cùng kiểu dấu phẩy động và kiểu trả về là kiểu phức với các thành phần dấu phẩy động tương ứng: complex64 cho các đối số float32, và complex128 cho các đối số float64. Nếu một trong các đối số được đánh giá là một hằng số không có kiểu, nó sẽ trước tiên được chuyển đổi ngầm định sang kiểu của đối số kia. Nếu cả hai đối số đều được đánh giá là các hằng số không có kiểu, chúng phải là các số không phức hoặc phần ảo của chúng phải bằng không, và giá trị trả về của hàm là một hằng số phức không có kiểu.

Đối với realimag, đối số phải có kiểu phức, và kiểu trả về là kiểu dấu phẩy động tương ứng: float32 cho đối số complex64, và float64 cho đối số complex128. Nếu đối số được đánh giá là một hằng số không có kiểu, nó phải là một số, và giá trị trả về của hàm là một hằng số dấu phẩy động không có kiểu.

Các hàm realimag cùng nhau tạo thành nghịch đảo của complex, vì vậy đối với một giá trị z của kiểu phức Z, z == Z(complex(real(z), imag(z))).

Nếu các toán hạng của các hàm này đều là hằng số, giá trị trả về là một hằng số.

var a = complex(2, -2)             // complex128
const b = complex(1.0, -1.4)       // untyped complex constant 1 - 1.4i
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var s int = complex(1, 0)          // untyped complex constant 1 + 0i can be converted to int
_ = complex(1, 2<<s)               // illegal: 2 assumes floating-point type, cannot shift
var rl = real(c64)                 // float32
var im = imag(a)                   // float64
const c = imag(b)                  // untyped constant -1.4
_ = imag(3 << s)                   // illegal: 3 assumes complex type, cannot shift

Các đối số có kiểu tham số kiểu không được phép.

Xóa phần tử map

Hàm dựng sẵn delete xóa phần tử có khóa k khỏi một map m. Giá trị k phải có thể gán cho kiểu khóa của m.

delete(m, k)  // remove element m[k] from map m

Nếu kiểu của m là một tham số kiểu, tất cả các kiểu trong tập kiểu đó phải là map, và chúng phải đều có kiểu khóa giống hệt nhau.

Nếu map mnil hoặc phần tử m[k] không tồn tại, delete là một thao tác không làm gì.

Độ dài và dung lượng

Các hàm dựng sẵn lencap nhận các đối số có nhiều kiểu khác nhau và trả về kết quả có kiểu int. Bản cài đặt đảm bảo rằng kết quả luôn vừa trong một int.

Call      Argument type    Result

len(s)    string type      string length in bytes
          [n]T, *[n]T      array length (== n)
          []T              slice length
          map[K]T          map length (number of defined keys)
          chan T           number of elements queued in channel buffer
          type parameter   see below

cap(s)    [n]T, *[n]T      array length (== n)
          []T              slice capacity
          chan T           channel buffer capacity
          type parameter   see below

Nếu kiểu đối số là một tham số kiểu P, lời gọi len(e) (hoặc cap(e) tương ứng) phải hợp lệ với mỗi kiểu trong tập kiểu của P. Kết quả là độ dài (hoặc dung lượng, tương ứng) của đối số có kiểu tương ứng với đối số kiểu mà P được khởi tạo.

Dung lượng của một slice là số phần tử mà có không gian được cấp phát trong mảng cơ sở. Tại bất kỳ thời điểm nào, mối quan hệ sau đây luôn đúng:

0 <= len(s) <= cap(s)

Độ dài của một slice, map hoặc channel nil là 0. Dung lượng của một slice hoặc channel nil là 0.

Biểu thức len(s)hằng số nếu s là một hằng số chuỗi. Các biểu thức len(s)cap(s) là hằng số nếu kiểu của s là một mảng hoặc con trỏ đến mảng và biểu thức s không chứa nhận channel hoặc lời gọi hàm (không phải hằng số); trong trường hợp này s không được đánh giá. Ngược lại, các lời gọi lencap không phải là hằng số và s được đánh giá.

const (
	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
	c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
)
var z complex128

Tạo slice, map và channel

Hàm dựng sẵn make nhận một kiểu T, phải là kiểu slice, map hoặc channel, hoặc một tham số kiểu, tùy chọn theo sau bởi một danh sách biểu thức đặc thù theo kiểu. Nó trả về một giá trị có kiểu T (không phải *T). Vùng nhớ được khởi tạo như mô tả trong phần về giá trị ban đầu.

Call             Type T            Result

make(T, n)       slice             slice of type T with length n and capacity n
make(T, n, m)    slice             slice of type T with length n and capacity m

make(T)          map               map of type T
make(T, n)       map               map of type T with initial space for approximately n elements

make(T)          channel           unbuffered channel of type T
make(T, n)       channel           buffered channel of type T, buffer size n

make(T, n)       type parameter    see below
make(T, n, m)    type parameter    see below

Nếu đối số đầu tiên là một tham số kiểu, tất cả các kiểu trong tập kiểu của nó phải có cùng kiểu cơ sở, phải là kiểu slice hoặc map, hoặc nếu có kiểu channel, chỉ được có kiểu channel, chúng phải đều có cùng kiểu phần tử, và chiều của channel không được xung đột.

Mỗi đối số kích thước nm phải có kiểu nguyên, có một tập kiểu chỉ chứa các kiểu nguyên, hoặc là một hằng số không có kiểu. Một đối số kích thước hằng số phải không âm và có thể biểu diễn bởi một giá trị có kiểu int; nếu nó là một hằng số không có kiểu thì được gán kiểu int. Nếu cả nm đều được cung cấp và đều là hằng số, thì n không được lớn hơn m. Đối với slice và channel, nếu n âm hoặc lớn hơn m lúc chạy, một panic lúc chạy xảy ra.

s := make([]int, 10, 100)       // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63)         // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0)         // illegal: len(s) > cap(s)
c := make(chan int, 10)         // channel with a buffer size of 10
m := make(map[string]int, 100)  // map with initial space for approximately 100 elements

Gọi make với kiểu map và gợi ý kích thước n sẽ tạo ra một map với không gian ban đầu để chứa n phần tử map. Hành vi chính xác phụ thuộc vào bản cài đặt.

Min và max

Các hàm dựng sẵn minmax tính toán giá trị nhỏ nhất—hoặc lớn nhất, tương ứng—trong số một lượng cố định các đối số có kiểu có thứ tự. Phải có ít nhất một đối số [Go 1.21].

Các quy tắc kiểu tương tự như đối với toán tử áp dụng: đối với các đối số có thứ tự xy, min(x, y) hợp lệ nếu x + y hợp lệ, và kiểu của min(x, y) là kiểu của x + y (và tương tự cho max). Nếu tất cả các đối số là hằng số, kết quả là hằng số.

var x, y int
m := min(x)                 // m == x
m := min(x, y)              // m is the smaller of x and y
m := max(x, y, 10)          // m is the larger of x and y but at least 10
c := max(1, 2.0, 10)        // c == 10.0 (floating-point kind)
f := max(0, float32(x))     // type of f is float32
var s []string
_ = min(s...)               // invalid: slice arguments are not permitted
t := max("", "foo", "bar")  // t == "foo" (string kind)

Đối với các đối số số, giả sử tất cả các NaN đều bằng nhau, minmax có tính giao hoán và kết hợp:

min(x, y)    == min(y, x)
min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))

Đối với các đối số dấu phẩy động, số không âm, NaN, và vô cực, các quy tắc sau áp dụng:

   x        y    min(x, y)    max(x, y)

  -0.0    0.0         -0.0          0.0    // negative zero is smaller than (non-negative) zero
  -Inf      y         -Inf            y    // negative infinity is smaller than any other number
  +Inf      y            y         +Inf    // positive infinity is larger than any other number
   NaN      y          NaN          NaN    // if any argument is a NaN, the result is a NaN

Đối với các đối số chuỗi, kết quả của min là đối số đầu tiên có giá trị nhỏ nhất (hoặc đối với max, lớn nhất), so sánh theo thứ tự từ điển theo từng byte:

min(x, y)    == if x <= y then x else y
min(x, y, z) == min(min(x, y), z)

Cấp phát bộ nhớ

Hàm dựng sẵn new tạo ra một biến mới được khởi tạo và trả về một con trỏ đến nó. Nó chấp nhận một đối số duy nhất, có thể là kiểu hoặc biểu thức.

Nếu đối số là một kiểu T, thì new(T) cấp phát một biến có kiểu T được khởi tạo về giá trị không của nó.

Nếu đối số là một biểu thức x, thì new(x) cấp phát một biến có kiểu của x được khởi tạo về giá trị của x. Nếu giá trị đó là một hằng số không có kiểu, nó trước tiên được chuyển đổi ngầm định về kiểu mặc định của nó; nếu là một giá trị boolean không có kiểu, nó được chuyển đổi ngầm định sang kiểu bool. Định danh được khai báo trước nil không thể được dùng như một đối số cho new.

Ví dụ, new(int)new(123) đều trả về một con trỏ đến một biến mới có kiểu int. Giá trị của biến đầu tiên là 0, và giá trị của biến thứ hai là 123. Tương tự

type S struct { a int; b float64 }
new(S)

cấp phát một biến có kiểu S, khởi tạo nó (a=0, b=0.0), và trả về một giá trị có kiểu *S chứa địa chỉ của biến đó.

Xử lý panic

Hai hàm dựng sẵn, panicrecover, hỗ trợ trong việc báo cáo và xử lý panic lúc chạy và các điều kiện lỗi do chương trình định nghĩa.

func panic(interface{})
func recover() interface{}

Trong khi thực thi một hàm F, một lời gọi tường minh đến panic hoặc một panic lúc chạy sẽ kết thúc việc thực thi F. Bất kỳ hàm nào được trì hoãn bởi F sau đó được thực thi như thường. Tiếp theo, bất kỳ hàm trì hoãn nào được chạy bởi caller của F cũng được chạy, và cứ như vậy cho đến bất kỳ hàm trì hoãn nào bởi hàm cấp cao nhất trong goroutine đang thực thi. Tại thời điểm đó, chương trình bị kết thúc và điều kiện lỗi được báo cáo, bao gồm giá trị của đối số cho panic. Chuỗi kết thúc này được gọi là panicking.

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

Hàm recover cho phép một chương trình quản lý hành vi của một goroutine đang panic. Giả sử một hàm G trì hoãn một hàm D gọi recover và một panic xảy ra trong một hàm trên cùng goroutine mà G đang thực thi. Khi việc chạy các hàm trì hoãn đến D, giá trị trả về của lời gọi recover của D sẽ là giá trị được truyền vào lời gọi của panic. Nếu D trả về bình thường, mà không bắt đầu một panic mới, chuỗi panicking dừng lại. Trong trường hợp đó, trạng thái của các hàm được gọi giữa G và lời gọi đến panic bị loại bỏ, và việc thực thi bình thường tiếp tục. Bất kỳ hàm nào được trì hoãn bởi G trước D sau đó được chạy và việc thực thi G kết thúc bằng cách trả về cho caller của nó.

Giá trị trả về của recovernil khi goroutine không đang panic hoặc recover không được gọi trực tiếp bởi một hàm trì hoãn. Ngược lại, nếu một goroutine đang panic và recover được gọi trực tiếp bởi một hàm trì hoãn, giá trị trả về của recover được đảm bảo không phải nil. Để đảm bảo điều này, việc gọi panic với một giá trị interface nil (hoặc một nil không có kiểu) gây ra một panic lúc chạy.

Hàm protect trong ví dụ dưới đây gọi đối số hàm g và bảo vệ các caller khỏi các panic lúc chạy gây ra bởi g.

func protect(g func()) {
	defer func() {
		log.Println("done")  // Println executes normally even if there is a panic
		if x := recover(); x != nil {
			log.Printf("run time panic: %v", x)
		}
	}()
	log.Println("start")
	g()
}

Bootstrapping

Các bản cài đặt hiện tại cung cấp một số hàm dựng sẵn hữu ích trong quá trình bootstrapping. Các hàm này được ghi lại để đầy đủ nhưng không được đảm bảo sẽ ở lại trong ngôn ngữ. Chúng không trả về kết quả.

Function   Behavior

print      prints all arguments; formatting of arguments is implementation-specific
println    like print but prints spaces between arguments and a newline at the end

Hạn chế bản cài đặt: printprintln không nhất thiết phải chấp nhận các kiểu đối số tùy ý, nhưng việc in các kiểu boolean, số và chuỗi phải được hỗ trợ.

Các gói

Các chương trình Go được xây dựng bằng cách liên kết các gói lại với nhau. Một gói lần lượt được xây dựng từ một hoặc nhiều tệp nguồn cùng nhau khai báo các hằng số, kiểu, biến và hàm thuộc gói và có thể truy cập trong tất cả các tệp của cùng gói đó. Các phần tử đó có thể được xuất và dùng trong gói khác.

Tổ chức tệp nguồn

Mỗi tệp nguồn bao gồm một mệnh đề gói định nghĩa gói mà nó thuộc về, theo sau là một tập hợp có thể rỗng các khai báo import khai báo các gói mà nó muốn sử dụng, theo sau là một tập hợp có thể rỗng các khai báo hàm, kiểu, biến, và hằng số.

SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .

Mệnh đề gói

Một mệnh đề gói bắt đầu mỗi tệp nguồn và định nghĩa gói mà tệp đó thuộc về.

PackageClause = "package" PackageName .
PackageName   = identifier .

PackageName không được là định danh trống.

package math

Một tập hợp các tệp có cùng PackageName tạo thành bản cài đặt của một gói. Một bản cài đặt có thể yêu cầu tất cả các tệp nguồn của một gói phải nằm trong cùng thư mục.

Các khai báo import

Một khai báo import cho biết rằng tệp nguồn chứa khai báo đó phụ thuộc vào chức năng của gói được import (§Khởi tạo và thực thi chương trình) và cho phép truy cập vào các định danh được xuất của gói đó. Khai báo import đặt tên một định danh (PackageName) để dùng để truy cập và một ImportPath chỉ định gói cần được import.

ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec = [ "." | PackageName ] ImportPath .
ImportPath = string_lit .

PackageName được dùng trong định danh đủ điều kiện để truy cập các định danh được xuất của gói trong tệp nguồn đang import. Nó được khai báo trong khối tệp. Nếu PackageName bị bỏ qua, nó mặc định là định danh được chỉ định trong mệnh đề gói của gói được import. Nếu một dấu chấm tường minh (.) xuất hiện thay vì tên, tất cả các định danh được xuất của gói được khai báo trong khối gói của gói đó sẽ được khai báo trong khối tệp của tệp nguồn đang import và phải được truy cập mà không cần bộ định danh.

Cách diễn giải ImportPath phụ thuộc vào bản cài đặt nhưng thường là một chuỗi con của tên tệp đầy đủ của gói được biên dịch và có thể liên quan đến một repository của các gói đã cài đặt.

Hạn chế bản cài đặt: Một trình biên dịch có thể hạn chế ImportPath chỉ là các chuỗi không rỗng chỉ sử dụng các ký tự thuộc về các danh mục chung L, M, N, P và S của Unicode (các ký tự đồ họa không có khoảng trắng) và cũng có thể loại trừ các ký tự !"#$%&'()*,:;<=>?[\]^`{|} và ký tự thay thế Unicode U+FFFD.

Hãy xét một gói đã biên dịch chứa mệnh đề gói package math, xuất hàm Sin, và đã cài đặt gói được biên dịch trong tệp được xác định bởi "lib/math". Bảng này minh họa cách Sin được truy cập trong các tệp import gói sau các dạng khai báo import khác nhau.

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

Một khai báo import khai báo một quan hệ phụ thuộc giữa gói đang import và gói được import. Việc một gói tự import chính nó, trực tiếp hay gián tiếp, hoặc trực tiếp import một gói mà không tham chiếu đến bất kỳ định danh được xuất nào của nó là bất hợp lệ. Để import một gói chỉ vì tác dụng phụ của nó (khởi tạo), hãy dùng định danh trống như tên gói tường minh:

import _ "lib/math"

Một gói ví dụ

Đây là một gói Go hoàn chỉnh cài đặt một sàng nguyên tố đồng thời.

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i  // Send 'i' to channel 'ch'.
	}
}

// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {  // Loop over values received from 'src'.
		if i%prime != 0 {
			dst <- i  // Send 'i' to channel 'dst'.
		}
	}
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
	ch := make(chan int)  // Create a new channel.
	go generate(ch)       // Start generate() as a subprocess.
	for {
		prime := <-ch
		fmt.Print(prime, "\n")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}

func main() {
	sieve()
}

Khởi tạo và thực thi chương trình

Giá trị không

Khi vùng nhớ được cấp phát cho một biến, dù thông qua một khai báo hay một lời gọi new, hoặc khi một giá trị mới được tạo ra, dù thông qua một literal tổng hợp hay một lời gọi make, và không có khởi tạo tường minh nào được cung cấp, biến hoặc giá trị đó được gán một giá trị mặc định. Mỗi phần tử của một biến hoặc giá trị như vậy được đặt về giá trị không của kiểu của nó: false cho boolean, 0 cho các kiểu số, "" cho chuỗi, và nil cho con trỏ, hàm, interface, slice, channel và map. Quá trình khởi tạo này được thực hiện đệ quy, vì vậy ví dụ mỗi phần tử của một mảng struct sẽ có các trường của nó được đặt về không nếu không có giá trị nào được chỉ định.

Hai khai báo đơn giản này tương đương:

var i int
var i int = 0

Sau

type T struct { i int; f float64; next *T }
t := new(T)

những điều sau đây đúng:

t.i == 0
t.f == 0.0
t.next == nil

Tương tự cũng đúng sau

var t T

Khởi tạo gói

Trong một gói, việc khởi tạo biến cấp gói tiến hành từng bước, với mỗi bước chọn biến sớm nhất trong thứ tự khai báo không có phụ thuộc vào các biến chưa được khởi tạo.

Chính xác hơn, một biến cấp gói được coi là sẵn sàng để khởi tạo nếu nó chưa được khởi tạo và hoặc không có biểu thức khởi tạo hoặc biểu thức khởi tạo của nó không có phụ thuộc vào các biến chưa được khởi tạo. Việc khởi tạo tiến hành bằng cách lặp đi lặp lại khởi tạo biến cấp gói tiếp theo sớm nhất trong thứ tự khai báo và sẵn sàng để khởi tạo, cho đến khi không còn biến nào sẵn sàng để khởi tạo.

Nếu bất kỳ biến nào vẫn chưa được khởi tạo khi quá trình này kết thúc, các biến đó là một phần của một hoặc nhiều vòng lặp khởi tạo, và chương trình không hợp lệ.

Nhiều biến ở vế trái của một khai báo biến được khởi tạo bởi một biểu thức đơn (đa giá trị) ở vế phải được khởi tạo cùng nhau: Nếu bất kỳ biến nào ở vế trái được khởi tạo, tất cả các biến đó được khởi tạo trong cùng một bước.

var x = a
var a, b = f() // a and b are initialized together, before x is initialized

Với mục đích khởi tạo gói, các biến trống được xử lý như bất kỳ biến nào khác trong các khai báo.

Thứ tự khai báo của các biến được khai báo trong nhiều tệp được xác định bởi thứ tự mà các tệp được trình bày cho trình biên dịch: Các biến được khai báo trong tệp đầu tiên được khai báo trước bất kỳ biến nào được khai báo trong tệp thứ hai, và cứ như vậy. Để đảm bảo hành vi khởi tạo có thể tái lập được, các hệ thống xây dựng được khuyến khích trình bày nhiều tệp thuộc cùng gói theo thứ tự tên tệp từ điển cho trình biên dịch.

Phân tích phụ thuộc không dựa vào các giá trị thực tế của các biến, chỉ dựa vào các tham chiếu từ vựng đến chúng trong nguồn, được phân tích theo truyền bắc cầu. Ví dụ, nếu biểu thức khởi tạo của một biến x tham chiếu đến một hàm có thân tham chiếu đến biến y thì x phụ thuộc vào y. Cụ thể:

  • Một tham chiếu đến một biến hoặc hàm là một định danh biểu thị biến hoặc hàm đó.
  • Một tham chiếu đến một phương thức m là một giá trị phương thức hoặc biểu thức phương thức có dạng t.m, trong đó kiểu (tĩnh) của t là không phải kiểu interface, và phương thức m nằm trong tập phương thức của t. Việc giá trị hàm kết quả t.m có được gọi hay không là không quan trọng.
  • Một biến, hàm, hoặc phương thức x phụ thuộc vào một biến y nếu biểu thức khởi tạo hoặc thân (đối với hàm và phương thức) của x chứa một tham chiếu đến y hoặc đến một hàm hay phương thức phụ thuộc vào y.

Ví dụ, với các khai báo sau

var (
	a = c + b  // == 9
	b = f()    // == 4
	c = f()    // == 5
	d = 3      // == 5 after initialization has finished
)

func f() int {
	d++
	return d
}

thứ tự khởi tạo là d, b, c, a. Lưu ý rằng thứ tự của các biểu thức con trong các biểu thức khởi tạo là không liên quan: a = c + ba = b + c dẫn đến cùng thứ tự khởi tạo trong ví dụ này.

Phân tích phụ thuộc được thực hiện theo từng gói; chỉ các tham chiếu đến các biến, hàm và phương thức (không phải interface) được khai báo trong gói hiện tại mới được xem xét. Nếu có các phụ thuộc dữ liệu ẩn khác giữa các biến, thứ tự khởi tạo giữa các biến đó là không được chỉ định.

Ví dụ, với các khai báo sau

var x = I(T{}).ab()   // x has an undetected, hidden dependency on a and b
var _ = sideEffect()  // unrelated to x, a, or b
var a = b
var b = 42

type I interface      { ab() []int }
type T struct{}
func (T) ab() []int   { return []int{a, b} }

biến a sẽ được khởi tạo sau b nhưng việc x được khởi tạo trước b, giữa ba, hay sau a, và do đó thời điểm sideEffect() được gọi (trước hay sau khi x được khởi tạo) là không được chỉ định.

Các biến cũng có thể được khởi tạo bằng cách dùng các hàm có tên init được khai báo trong khối gói, không có đối số và không có tham số kết quả.

func init() { … }

Nhiều hàm như vậy có thể được định nghĩa trong mỗi gói, thậm chí trong một tệp nguồn duy nhất. Trong khối gói, định danh init chỉ có thể được dùng để khai báo các hàm init, nhưng định danh chính nó không được khai báo. Do đó các hàm init không thể được tham chiếu từ bất cứ đâu trong một chương trình.

Toàn bộ gói được khởi tạo bằng cách gán các giá trị ban đầu cho tất cả các biến cấp gói của nó, theo sau là gọi tất cả các hàm init theo thứ tự chúng xuất hiện trong nguồn, có thể trong nhiều tệp, như được trình bày cho trình biên dịch.

Khởi tạo chương trình

Các gói của một chương trình hoàn chỉnh được khởi tạo từng bước, mỗi lần một gói. Nếu một gói có các import, các gói được import sẽ được khởi tạo trước khi khởi tạo chính gói đó. Nếu nhiều gói import một gói, gói được import sẽ chỉ được khởi tạo một lần. Việc import các gói, theo cấu trúc, đảm bảo rằng không thể có các phụ thuộc khởi tạo tuần hoàn. Chính xác hơn:

Với danh sách tất cả các gói, được sắp xếp theo đường dẫn import, trong mỗi bước gói đầu tiên chưa được khởi tạo trong danh sách mà tất cả các gói được import (nếu có) đã được khởi tạo sẽ được khởi tạo. Bước này được lặp lại cho đến khi tất cả các gói được khởi tạo.

Khởi tạo gói—khởi tạo biến và việc gọi các hàm init—xảy ra trong một goroutine duy nhất, tuần tự, mỗi lần một gói. Một hàm init có thể khởi chạy các goroutine khác, có thể chạy đồng thời với mã khởi tạo. Tuy nhiên, quá trình khởi tạo luôn xếp hàng các hàm init: nó sẽ không gọi hàm tiếp theo cho đến khi hàm trước đó đã trả về.

Thực thi chương trình

Một chương trình hoàn chỉnh được tạo ra bằng cách liên kết một gói duy nhất chưa được import được gọi là gói main với tất cả các gói mà nó import, theo cách bắc cầu. Gói main phải có tên gói main và khai báo một hàm main không nhận đối số và không trả về giá trị.

func main() { … }

Việc thực thi chương trình bắt đầu bằng cách khởi tạo chương trình và sau đó gọi hàm main trong gói main. Khi lời gọi hàm đó trả về, chương trình thoát. Nó không chờ các goroutine khác (không phải main) hoàn thành.

Lỗi

Kiểu được khai báo trước error được định nghĩa là

type error interface {
	Error() string
}

Đây là interface thông thường để biểu diễn một điều kiện lỗi, với giá trị nil biểu thị không có lỗi. Ví dụ, một hàm để đọc dữ liệu từ một tệp có thể được định nghĩa:

func Read(f *File, b []byte) (n int, err error)

Panic lúc chạy

Các lỗi thực thi chẳng hạn như cố gắng đánh chỉ số một mảng ngoài giới hạn kích thước kích hoạt một panic lúc chạy tương đương với một lời gọi của hàm dựng sẵn panic với một giá trị của kiểu interface do bản cài đặt định nghĩa runtime.Error. Kiểu đó thỏa mãn kiểu interface được khai báo trước error. Các giá trị lỗi chính xác biểu diễn các điều kiện lỗi lúc chạy khác nhau là không được chỉ định.

package runtime

type Error interface {
	error
	// and perhaps other methods
}

Các lưu ý hệ thống

Gói unsafe

Gói dựng sẵn unsafe, được trình biên dịch biết đến và có thể truy cập thông qua đường dẫn import "unsafe", cung cấp các tiện ích cho lập trình cấp thấp bao gồm các thao tác vi phạm hệ thống kiểu. Một gói sử dụng unsafe phải được kiểm tra thủ công về an toàn kiểu và có thể không mang tính di động. Gói cung cấp interface sau:

package unsafe

type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

type IntegerType int  // shorthand for an integer type; it is not a real type
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
func SliceData(slice []ArbitraryType) *ArbitraryType
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte

Một Pointer là một kiểu con trỏ nhưng một giá trị Pointer không thể được giải tham chiếu. Bất kỳ con trỏ hay giá trị nào có kiểu cơ sở uintptr đều có thể được chuyển đổi sang một kiểu có kiểu cơ sở Pointer và ngược lại. Nếu các kiểu tương ứng là tham số kiểu, tất cả các kiểu trong các tập kiểu tương ứng của chúng phải có cùng kiểu cơ sở, lần lượt là uintptrPointer. Hiệu ứng của việc chuyển đổi giữa Pointeruintptr phụ thuộc vào bản cài đặt.

var f float64
bits = *(*uint64)(unsafe.Pointer(&f))

type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))

func f[P ~*B, B any](p P) uintptr {
	return uintptr(unsafe.Pointer(p))
}

var p ptr = nil

Các hàm AlignofSizeof nhận một biểu thức x của bất kỳ kiểu nào và trả về căn chỉnh hoặc kích thước, tương ứng, của một biến giả thuyết v như thể v được khai báo qua var v = x.

Hàm Offsetof nhận một selector (có thể có dấu ngoặc đơn) s.f, biểu thị một trường f của struct được biểu thị bởi s hoặc *s, và trả về độ lệch trường tính bằng byte so với địa chỉ của struct. Nếu f là một trường nhúng, nó phải có thể tiếp cận được mà không cần đi qua con trỏ gián tiếp qua các trường của struct. Đối với một struct s có trường f:

uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))

Các kiến trúc máy tính có thể yêu cầu các địa chỉ bộ nhớ phải được căn chỉnh; tức là, đối với các địa chỉ của một biến phải là bội số của một hệ số, căn chỉnh của kiểu biến. Hàm Alignof nhận một biểu thức biểu thị một biến của bất kỳ kiểu nào và trả về căn chỉnh của (kiểu) biến tính bằng byte. Đối với một biến x:

uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

Một (biến của) kiểu Tkích thước biến đổi nếu T là một tham số kiểu, hoặc nếu nó là một kiểu mảng hay struct chứa các phần tử hoặc trường có kích thước biến đổi. Ngược lại kích thước là hằng số. Các lời gọi đến Alignof, Offsetof, và Sizeof là các biểu thức hằng số lúc biên dịch có kiểu uintptr nếu các đối số của chúng (hoặc struct s trong biểu thức selector s.f đối với Offsetof) là các kiểu có kích thước hằng số.

Hàm Add cộng len vào ptr và trả về con trỏ đã được cập nhật unsafe.Pointer(uintptr(ptr) + uintptr(len)) [Go 1.17]. Đối số len phải có kiểu nguyên hoặc là một hằng số không có kiểu. Một đối số len hằng số phải có thể biểu diễn bởi một giá trị có kiểu int; nếu nó là một hằng số không có kiểu thì được gán kiểu int. Các quy tắc cho các cách dùng hợp lệ của Pointer vẫn áp dụng.

Hàm Slice trả về một slice có mảng cơ sở bắt đầu tại ptr và có độ dài và dung lượng là len. Slice(ptr, len) tương đương với

(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]

ngoại trừ, như một trường hợp đặc biệt, nếu ptrnillen bằng không, Slice trả về nil [Go 1.17].

Đối số len phải có kiểu nguyên hoặc là một hằng số không có kiểu. Một đối số len hằng số phải không âm và có thể biểu diễn bởi một giá trị có kiểu int; nếu nó là một hằng số không có kiểu thì được gán kiểu int. Lúc chạy, nếu len âm, hoặc nếu ptrnillen khác không, một panic lúc chạy xảy ra [Go 1.17].

Hàm SliceData trả về một con trỏ đến mảng cơ sở của đối số slice. Nếu dung lượng của slice cap(slice) khác không, con trỏ đó là &slice[:1][0]. Nếu slicenil, kết quả là nil. Ngược lại nó là một con trỏ không phải nil đến một địa chỉ bộ nhớ không xác định [Go 1.20].

Hàm String trả về một giá trị string có các byte cơ sở bắt đầu tại ptr và có độ dài là len. Các yêu cầu tương tự áp dụng cho đối số ptrlen như trong hàm Slice. Nếu len bằng không, kết quả là chuỗi rỗng "". Vì các chuỗi Go là bất biến, các byte được truyền vào String không được sửa đổi sau đó. [Go 1.20]

Hàm StringData trả về một con trỏ đến các byte cơ sở của đối số str. Đối với một chuỗi rỗng, giá trị trả về là không xác định và có thể là nil. Vì các chuỗi Go là bất biến, các byte được trả về bởi StringData không được sửa đổi [Go 1.20].

Đảm bảo kích thước và căn chỉnh

Đối với các kiểu số, các kích thước sau được đảm bảo:

type                                 size in bytes

byte, uint8, int8                     1
uint16, int16                         2
uint32, int32, float32                4
uint64, int64, float64, complex64     8
complex128                           16

Các thuộc tính căn chỉnh tối thiểu sau được đảm bảo:

  1. Đối với một biến x của bất kỳ kiểu nào: unsafe.Alignof(x) là ít nhất 1.
  2. Đối với một biến x của kiểu struct: unsafe.Alignof(x) là giá trị lớn nhất trong tất cả các giá trị unsafe.Alignof(x.f) cho mỗi trường f của x, nhưng ít nhất là 1.
  3. Đối với một biến x của kiểu mảng: unsafe.Alignof(x) giống với căn chỉnh của một biến có kiểu phần tử của mảng.

Một kiểu struct hoặc mảng có kích thước không nếu nó không chứa các trường (hoặc phần tử, tương ứng) có kích thước lớn hơn không. Hai biến có kích thước không khác biệt có thể có cùng địa chỉ trong bộ nhớ.

Phụ lục

Các phiên bản ngôn ngữ

Đảm bảo tương thích Go 1 đảm bảo rằng các chương trình được viết theo đặc tả Go 1 sẽ tiếp tục biên dịch và chạy đúng, không thay đổi, trong suốt thời gian tồn tại của đặc tả đó. Tổng quát hơn, khi các điều chỉnh được thực hiện và các tính năng được thêm vào ngôn ngữ, đảm bảo tương thích đảm bảo rằng một chương trình Go hoạt động với một phiên bản ngôn ngữ Go cụ thể sẽ tiếp tục hoạt động với bất kỳ phiên bản tiếp theo nào.

Ví dụ, khả năng sử dụng tiền tố 0b cho các literal nguyên nhị phân được giới thiệu với Go 1.13, được chỉ ra bởi [Go 1.13] trong phần về literal nguyên. Mã nguồn chứa một literal nguyên như 0b1011 sẽ bị từ chối nếu phiên bản ngôn ngữ ngầm định hoặc bắt buộc được sử dụng bởi trình biên dịch cũ hơn Go 1.13.

Bảng sau mô tả phiên bản ngôn ngữ tối thiểu cần thiết cho các tính năng được giới thiệu sau Go 1.

Go 1.9

  • Một khai báo bí danh có thể được dùng để khai báo tên bí danh cho một kiểu.

Go 1.13

  • Các literal nguyên có thể dùng các tiền tố 0b, 0B, 0o, và 0O cho các literal nhị phân và bát phân, tương ứng.
  • Các literal dấu phẩy động thập lục phân có thể được viết bằng các tiền tố 0x0X.
  • Hậu tố ảo i có thể được dùng với bất kỳ literal nguyên hoặc dấu phẩy động nào (nhị phân, thập phân, thập lục phân), không chỉ các literal thập phân.
  • Các chữ số của bất kỳ literal số nào có thể được phân tách (nhóm) bằng dấu gạch dưới _.
  • Số lượng dịch chuyển trong một thao tác dịch chuyển có thể là kiểu nguyên có dấu.

Go 1.14

  • Nhúng một phương thức nhiều hơn một lần thông qua các interface nhúng khác nhau không phải là lỗi.

Go 1.17

  • Một slice có thể được chuyển đổi sang con trỏ mảng nếu kiểu phần tử của slice và mảng khớp nhau, và mảng không dài hơn slice.
  • Gói dựng sẵn unsafe bao gồm các hàm mới AddSlice.

Go 1.18

Bản phát hành 1.18 thêm các hàm và kiểu đa hình ("generic") vào ngôn ngữ. Cụ thể:

Go 1.20

  • Một slice có thể được chuyển đổi sang mảng nếu kiểu phần tử của slice và mảng khớp nhau và mảng không dài hơn slice.
  • Gói dựng sẵn unsafe bao gồm các hàm mới SliceData, String, và StringData.
  • Các kiểu có thể so sánh (chẳng hạn như các interface thông thường) có thể thỏa mãn ràng buộc comparable, ngay cả khi các đối số kiểu không hoàn toàn có thể so sánh.

Go 1.21

  • Tập các hàm được khai báo trước bao gồm các hàm mới min, max, và clear.
  • Suy luận kiểu sử dụng các kiểu của các phương thức interface để suy luận. Nó cũng suy luận các đối số kiểu cho các hàm generic được gán cho các biến hoặc được truyền làm đối số cho các hàm khác (có thể là generic).

Go 1.22

  • Trong một câu lệnh "for", mỗi lần lặp có tập biến lặp riêng của nó thay vì chia sẻ cùng các biến trong mỗi lần lặp.
  • Một câu lệnh "for" với mệnh đề "range" có thể lặp qua các giá trị nguyên từ không đến một giới hạn trên.

Go 1.23

  • Một câu lệnh "for" với mệnh đề "range" chấp nhận một hàm iterator làm biểu thức range.

Go 1.24

Các quy tắc hợp nhất kiểu

Các quy tắc hợp nhất kiểu mô tả liệu và cách hai kiểu hợp nhất với nhau. Các chi tiết chính xác có liên quan đến các bản cài đặt Go, ảnh hưởng đến các chi tiết cụ thể của thông báo lỗi (chẳng hạn như việc trình biên dịch báo cáo lỗi suy luận kiểu hay lỗi khác), và có thể giải thích lý do tại sao suy luận kiểu thất bại trong các tình huống mã bất thường. Nhưng nhìn chung các quy tắc này có thể được bỏ qua khi viết mã Go: suy luận kiểu được thiết kế để phần lớn "hoạt động như mong đợi", và các quy tắc hợp nhất được tinh chỉnh cho phù hợp.

Hợp nhất kiểu được kiểm soát bởi một chế độ khớp, có thể là chính xác hoặc lỏng. Khi hợp nhất đệ quy đi xuống một cấu trúc kiểu tổng hợp, chế độ khớp được sử dụng cho các phần tử của kiểu, chế độ khớp phần tử, vẫn giống với chế độ khớp ngoại trừ khi hai kiểu được hợp nhất để có thể gán (A): trong trường hợp này, chế độ khớp là lỏng ở cấp cao nhất nhưng sau đó thay đổi thành chính xác cho các kiểu phần tử, phản ánh thực tế rằng các kiểu không cần phải giống hệt nhau để có thể gán được.

Hai kiểu không phải tham số kiểu ràng buộc hợp nhất chính xác nếu bất kỳ điều kiện nào sau đây là đúng:

  • Cả hai kiểu đều giống hệt nhau.
  • Cả hai kiểu đều có cấu trúc giống hệt nhau và các kiểu phần tử của chúng hợp nhất chính xác.
  • Đúng một kiểu là một tham số kiểu không ràng buộc, và tất cả các kiểu trong tập kiểu của nó hợp nhất với kiểu kia theo các quy tắc hợp nhất cho A (hợp nhất lỏng ở cấp cao nhất và hợp nhất chính xác cho các kiểu phần tử).

Nếu cả hai kiểu đều là tham số kiểu ràng buộc, chúng hợp nhất theo các chế độ khớp đã cho nếu:

  • Cả hai tham số kiểu đều giống hệt nhau.
  • Nhiều nhất một trong các tham số kiểu có đối số kiểu đã biết. Trong trường hợp này, các tham số kiểu được kết hợp: cả hai đều đại diện cho cùng một đối số kiểu. Nếu không có tham số kiểu nào có đối số kiểu đã biết, một đối số kiểu trong tương lai được suy luận cho một trong các tham số kiểu đồng thời được suy luận cho cả hai.
  • Cả hai tham số kiểu đều có đối số kiểu đã biết và các đối số kiểu hợp nhất theo các chế độ khớp đã cho.

Một tham số kiểu ràng buộc đơn P và một kiểu khác T hợp nhất theo các chế độ khớp đã cho nếu:

  • P không có đối số kiểu đã biết. Trong trường hợp này, T được suy luận là đối số kiểu cho P.
  • P có đối số kiểu đã biết A, AT hợp nhất theo các chế độ khớp đã cho, và một trong các điều kiện sau là đúng:
    • Cả AT đều là kiểu interface: Trong trường hợp này, nếu cả AT đều là các kiểu đã định nghĩa, chúng phải giống hệt nhau. Ngược lại, nếu không có kiểu nào trong số chúng là kiểu đã định nghĩa, chúng phải có cùng số lượng phương thức (sự hợp nhất của AT đã xác lập rằng các phương thức khớp nhau).
    • Cả A lẫn T đều không phải kiểu interface: Trong trường hợp này, nếu T là kiểu đã định nghĩa, T thay thế A làm đối số kiểu được suy luận cho P.

Cuối cùng, hai kiểu không phải tham số kiểu ràng buộc hợp nhất lỏng (và theo chế độ khớp phần tử) nếu:

  • Cả hai kiểu hợp nhất chính xác.
  • Một kiểu là kiểu đã định nghĩa, kiểu kia là một literal kiểu, nhưng không phải interface, và các kiểu cơ sở của chúng hợp nhất theo chế độ khớp phần tử.
  • Cả hai kiểu đều là interface (nhưng không phải tham số kiểu) với các số hạng kiểu giống hệt nhau, cả hai hoặc không kiểu nào nhúng kiểu được khai báo trước có thể so sánh, các kiểu phương thức tương ứng hợp nhất chính xác, và tập phương thức của một trong các interface là tập con của tập phương thức của interface kia.
  • Chỉ một kiểu là interface (nhưng không phải tham số kiểu), các phương thức tương ứng của hai kiểu hợp nhất theo chế độ khớp phần tử, và tập phương thức của interface là tập con của tập phương thức của kiểu kia.
  • Cả hai kiểu đều có cùng cấu trúc và các kiểu phần tử của chúng hợp nhất theo chế độ khớp phần tử.