Pendahuluan
Dokumen ini adalah referensi manual untuk bahasa pemrograman Go. Untuk informasi dan dokumentasi lainnya lihat golang.org (Inggris) atau golang-id.org (Indonesia).
Go adalah bahasa pemrograman yang dirancang untuk pemrograman sistem. Go adalah bahasa dengan tipe yang kuat dan dengan garbage-collection dan mendukung pemrograman konkuren secara eksplisit. Program dibangun dari paket-paket, yang membolehkan ketergantungan paket secara efisien.
Tata-bahasa dari Go padat dan teratur, membuatnya mudah untuk dianalisis oleh perkakas-perkakas otomatis seperti lingkungan pengembangan integrasi (IDE).
Notasi
Spesifikasi sintaksis menggunakan Extended Backus-Naur Form (EBNF):
Production = production_name "=" [ Expression ] "." . Expression = Alternative { "|" Alternative } . Alternative = Term { Term } . Term = production_name | token [ "…" token ] | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . Repetition = "{" Expression "}" .
Production adalah ekspresi yang dibangun dari sekumpulan term dan operator pendukung berikut, secara berurut:
| alternasi () pengelompokan [] opsi (0 atau 1 kali) {} pengulangan (0 sampai n kali)
Nama production_name
dengan huruf kecil digunakan untuk mengidentifikasi
token leksikal.
production_name
dengan CamelCase adalah non-terminal (tidak berakhir).
Token leksikal dibungkus dalam tanda kutip ganda " " atau kutip terbalik .
Bentuk a … b
merepresentasikan kumpulan huruf alternatif dari a
sampai
b
.
Elipsis horizontal …
juga digunakan dalam spesifikasi ini
untuk secara informal menandakan enumerasi atau kode yang dipotong.
Karakter …
(berlawanan dengan tiga karakter …) bukanlah token dari
bahasa Go.
Representasi sumber kode
Sumber kode adalah teks Unicode dalam UTF-8. Teks tersebut tidak kanonis, sehingga titik kode dengan aksen berbeda dengan karakter yang sama yang dibangun dari menggabungkan sebuah aksen dan sebuah huruf; keduanya dianggap sebagai dua titik kode. Demi kemudahan, dokumen ini menggunakan istilah karakter untuk mengacu pada titik kode Unicode dalam teks sumber kode.
Setiap titik kode berbeda satu dengan yang lainnya; misalnya, huruf besar dan kecil adalah karakter yang berbeda.
Batasan implementasi: Untuk kompatibilitas dengan perkakas lainnya, compiler mungkin tidak membolehkan karakter NUL (U+0000) dalam teks sumber kode.
Batasan implementasi: Untuk kompatibilitas dengan perkakas lainnya, compiler bisa mengindahkan penanda urutan byte untuk UTF-8-encoded (U+FEFF) jika ia adalah titik kode Unicode pertama dalam teks sumber kode. Penanda urutan byte bisa saja tidak dibolehkan dalam sumber kode.
Karakter
Istilah berikut digunakan untuk menandakan kelas karakter Unicode tertentu:
newline = /* titik kode Unicode U+000A untuk baris baru */ . unicode_char = /* titik kode Unicode apa pun kecuali baris baru */ . unicode_letter = /* titik kode Unicode dikelompokkan sebagai "Letter" (huruf) */ . unicode_digit = /* titik kode Unicode dikelompokkan sebagai "Number, decimal digit" */ .
The Unicode Standard 8.0, Bagian 4.5 "General Category" mendefinisikan sekumpulan kategori karakter. Go mengenali semua karakter dalam kategori Letter berikut Lu, Ll, Lt, Lm, atau Lo sebagai huruf Unicode, dan yang berada dalam kategori Number Nd sebagai angka Unicode.
Huruf dan angka
Karakter garis bawah _
(U+005F) dianggap sebagai huruf.
letter = unicode_letter | "_" . decimal_digit = "0" … "9" . octal_digit = "0" … "7" . hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
Elemen leksikal
Komentar
Komentar berfungsi sebagai dokumentasi program. Ada dua bentuk komentar:
-
Komentar baris dimulai dengan urutan karakter
//
dan berakhir pada ujung baris. -
Komentar umum dimulai dengan urutan karakter
/
dan berakhir dengan urutan karakter/
yang pertama.
Token
Token-token membentuk kosakata dari bahasa Go. Ada empat kelas token: identifiers (pengidentifikasi), keywords (kata-kunci), operators dan punctuation (operator dan tanda baca), dan literal (literal). Karakter kosong yang dibentuk dari spasi (U+0020), tab horizontal (U+0009), carriage returns (U+0009), dan baris baru (U+000A), diindahkan kecuali ia memisahkan token-token yang bila digabungkan menjadi sebuah token. Juga, sebuah baris baru atau akhir dari berkas bisa memicu titik-koma. Saat memecah input menjadi token-token, token selanjutnya adalah urutan karakter terpanjang yang membentuk sebuah token yang valid.
Titik-koma
Tata-bahasa formal menggunakan titik-koma ";" sebagai penanda akhir pada beberapa pernyataan. Program Go bisa menghilangkan titik-koma tersebut menggunakan dua aturan berikut:
-
Saat input terpecah menjadi token-token, sebuah titik-koma secara otomatis langsung ditambahkan ke dalam aliran token setelah token terakhir pada sebuah baris jika token tersebut adalah
-
sebuah pengidentifikasi
-
sebuah integer, floating-point, rune, atau string
-
salah satu kata-kunci
break
,continue
,fallthrough
, ataureturn
-
salah satu operator dan tanda baca
,
—
,)
,]
, atau}
-
-
Untuk membolehkan perintah yang kompleks menghabiskan satu baris, titik-koma bisa dihilangkan sebelum ditutup oleh ")" atau "}"
Untuk merefleksikan penggunaan idiomatis, contoh kode dalam dokumen ini tidak menggunakan titik-koma menggunakan aturan di atas.
Pengidentifikasi
Pengidentifikasi memberi nama entitas seperti variabel dan tipe. Sebuah pengidentifikasi adalah sebuah urutan satu atau lebih huruf dan angka. Karakter pertama dalam sebuah pengidentifikasi haruslah huruf.
identifier = letter { letter | unicode_digit } .
a _x9 ThisVariableIsExported αβ
Beberapa pengidentifikasi telah dideklarasikan sebelumnya.
Kata kunci
Kata kunci berikut telah disiapkan dan tidak bisa digunakan sebagai pengidentifikasi:
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
Operator dan tanda baca
Urutan karakter berikut merepresentasikan operator (termasuk operator penempatan) dan tanda baca:
+ & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^=
Literal integer
Literal integer adalah urutan angka merepresentasikan sebuah
konstan integer.
Beberapa opsi prefiks menyatakan basis non-desimal: 0
untuk oktal, 0x
atau 0x
untuk heksadesimal.
Dalam literal heksadesimal, huruf a-f
dan A-F
merepresentasikan nilai antara
10 sampai 155.
int_lit = decimal_lit | octal_lit | hex_lit . decimal_lit = ( "1" … "9" ) { decimal_digit } . octal_lit = "0" { octal_digit } . hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
42 0600 0xBadFace 170141183460469231731687303715884105727
Literal floating-point
Literal floating-point adalah representasi desimal dari
konstan floating-point.
Ia memiliki bagian integer, titik desimal, bagian pecahan, dan bagian
eksponen.
Bagian integer dan pecahan terdiri dari angka desimal;
bagian eksponen yaitu e
atau E
diikuti dengan nilai eksponen.
Salah satu bagian integer atau pecahan bisa diindahkan;
salah satu bagian pecahan atau eksponen bisa diindahkan juga.
float_lit = decimals "." [ decimals ] [ exponent ] | decimals exponent | "." decimals [ exponent ] . decimals = decimal_digit { decimal_digit } . exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5
Literal imajiner
Literal imajiner merepresentasikan bagian imajiner dari
konstan kompleks.
Ia terdiri dari literal
integer
atau
floating-point
diikuti oleh huruf kecil i
.
Nilai dari literal imajiner yaitu nilai dari literal integer atau floating-point
dikalikan dengan unit imajiner i.
imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
Untuk kompatibilitas, bagian integer dari literal imajiner yang
semuanya terdiri dari angka desimal (dan kemungkinan garis bawah) dianggap
sebagai integer desimal, walaupun dimulai dengan 0
.
0i 0123i // == 123i untuk kompatibilitas 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
Literal rune merepresentasikan sebuah konstan rune, sebuah nilai integer yang mengidentifikasi sebuah kode poin Unicode. Literal rune diekspresikan dengan satu atau lebih karakter yang ditutup dengan tanda kutip tunggal, seperti ’x’` atau ’n'`. Di dalam tanda kutip tersebut, karakter apa pun dapat ditulis kecuali baris baru dan tanda kutip tunggal itu sendiri. Sebuah karakter dengan kutip merepresentasikan nilai Unicode dari karakter itu sendiri, sementara seurutan karakter yang dimulai dengan garis miring terbalik (backslash) akan ditulis nilainya menjadi beragam format.
Bentuk sederhana dari rune merepresentasikan karakter tunggal antara tanda
kutip; secara teks sumber kode dari Go adalah karakter Unicode yang ditulis
dalam UTF-8, multipel UTF-8 byte bisa merepresentasikan sebuah nilai integer.
Misalnya, literal ’a’` menyimpan sebuah byte yang merepresentasikan sebuah
literal a
, Unicode U+0061, dengan nilai 0x61
; sementara ’ä'` menyimpan dua
byte (0xc3 0xa4
) yang merepresentasikan literal a-dwititik, U+00E4, nilai
0xe4
.
Beberapa backslash membolehkan nilai beragam ditulis sebagai teks ASCII. Ada empat cara untuk merepresentasikan nilai integer sebagai konstan numerik: `x` diikuti dengan dua digit heksadesimal; `u` diikuti dengan empat digit heksadesimal; `U` diikuti dengan delapan heksadesimal, dan backslash kosong `` diikuti dengan tiga digit oktal. Pada setiap kasus tersebut nilai dari literal adalah nilai yang direpresentasikan oleh digit pada basis yang berhubungan.
Walaupun representasi tersebut semua hasilnya adalah sebuah integer, mereka
memiliki rentang yang berbeda.
Oktal harus merepresentasikan sebuah nilai antara 0 dan 255 secara inklusif.
Heksadesimal memenuhi kondisi ini secara konstruksi.
u` dan `U` merepresentasikan kode poin Unicode sehingga beberapa nilai
adalah ilegal, khususnya nilai di atas `0x10FFFF
dan surrogate half
(bagian setengah atas dari empat heksadesimal).
Setelah tanda backslash, beberapa karakter tunggal merepresentasikan nilai spesial:
\a U+0007 alert atau bel \b U+0008 backspace \f U+000C form feed \n U+000A line feed atau newline \r U+000D carriage return \t U+0009 tab horizontal \v U+000b tab vertikal \\ U+005c backslash (garis miring terbalik) \' U+0027 tanda kutip tunggal (valid hanya pada literal rune) \" U+0022 tanda kutip ganda (valid hanya pada literal string)
Urutan karakter lain yang dimulai dengan sebuah backslash adalah ilegal di dalam literal rune.
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' '\'' // literal rune berisi sebuah karakter kutip 'aa' // ilegal: terlalu banyak karaketer '\xa' // ilegal: terlalu sedikit digit heksadesimal '\0' // ilegal: terlalu sedikit digit oktal '\uDFFF' // ilegal: setengah '\U00110000' // ilegal: invalid Unicode
Literal string
Literal string merepresentasikan konstan string yang didapat dari menggabungkan seurutan karakter-karakter. Ada dua bentuk string: literal string mentah dan literal string interpretasi.
Literal string mentah yaitu seurutan karakter antara kutip terbalik (aksen
nontirus), seperti dalam foo
.
Di antara tanda kutip terbalik, karakter apa pun dapat muncul kecuali tanda
kutip terbalik itu sendiri.
Nilai dari literal string mentah tersebut yaitu string yang terdiri dari
karakter-karakter yang tidak diinterpretasi (implisit UTF-8) di antara tanda
kutip;
pada khususnya, backslash tidak berfungsi dan string bisa memiliki baris
baru.
Karakter carriage return (’r'`) di dalam literal string mentah diindahkan
dari nilai string mentah.
Literal string interpretasi yaitu seurutan karakter antara tanda kutip ganda,
seperti dalam”bar".
Di antara tanda kutip, karakter apa pun bisa muncul kecuali baris baru dan
tanda kutip ganda lepas (tanpa backslash).
Teks antara tanda kutip membentuk nilai dari literal, dengan backslash
diartikan seperti halnya dalam
literal rune
(kecuali `'
adalah ilegal dan "
adalah legal), dengan batasan-batasan
yang sama.
Tiga-digit oktal (nnn_) dan dua-digit heksadesimal (x_nn)
merepresentasikan byte individu dari string;
karakter lepas lainnya merepresentasikan encoding UTF-8 (bisa jadi
multi-byte) dari karakter-karakter tersebut.
Maka di dalam literal string, 377` dan `xFF` merepresentasikan nilai
`0xFF
=255, sementara ÿ, `u00FF`, `U000000FF` dan `xc3\xbf
merepresentasikan dua byte 0xc3 0xbf
dari encoding karakter UTF-8 U+00FF.
string_lit = raw_string_lit | interpreted_string_lit . raw_string_lit = "`" { unicode_char | newline } "`" . interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // sama dengan "abc" `\n \n` // sama dengan "\\n\n\\n" "\n" "\"" // sama dengan `"` "Hello, world!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // ilegal: _surrogate half_ "\U00110000" // ilegal: invalid kode poin Unicode
Contoh berikut merepresentasikan string yang sama:
"日本語" // UTF-8 input teks `日本語` // UTF-8 input teks sebagai literal mentah "\u65e5\u672c\u8a9e" // kode poin Unicode eksplisit "\U000065e5\U0000672c\U00008a9e" // kode poin Unicode eksplisit "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // UTF-8 byte eksplisit
Jika sumber kode merepresentasikan sebuah karakter sebagai dua kode poin, seperti sebuah gabungan bentuk mengikutkan aksen dan sebuah huruf, hasilnya akan eror jika ditempatkan dalam sebuah literal rune (karena bukanlah kode poin tunggal), dan akan muncul sebagai dua kode poin jika ditempatkan dalam literal string.
Konstan
Ada konstan boolean, konstan rune, konstan integer, konstan floating-point, konstan complex, dan konstan string. Konstan rune, integer, floating-point, dan complex secara kolektif disebut dengan konstan numerik.
Nilai sebuah konstan direpresentasikan oleh sebuah literal
rune,
integer,
floating-point,
imajiner, atau
string,
sebuah pengidentifikasi yang menyatakan sebuah konstan,
sebuah
ekspresi konstan,
sebuah
konversi
dengan hasil sebuah konstan, atau nilai hasil dari fungsi bawaan seperti
unsafe.Sizeof
yang diterapkan terhadap nilai apa pun, cap
atau len
yang
diterapkan pada
beberapa ekspresi,
real
dan imag
yang diterapkan pada konstan complex dan
complex
yang diterapkan pada konstan numerik.
Nilai boolean direpresentasikan dengan konstan true
dan false
.
Identifikasi
iota
menyatakan konstan integer.
Secara umum, konstan complex adalah sebuah bentuk dari ekspresi konstan dan didiskusikan di bagian tersebut.
Konstan numerik merepresentasikan nilai eksak dari presisi beragam dan tidak overflow. Karena itu, tidak ada konstan yang menyatakan nilai negatif nol IEEE-754, infinity, dan nilai bukan-angka.
Konstan bisa bertipe atau tak bertipe.
Konstan harfiah, true
, false
, iota
, dan beberapa
ekspresi konstan
yang berisi hanya operan konstan tanpa tipe adalah konstan tak bertipe.
Sebuah konstan bisa diberikan tipe secara eksplisit dengan deklarasi konstan atau konversi, atau secara implisit bila digunakan dalam sebuah deklarasi variabel atau dalam sebuah penempatan atau sebagai sebuah operan dalam sebuah ekspresi. Jika nilai konstan tidak dapat direpresentasikan sebagai sebuah nilai dari tipe maka ia akan eror.
Konstan tak bertipe memiliki tipe bawaan yaitu tipe yang mana konstan
secara implisit dikonversi dalam konteks di mana nilai bertipe dibutuhkan,
misalnya,
dalam
deklarasi variabel singkat
seperti i := 0
yang mana tidak ada tipe eksplisit.
Tipe bawaan dari konstan tak bertipe adalah bool
, rune
, int
, float64
,
complex128
atau string
, bergantung kepada apakah ia adalah sebuah konstan
boolean, rune, integer, floating-point, complex, atau string.
Batasan implementasi: Walaupun konstan numerik memiliki presisi beragam dalam bahasa Go, compiler bisa saja mengimplementasikan mereka menggunakan representasi internal dengan presisi terbatas. Dengan kata lain, setiap implementasi harus:
-
Merepresentasikan konstan integer paling tidak 256 bit.
-
Merepresentasikan konstan floating-point, termasuk bagian dari konstan complex, dengan mantisa setidaknya 256 bit dan sebuah eksponen binary signed setidaknya 16 bit.
-
Kembalikan eror bila tidak bisa merepresentasikan konstan integer secara tepat.
-
Kembalikan eror bila tidak bisa merepresentasikan sebuah konstan floating-point atau complex karena overflow.
-
Bulatkan ke konstan terdekat yang dapat direpresentasikan jika tidak bisa merepresentasikan konstan floating-point atau complex dikarenakan limit atau presisi.
Kebutuhan-kebutuhan di atas berlaku baik terhadap konstan harfiah dan terhadap hasil dari mengevaluasi ekspresi konstan.
Variabel
Sebuah variabel adalah sebuah lokasi penampung untuk menyimpan sebuah nilai. Kumpulan dari nilai yang diperbolehkan ditentukan oleh tipe variabel.
Sebuah deklarasi variabel atau penanda dari deklarasi fungsi (pada parameter dan kembalian dari fungsi), atau fungsi literal mencadangkan penampung untuk sebuah variabel bernama. Memanggil fungsi bawaan new atau mengambil alamat dari sebuah literal komposit mengalokasikan penampung untuk sebuah variabel pada saat runtime. Variabel anonim diacu lewat sebuah (bisa jadi implisit) pointer tak-langsung.
Variabel berstruktur dari tipe array, slice, dan struct memiliki elemen-elemen dan field-field yang bisa diakses secara individu. Setiap elemen tersebut berlaku seperti sebuah variabel.
Tipe statis (atau tipe) dari sebuah variabel yaitu tipe yang diberikan
saat deklarasi, tipe yang diberikan pada saat pemanggilan new
atau
komposit literal, atau tipe dari sebuah elemen dari variabel berstruktur.
Variabel dari tipe interface memiliki tipe dinamis berbeda, yaitu tipe
konkret dari nilai yang diberikan ke variabel pada saat runtime (kecuali
bila nilainya adalah nil
, yang berarti tak bertipe).
Tipe dinamis bisa beragam selama eksekusi namun nilai yang disimpan dalam
variabel interface selalu
ditempatkan
ke tipe statis dari variabel.
var x interface{} // x adalah nil dengan tipe statis interface{} var v *T // v bernilai nil, tipe statis *T x = 42 // x bernilai 42 dan tipe dinamis int x = v // x bernilai (*T)(nil) dan tipe dinamis *T
Nilai variabel diambil dengan mengacu pada variabel dalam sebuah ekspresi; nilai dari variabel yaitu nilai terakhir yang ditempatkan ke variabel tersebut. Jika sebuah variabel belum diberi nilai, nilainya adalah nilai kosong dari tipe variabel.
Tipe
Sebuah tipe menentukan sekumpulan nilai berikut dengan operasi dan method yang spesifik terhadap nilainya. Sebuah tipe bisa ditulis dengan sebuah nama tipe, jika ada, atau dispesifikasikan menggunakan literal tipe, yang membentuk sebuah tipe dari tipe-tipe yang telah ada.
Type = TypeName | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
Bahasa Go mendeklarasikan beberapa nama tipe. Nama tipe lainnya diperkenalkan lewat deklarasi tipe. Tipe komposit — tipe array, struct, pointer, fungsi, interface, slice, map, dan channel — bisa dibangun menggunakan literal tipe.
Setiap tipe T memiliki tipe dasar: Jika T
adalah salah satu tipe boolean,
numerik, string, literal; make tipe dasar dari T
itu sendiri adalah T
.
Selain itu, tipe dasar dari T
yaitu tipe yang diberikan saat
deklarasi.
type ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 )
Tipe dasar dari string
, A1
, A2
, B1
, dan B2
adalah string
.
Tipe dasar dari []B1
, B3
, dan B4
yaitu []B1
.
Kumpulan method
Sebuah tipe bisa memiliki sekumpulan method.
Kumpulan method dari
tipe interface
yaitu interface-nya sendiri.
Kumpulan method dari tipe T
terdiri dari semua
method-method
yang dideklarasikan pada penerima (receiver) tipe T
.
Kumpulan method dari
tipe pointer
*T
yaitu semua method yang dideklarasikan dengan penerima *T
atau T
(ia
berisi juga kumpulan method dari T
).
Aturan yang sama juga berlaku untuk struct yang berisi field-field yang
ditanam (embed), seperti yang dijelaskan dalam bagian
tipe struct.
Tipe apa pun memiliki kumpulan method kosong.
Di dalam sebuah kumpulan method, setiap method harus memiliki
nama method
yang unik dan tidak kosong.
Kumpulan method dari sebuah tipe menentukan interface yang diimplementasi oleh tipe tersebut dan method tersebut bisa dipanggil menggunakan penerima dari tipe tersebut.
Tipe boolean
Sebuah tipe boolean merepresentasikan kumpulan nilai Boolean yang
dilambangkan oleh konstan true
dan false
.
Tipe boolean dikenal sebagai bool
;
ia adalah
tipe terdefinisi.
Tipe numerik
Tipe numerik merepresentasikan kumpulan nilai integer atau floating-point. Deklarasi dari tipe numerik, yang bergantung pada arsitektur, yaitu:
uint8 kumpulan semua unsigned 8-bit integer (0 sampai 255) uint16 kumpulan semua unsigned 16-bit integer (0 sampai 65535) uint32 kumpulan semua unsigned 32-bit integer (0 sampai 4294967295) uint64 kumpulan semua unsigned 64-bit integer (0 sampai 18446744073709551615) int8 kumpulan semua signed 8-bit integers (-128 sampai 127) int16 kumpulan semua signed 16-bit integers (-32768 sampai 32767) int32 kumpulan semua signed 32-bit integers (-2147483648 sampai 2147483647) int64 kumpulan semua signed 64-bit integers (-9223372036854775808 sampai 9223372036854775807) float32 kumpulan semua IEEE-754 32-bit bilangan floating-point float64 kumpulan semua IEEE-754 64-bit bilangan floating-point complex64 kumpulan semua bilangan complex dengan float32 pada bagian real dan imajiner complex128 kumpulan semua bilangan complex dengan float64 pada bagian real dan imajiner byte alias untuk uint8 rune alias untuk int32
Nilai dari n-bit integer yaitu n bit lebarnya dan direpresentasikan menggunakan aritmetika komplemen ganda.
Ada juga kumpulan tipe numerik yang ukurannya tergantung pada arsitektur:
uint bisa 32 atau 64 bit int berukuran sama dengan uint uintptr unsigned integer cukup besar menyimpan bit dari nilai pointer
Untuk menghindari masalah portabilitas semua tipe numerik adalah
tipe terdefinisi
dan makanya berbeda kecuali byte
, yang merupakan
alias
dari uint8
, dan rune
, yang merupakan alias untuk uint32
.
Konversi eksplisit dibutuhkan saat tipe numerik berbeda bertemu pada sebuah
ekspresi atau pernyataan.
Misalnya, int32
dan int
bukanlah tipe yang sama walaupun keduanya
berukuran sama pada arsitektur tertentu.
Tipe string
Tipe string merepresentasikan kumpulan nilai string.
Nilai sebuah string yaitu (bisa kosong) urutan dari byte.
Jumlah byte disebut juga panjang dari string dan tidak pernah negatif.
String adalah immutable (tetap): sekali dibuat, maka tidak akan bisa lagi
diubah isinya.
Tipe string yaitu string
; ia adalah
tipe terdefinisi.
Panjang dari string s
dapat diketahui lewat fungsi bawaan
len.
Panjangnya akan konstan bila string tersebut adalah konstan.
Isi byte dari string dapat diakses dengan
indeks
0 sampai len(s)-1
.
Mengambil alamat byte pada string adalah ilegal;
jika s[i]
adalah byte ke i
dari string, maka &s[i]
tidak valid.
Tipe array
Sebuah array yaitu urutan elemen dari sebuah tipe yang jumlahnya tetap. Jumlah dari elemen disebut juga dengan panjang array dan tidak pernah negatif.
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type .
Panjang array adalah bagian dari tipe array;
Ia harus berupa
konstanta
non-negatif yang
direpresentasikan
oleh nilai bertipe int
.
Panjang dari array a
dapat diketahui dengan fungsi bawaan
len.
Setiap elemen array dapat diakses lewat
indeks
0 sampai len(a)-1
.
Tipe array selalu satu dimensi namun bisa digabung untuk membentuk tipe
multi-dimensi.
[32]byte [2*N] struct { x, y int32 } [1000]*float64 [3][5]int [2][2][2]float64 // sama dengan [2]([2]([2]float64))
Tipe slice
Sebuah slice adalah penampung dari segmen bersambungan dari sebuah
array dasar dan menyediakan akses ke elemen dari array tersebut.
Sebuah tipe slice menandakan kumpulan dari semua bagian dari array dari tipe
elemennya.
Jumlah elemen disebut dengan panjang slice dan tidak pernah negatif.
Nilai dari slice yang tidak diinisiasi yaitu nil
.
SliceType = "[" "]" ElementType .
Panjang dari slice diketahui lewat fungsi bawaan
len;
tidak seperti array ia bisa berubah selama eksekusi.
Elemen slice bisa diakses lewat integer
indeks
0 sampai len(s)-1
.
Indeks slice dari elemen bisa kecil dari indeks dari elemen yang sama di array
dasarnya.
Sebuah slice, setelah diinisiasi, selalu berasosiasi dengan array dasar yang menyimpan elemen-elemennya. Oleh karena itu sebuah slice berbagi tempat penyimpanan dengan array-nya dan dengan slice lain dari array yang sama; sebaliknya, array yang berbeda merepresentasikan tempat penyimpanan yang berbeda.
Array yang mendasari sebuah slice bisa diperlebar melebihi akhir dari slice.
Konsep ini dikenal dengan kapasitas: ia adalah jumlah panjang dari slice dan
panjang dari array-dasar slice;
Sebuah slice yang panjangnya lebih dari kapasitas bisa dibuat dengan
slicing (memotong)
yang baru dari slice asli.
Kapasitas dari slice a
dapat diketahui menggunakan fungsi bawaan
`cap(a)`.
Slice baru bertipe T
dapat diinisiasi menggunakan fungsi bawaan
make,
yang menerima tipe slice dan parameter yang menentukan panjang, dan opsi
kapasitas.
Sebuah slice yang dibuat dengan make
selalu mengalokasikan array yang baru
yang diacu oleh slice yang dikembalikan.
Sehingga, mengeksekusi
make([]T, length, capacity)
menghasilkan slice yang sama seperti membuat array dan memotongnya, sehingga dua ekspresi berikut adalah sama:
make([]int, 50, 100) new([100]int)[0:50]
Seperti halnya array, slice selalu satu-dimensi namun bisa bergabung membentuk objek dengan dimensi lebih tinggi. Lewat array dari array, array di dalamnya, selalu memiliki panjang yang sama; namun dengan slice dari slice (atau array dari slice), panjang di dalamnya bisa beragam secara dinamis. Lebih lanjut, slice paling dalam harus diinisiasi secara sendiri-sendiri.
Tipe struct
Sebuah struct yaitu urutan dari elemen bernama, disebut field, tiap-tiap field memiliki nama dan tipe. Nama field bisa secara eksplisit (IdentifierList) atau secara implisit (EmbeddedField). Dalam sebuah struct, nama field yang tidak kosong haruslah unik.
StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName . Tag = string_lit .
// Sebuah struct kosong. struct {} // Sebuah struct dengan 6 field. struct { x, y int u float32 _ float32 // padding A *[]int F func() }
Field dengan tipe namun tanpa nama disebut dengan field tertanam.
Field tertanam haruslah tipe bernama T
atau sebagai pointer ke tipe bernama
yang bukan interface *T
, dan T
itu sendiri bisa saja bukan tipe pointer.
Nama tipe pada field tertanam berlaku sebagai nama field.
// Sebuah struct dengan empat field tertanam bertipe T1, *T2, P.T3 dan *P.T4 struct { T1 // nama fieldnya yaitu T1 *T2 // nama fieldnya yaitu T2 P.T3 // nama fieldnya yaitu T3 *P.T4 // nama field nya yaitu T4 x, y int // nama field nya yaitu x dan y }
Deklarasi berikut ilegal karena nama field harus unik dalam sebuah tipe struct:
struct { T // konflik dengan field tertanam *T dan *P.T *T // konflik dengan field tertanam T dan *P.T *P.T // konflik dengan field tertanam T dan *T }
Sebuah field atau
method
f
dari field tertanam dalam sebuah struct x
disebut dipromosikan jika
x.f
adalah
selektor
yang legal yang menandakan field atau method f
tersebut.
Field yang dipromosikan berlaku seperti field biasa kecuali ia tidak bisa digunakan sebagai nama field di dalam literal komposit dari struct.
Diberikan sebuah tipe struct S
dan
tipe terdefinisi T
,
method-method yang dipromosikan yang masuk ke dalam kumpulan method dari
struct S
yaitu:
-
Jika
S
berisi field tertanamT
, kumpulan method dariS
dan*S
mengikutkan method-method promosi dariT
. Kumpulan method dari*S
juga mengikutkan method promosi dari*T
. -
Jika
S
berisi field tertanamT
, kumpulan method dariS
dan*S
keduanya mengikutkan method promosi dariT
atau*T
.
Deklarasi field bisa ditambah dengan literal string tag, yang menjadi atribut dari field. Tag kosong berarti tidak ada tag. Tag bisa diakses lewat interface refleksi dan ambil bagian dalam identitas tipe dari struct.
struct { x, y float64 "" // tag kosong sama dengan tanpa tag. name string "string apa pun dibolehkan sebagai tag" _ [4]byte "ceci n'est pas un champ de structure" } // Sebuah struct untuk TimeStamp pada protocol buffer. // String tag mendefinisikan field angka untuk protocol buffer; // hal ini mengikuti konvensi yang dijelaskan oleh paket reflect. struct { microsec uint64 `protobuf:"1"` serverIP6 uint64 `protobuf:"2"` }
Tipe pointer
Tipe pointer menandakan kumpulan semua pointer terhadap
variabel
dari tipe yang diberikan, disebut juga dengan tipe dasar dari pointer.
Nilai dari pointer yang tidak diinisiasi adalah nil
.
PointerType = "*" BaseType . BaseType = Type .
*Point *[4]int
Tipe fungsi
Tipe fungsi menandakan kumpulan semua fungsi dengan tipe parameter dan
kembalian yang sama.
Nilai dari variabel bertipe fungsi yang tidak diinisiasi yaitu nil
.
FunctionType = "func" Signature . Signature = Parameters [ Result ] . Result = Parameters | Type . Parameters = "(" [ ParameterList [ "," ] ] ")" . ParameterList = ParameterDecl { "," ParameterDecl } . ParameterDecl = [ IdentifierList ] [ "..." ] Type .
Dalam parameter atau kembalian, nama (IdentifierList) haruslah ada atau tidak ada sama sekali. Jika ada, setiap nama mengacu untuk satu item (parameter atau kembalian) dari tipe dan semua nama haruslah unik. Jika tanpa nama, setiap tipe merepresentasikan satu item untuk tipe tersebut. Daftar parameter dan kembalian selalu dalam tanda kurung kecuali pada kembalian tunggal tanpa nama, ia bisa ditulis tanpa tanda kurung.
Parameter terakhir dalam penanda fungsi bisa memiliki tipe yang dimulai dengan "…" . Fungsi dengan parameter tersebut disebut variadic dan bisa dipanggil tanpa atau lebih argumen dengan tipe yang sama.
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)
Tipe interface
Tipe interface menspesifikasikan
sekumpulan method
yang disebut dengan interface.
Sebuah variabel bertipe interface dapat menyimpan tipe apa pun asalkan
memiliki kumpulan method yang dimiliki oleh interface tersebut.
Tipe tersebut dikatakan mengimplementasikan interface.
Nilai dari variabel interface yang tidak diinisiasi adalah nil
.
InterfaceType = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" . MethodSpec = MethodName Signature . MethodName = identifier . InterfaceTypeName = TypeName .
Sebuah tipe interface bisa menspesifikasikan beberapa method secara eksplisit lewat spesifikasi method, atau ia bisa menanam method-method dari interface yang lain lewat nama tipe interface.
// Sebuah interface berkas sederhana. interface { Read(b Buffer) bool Write(b Buffer) bool Close() }
interface { String() string String() string // ilegal: String tidak unik _(x int) // ilegal: nama method tidak boleh kosong }
Satu atau lebih tipe yang berbeda dapat mengimplementasikan sebuah interface.
Misalnya, jika dua tipe S1
dan S2
memiliki sekumpulan method
func (p T) Read(b Buffer) bool func (p T) Write(b Buffer) bool func (p T) Close()
(yang mana T
bisa S1
atau S2
) maka interface File
dikatakan
diimplementasikan oleh S1
dan S2
, walaupun S1
dan S2
bisa saja punya
method yang lain.
Sebuah tipe bisa mengimplementasikan satu atau lebih interface yang berbeda. Misalnya, semua tipe mengimplementasikan interface kosong:
interface{}
Hal yang sama, pada spesifikasi interface berikut, yang muncul dalam
deklarasi tipe
yang mendefinisikan sebuah interface bernama Locker
:
type Locker interface { Lock() Unlock() }
Jika S1
dan S2
juga mengimplementasikan
func (p T) Lock() { … } func (p T) Unlock() { … }
maka mereka juga mengimplementasikan interface Locker
dan juga interface
File
.
Sebuah interface T
bisa menanam tipe interface bernama E
.
Hal ini disebut penanaman interface E
dalam T
.
Kumpulan method dari T
adalah gabungan dari kumpulan
method T
yang secara eksplisit dan interface-interface yang tertanam pada
T
.
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 }
Gabungan dari kumpulan method berisi method-method (yang diekspor atau tidak diekspor) yang mana setiap method di set sekali saja, dan method-method dengan nama yang sama haruslah memiliki penanda yang identik.
type ReadCloser interface { Reader // mengikutkan method-method Reader dalam ReadCloser Close() // ilegal: penanda dari Reader.Close dan Close berbeda }
Interface bertipe T
tidak bisa secara rekursif menanam dirinya sendiri atau
interface lain yang menanam T
.
// ilegal: Bad tidak bisa menanam dirinya sendiri type Bad interface { Bad } // ilegal: Bad1 tidak bisa menanam dirinya sendiri menggunakan Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 }
Tipe map
Sebuah map adalah gabungan tak urut dari elemen dari satu tipe, yang
disebut tipe elemen, yang memiliki indeks berupa sekumpulan key unik dari
tipe lainnya, disebut tipe key.
Nilai dari map yang tidak diinisiasi yaitu nil
.
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
Operator pembanding ==
dan !=
haruslah terdefinisi untuk operan dari tipe key;
oleh karena itu tipe key tidak bisa berupa fungsi, map, atau slice.
Jika tipe key adalah sebuah tipe interface, maka operator pembanding harus
terdefinisi untuk nilai key dinamis;
jika tidak akan menyebabkan
run-time panic.
map[string]int map[*T]struct{ x, y float64 } map[string]interface{}
Jumlah elemen map disebut panjang.
Untuk map m
, jumlahnya bisa diambil dengan fungsi bawaan
len
dan ia bisa berubah selama eksekusi.
Elemen bisa ditambah selama eksekusi menggunakan
penempatan
dan diambil dengan
ekspresi indeks;
dan elemen bisa dihapus dengan fungsi bawaan
delete.
Nilai map yang baru dan kosong dibuat dengan fungsi bawaan make, yang menerima tipe map dan kapasitas opsional sebagai argumen:
make(map[string]int) make(map[string]int, 100)
Kapasitas awal tidak ada batas ukurannya: map berkembang untuk mengakomodasi
sejumlah item yang disimpannya, kecuali pada map yang nil
.
Map yang nil
sama dengan map kosong kecuali ia tidak bisa ditambahkan dengan
elemen.
Tipe channel
Sebuah channel menyediakan sebuah mekanisme untuk
mengeksekusi fungsi secara konkuren
untuk berkomunikasi dengan
mengirim
dan
menerima
nilai dari tipe elemen yang ditentukan.
Nilai dari channel yang tidak diinisiasi yaitu nil
.
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
Operator opsional ←
menspesifikasikan arah channel, kirim atau
terima.
Jika tidak ada arah yang diberikan, maka channel adalah bidirectional (dua
arah).
Sebuah channel bisa dibatasi hanya untuk mengirim atau menerima saja dengan
penempatan
atau
konversi
eksplisit.
chan T // bisa digunakan untuk mengirim dan menerima nilai bertipe T chan<- float64 // hanya bisa digunakan mengirim float64 <-chan int // hanya bisa menerima int
Operator ←
berhubungan dengan chan
yang paling kiri:
chan<- chan int // sama dengan chan<- (chan int) chan<- <-chan int // sama dengan chan<- (<-chan int) <-chan <-chan int // sama dengan <-chan (<-chan int) chan (<-chan int)
Inisiasi nilai channel yang baru bisa dibuat dengan fungsi bawaan make yang menerima tipe channel dan kapasitas opsional sebagai argumen:
make(chan int, 100)
Kapasitas, atau jumlah elemen, mengatur ukuran buffer dalam channel.
Jika kapasitasnya kosong atau tidak ada, channel adalah tanpa buffer dan
komunikasi akan sukses saat pengirim dan penerima telah siap.
Sebaliknya, bila kapasitas tidak kosong, maka channel adalah ber-buffer
dan komunikasi akan sukses tanpa ditahan jika buffer tidak penuh (mengirim)
atau tidak kosong (menerima).
Channel yang nil
tidak akan pernah bisa berkomunikasi.
Channel bisa ditutup dengan fungsi bawaan close. Bentuk penempatan banyak-nilai dari operator penerima melaporkan apakah nilai yang diterima dikirim sebelum channel ditutup.
Sebuah channel bisa digunakan untuk mengirim, menerima, dan memanggil fungsi bawaan cap dan len oleh sejumlah goroutine berbeda tanpa perlu sinkronisasi. Channel bersifat antrean first-in-first-out (yang pertama masuk, yang pertama keluar). Misalnya, jika sebuah goroutine mengirim nilai ke channel dan goroutine kedua menerimanya, nilai diterima sesuai urutan yang dikirim.
Properti dari tipe dan nilai
Identitas tipe
Dua buah tipe akan identik atau berbeda.
Tipe terdefinisi selalu berbeda dengan tipe lainnya. Sebaliknya, dua tipe adalah identik jika tipe dasar mereka secara struktural sama; yaitu, memiliki struktur literal yang sama dan komponen yang berhubungan memiliki tipe yang sama. Secara lebih rinci:
-
Dua tipe array adalah identik jika mereka punya tipe elemen dan panjang yang sama.
-
Dua tipe slice adalah identik jika mereka punya tipe elemen yang sama.
-
Dua tipe struct adalah identik jika mereka memiliki urutan field yang sama, dan jika field-field tersebut memiliki nama. tipe, dan tag yang sama. Nama field yang tidak diekspor dari paket yang berbeda selalu menghasilkan tipe struct yang tidak identik.
-
Dua tipe pointer adalah identik jika mereka memiliki tipe dasar yang sama.
-
Dua tipe fungsi adalah identik jika mereka memiliki jumlah parameter dan kembalian yang sama, dengan tipe parameter dan kembalian yang sama, dan bila kedua fungsi adalah variadic atau tidak sama sekali. Nama pada parameter dan kembalian tidak harus sama.
-
Dua tipe interface adalah identik jika mereka memiliki kumpulan method dengan nama yang sama dan tipe fungsi yang sama. Nama method yang tidak diekspor dari paket yang berbeda akan selalu menghasilkan tipe yang tidak identik. Urutan dari method tidak berpengaruh.
-
Dua tipe map adalah identik jika mereka memiliki tipe key dan elemen yang sama.
-
Dua tipe channel adalah identik jika mereka memiliki tipe elemen dan arah yang sama.
Diberikan deklarasi berikut,
type ( A0 = []string A1 = A0 A2 = struct{ a, b int } A3 = int A4 = func(A3, float64) *A0 A5 = func(x int, _ float64) *[]string ) type ( 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 ) type C0 = B0
tipe-tipe berikut adalah identik:
A0, A1, dan []string A2 dan struct{ a, b int } A3 dan int A4, func(int, float64) *[]string, dan A5 B0 dan C0 []int dan []int struct{ a, b *T5 } dan struct{ a, b *T5 } func(x int, y float64) *[]string, func(int, float64) (result *[]string), dan A5
B0
dan B1
berbeda karena mereka adalah tipe baru yang dibuat dengan
definisi tipe
yang berbeda;
func(int, float64) *B0
dan func(x int, y float64) *[]string
adalah berbeda karena B0
berbeda dari []string
.
Assignability (Penempatan)
Sebuah nilai x
bisa ditempatkan ke sebuah
variabel
bertipe T
("x bisa diisi ke T") jika salah satu kondisi berikut berlaku:
-
tipe x identik dengan
T
. -
tipe x yaitu
V
,V
danT
memiliki tipe dasar yang sama dan paling tidak salah satu dariV
atauT
bukanlah tipe tipe terdefinisi. -
T
adalah tipe interface danx
mengimplementasikanT
-
x
adalah nilai channel dua arah,T
bertipe channel, bila tipex
dari yaituV
danT
memiliki tipe elemen yang sama, dan paling tidak salah satu dariV
atauT
bukanlah tipe terdefinisi. -
x
adalahnil
danT
bertipe pointer, fungsi, slice, map, channel, atau interface. -
x
adalah sebuah konstan direpresentasikan dengan nilai bertipeT
.
Representability
Sebuah
konstan
x
bisa direpresentasikan oleh sebuah nilai bertipe T
jika salah satu
kondisi berikut berlaku:
-
x
ada dalam kumpulan nilai yang ditentukan olehT
. -
T
bertipe floating-point danx
bisa dibulatkan ke presisiT
tanpa overflow. Pembulatan menggunakan aturan pembulatan-genap dari IEEE 754 namun dengan IEEE nol negatif disederhanakan menjadi unsigned nol. Ingatlah bahwa nilai konstan tidak pernah menghasilkan IEEE nol negatif, NaN, atau tanpa batas. -
T
bertipe complex, dan komponen xreal(x)
danimag(x)
bisa direpresentasikan oleh nilai tipe komponen dariT
(float32
ataufloat64
).
x T x bisa direpresentasikan oleh nilai T karena 'a' byte 97 ada dalam kumpulan nilai byte 97 rune rune adalah alias untuk int32, dan 97 ada dalam kumpulan integer 32-bit "foo" string "foo" ada dalam kumpulan nilai string 1024 int16 1024 ada dalam kumpulan integer 16-bit 42.0 byte 42 ada dalam kumpulan unsigned integer 8-bit 1e10 uint64 10000000000 ada dalam kumpulan unsigned integer 64-bit 2.718281828459045 float32 2.718281828459045 dibulatkan ke 2.7182817 yang ada dalam kumpulan nilai float32 -1e-1000 float64 -1e-1000 dibulatkan ke IEEE -0.0 yang kemudian disederhanakan menjadi 0.0 0i int 0 adalah nilai integer (42 + 0i) float32 42.0 (dengan bagian imajiner nol) ada dalam kumpulan nilai float32
x T x tidak direpresentasikan oleh nilai T karena 0 bool 0 tidak ada dalam kumpulan nilai boolean 'a' string 'a' adalah rune, ia tidak ada dalam kumpulan nilai string 1024 byte 1024 bukan berada dalam kumpulan unsigned integer 8-bit -1 uint16 -1 bukan berada dalam kumpulan unsigned integer 16-bit 1.1 int 1.1 bukanlah nilai integer 42i float32 (0 + 42i) bukan berada dalam kumpulan nilai float32 1e1000 float64 1e1000 menjadi overflow ke IEEE +Inf setelah pembulatan
Blok
Sebuah blok yaitu seurutan deklarasi dan perintah, yang bisa saja kosong, di antara tanda kurung kurawal.
Block = "{" StatementList "}" . StatementList = { Statement ";" } .
Selain blok eksplisit dalam kode, ada beberapa blok implisit:
-
blok universal melingkupi semua teks kode Go
-
Setiap paket memiliki sebuah blok paket yang berisi semua teks sumber kode Go untuk paket tersebut
-
Setiap berkas memiliki sebuah blok berkas berisi teks sumber kode Go dalam berkas tersebut
-
Setiap perintah "if", "for", dan "switch" dianggap berada dalam blok implisit-nya sendiri.
-
Setiap "case" di dalam perintah "switch" atau "select" bersifat sebagai blok implisit.
Sekumpulan blok yang bersarang mempengaruhi skop.
Deklarasi dan skop
Sebuah deklarasi mengikat pengidentifikasi yang tidak kosong terhadap sebuah konstan, tipe, variabel, fungsi, label, atau paket. Setiap identifikasi dalam sebuah program haruslah dideklarasikan. Tidak ada pengidentifikasi yang bisa dideklarasikan dua kali dalam blok yang sama, dan tidak ada pengidentifikasi bisa dideklarasikan dalam blok berkas dan paket.
Pengidentifikasi kosong
bisa digunakan seperti pengidentifikasi lainnya dalam sebuah deklarasi, namun
tidak mengakibatkan pengikatan sehingga tidak dideklarasi.
Dalam blok paket, pengidentifikasi init
hanya bisa digunakan untuk deklarasi
fungsi `init`,
dan seperti pengidentifikasi kosong ia tidak menghasilkan pengikatan yang
baru.
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
Ruang lingkup dari sebuah deklarasi pengidentifikasi yaitu betas dari teks sumber kode di mana pengidentifikasi menyatakan konstan, tipe, variabel, fungsi, label, atau paket yang ditentukan.
Go secara leksikal dibatasi menggunakan blok-blok:
1. Skop dari pengidentifikasi pra-deklarasi yaitu blok universal. 2. Skop dari pengidentifikasi yang menyatakan sebuah konstan, tipe, variabel, atau fungsi (tetapi tidak method) yang dideklarasikan pada bagian atas (di luar fungsi apa pun) adalah blok paket. 3. Skop dari nama paket yang diimpor yaitu blok berkas yang berisi deklarasi impor. 4. Skop dari pengidentifikasi yang menyatakan penerima method, parameter fungsi, atau variabel kembalian yaitu badan dari fungsi. 5. Skop dari konstan atau variabel yang dideklarasikan dalam fungsi dimulai dari ConstSpec atau VarSpec (ShortVarDecl untuk deklarasi variabel singkat) dan berakhir pada blok yang mengandungnya. 6. Skop dari pengidentifikasi tipe yang dideklarasikan dalam sebuah fungsi dimulai dari pengidentifikasi dalam TypeSpec dan berakhir pada blok yang mengandungnya.
Pengidentifikasi yang dideklarasikan dalam sebuah blok bisa dideklarasikan kembali di dalam blok sebelah dalam. Saat pengidentifikasi dari deklarasi di dalam skop, ia menyatakan entitas yang dideklarasikan oleh deklarasi di dalamnya.
Klausa paket bukanlah sebuah deklarasi; nama paket tidak muncul dalam skop manapun. Tujuan klausa paket yaitu untuk mengidentifikasi berkas berada dalam paket yang sama dan untuk menentukan nama paket untuk deklarasi impor.
Skop label
Label dideklarasikan oleh perintah label dan digunakan dalam perintah "break", "continue", dan "goto". Adalah ilegal mendefinisikan sebuah label yang tidak pernah digunakan. Berbeda dengan pengidentifikasi lainnya, label tidaklah dibatasi oleh skip dan tidak konflik dengan pengidentifikasi yang bukan label. Skop dari label yaitu badan dari fungsi di mana ia dideklarasikan dan tidak mengikutkan badan dari fungsi yang bersarang.
Pengidentifikasi kosong
Pengidentifikasi kosong direpresentasikan oleh karakter garis bawah _
.
Ia berfungsi sebagai penampung anonim bukan sebagai pengidentifikasi biasa
(yang bukan kosong) dan memiliki arti khusus dalam
deklarasi,
seperti sebuah
operan,
dan dalam
penempatan.
Pengidentifikasi pradeklarasi
Pengidentifikasi berikut secara implisit dideklarasikan dalam blok universal:
Tipe: bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr Konstan: true false iota Nilai kosong: nil Fungsi: append cap close complex copy delete imag len make new panic print println real recover
Pengidentifikasi yang diekspor
Sebuah pengidentifikasi bisa diekspor untuk membolehkan akses kepadanya dari paket lainnya. Pengidentifikasi diekspor jika:
1. Karakter pertama dari nama pengidentifikasi adalah huruf besar Unicode (kelas Unicode "Lu"); dan 2. Pengidentifikasi dideklarasikan dalam blok paket atau ia merupakan nama field atau nama method.
Pengidentifikasi lainnya tidak diekspor.
Keunikan pengidentifikasi
Diberikan sekumpulan pengidentifikasi, sebuah pengidentifikasi dikatakan unik jika ia berbeda dari yang lainnya dalam kumpulan tersebut. Dua pengidentifikasi adalah berbeda jika mereka dieja secara berbeda, atau jika mereka muncul di paket yang berbeda dan tidak diekspor. Selain itu, mereka adalah pengidentifikasi yang sama.
Deklarasi konstan
Deklarasi konstan mengikat sejumlah pengidentifikasi (nama-nama dari konstan) terhadap nilai dari daftar dari ekspresi konstan. Jumlah pengidentifikasi harus sama dengan jumlah ekspresi, dan pengidentifikasi ke-n di bagian kiri terikat ke nilai dari ekspresi ke-n di bagian kanan.
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = identifier { "," identifier } . ExpressionList = Expression { "," Expression } .
Jika tipe didefinisikan, semua konstan memakai tipe tersebut, dan ekspresi nilai haruslah dapat di-assign ke tipe tersebut. Jika tipe diindahkan, maka konstan memiliki tipe berdasarkan ekspresi. Jika nilai ekspresi adalah konstan tanpa tipe, maka konstan tetap tanpa tipe dan pengidentifikasi konstan menyatakan nilai konstan. Misalnya, jika ekspresi adalah literal floating-point, pengidentifikasi konstan menyatakan sebuah konstan floating-point, bahkan bila bagian pecahan adalah nol.
const Pi float64 = 3.14159265358979323846 const zero = 0.0 // konstan floating-point tanpa tipe const ( size int64 = 1024 eof = -1 // konstan integer tanpa tipe ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", konstan integer dan string tanpa tipe const u, v float32 = 0, 3 // u = 0.0, v = 3.0
Dalam deklarasi const
dengan tanda kurung, daftar ekspresi bisa diindahkan
kecuali ConstSpec yang pertama.
Daftar kosong seperti ini sama saja dengan penggantian tekstual dari daftar
ekspresi pertama yang tidak kosong dan tipenya jika ada.
Mengindahkan daftar ekspresi maka sama saja dengan mengulang daftar
sebelumnya.
Jumlah pengidentifikasi harus sama dengan jumlah ekspresi pada daftar
sebelumnya.
Bersama dengan
`iota` konstan generator
mekanisme ini membolehkan deklarasi ringan dari nilai berurutan:
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // this constant is not exported )
Iota
Di dalam sebuah
deklarasi konstan,
pengidentifikasi iota
merepresentasikan
konstan
integer tanpa-tipe beriringan.
Nilainya yaitu indeks dari
ConstSpec
dalam deklarasi konstan tersebut, dimulai dari nol.
Ia bisa digunakan untuk membentuk sekumpulan konstan yang berhubungan:
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, tidak terpakai) d = 1 << iota // d == 8 (iota == 3) ) const ( u = iota * 42 // u == 0 (konstan integer tanpa tipe) v float64 = iota * 42 // v == 42.0 (konstan float64) w = iota * 42 // w == 84 (konstan integer tanpa tipe) ) const x = iota // x == 0 const y = iota // y == 0
Secara definisi, penggunaan iota
berulang kali di dalam ConstSpec yang sama
memiliki nilai yang sama:
const ( bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0) bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1) _, _ // (iota == 2, tak terpakai) bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3) )
Contoh terakhir menggunakan pengulangan implisit dari daftar ekspresi yang tidak kosong.
Deklarasi tipe
Sebuah deklarasi tipe mengikat pengidentifikasi, name tipe, ke sebuah tipe Deklarasi tipe ada dua bentuk: deklarasi alias dan definisi tipe.
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . TypeSpec = AliasDecl | TypeDef .
Deklarasi alias
Deklarasi alias mengikat pengidentifikasi ke tipe yang diberikan.
AliasDecl = identifier "=" Type .
Dalam skop pengidentifikasi, ia berfungsi sebagai alias dari tipe.
type ( nodeList = []*Node // nodeList dan []*Node adalah tipe yang identik Polar = polar // Polar dan polar menyatakan tipe yang identik )
Definisi tipe
Sebuah definisi tipe membuat tipe yang baru dan berbeda dengan tipe dasar dan operasi yang sama dengan tipe yang diberikan, dan mengikat pengidentifikasi padanya.
TypeDef = identifier Type .
Tipe yang baru disebut tipe terdefinisi. Ia berbeda dengan tipe lainnya, termasuk dari tipe yang membentuknya.
type ( Point struct{ x, y float64 } // Point dan struct{ x, y float64 } // adalah tipe yang berbeda. polar Point // polar dan Point menyatakan tipe yang // berbeda. ) type TreeNode struct { left, right *TreeNode value *Comparable } type Block interface { BlockSize() int Encrypt(src, dst []byte) Decrypt(src, dst []byte) }
Tipe terdefinisi bisa memiliki method yang berasosiasi dengannya. Ia tidak mewariskan method apa pun dari tipe yang diikat, namun kumpulan method dari tipe interface atau elemen dari tipe komposit tidak berubah:
// Mutex adalah tipe data dengan dua method, Lock dan Unlock. type Mutex struct { /* field dari Mutex */ } func (m *Mutex) Lock() { /* implementasi Lock */ } func (m *Mutex) Unlock() { /* implementasi Unlock */ } // NewMutex memiliki komposisi yang sama dengan Mutex namun set method-nya // kosong. type NewMutex Mutex // Kumpulan method dari tipe dasar PtrMutex yaitu *Mutex tetap tidak berubah, // namun kumpulan method dari PtrMutex adalah kosong. type PtrMutex *Mutex // Kumpulan method dari *PrintableMutex berisi method Lock dan Unlock terikat // dari field tertanamnya Mutex. type PrintableMutex struct { Mutex } // MyBlock yaitu tipe interface yang memiliki kumpulan method yang sama dengan // Block. type MyBlock Block
Definisi tipe bisa digunakan untuk mendefinisikan tipe boolean, numerik, atau string yang berbeda dan mengasosiasikan method dengan tipe tersebut:
type TimeZone int const ( EST TimeZone = -(5 + iota) CST MST PST ) func (tz TimeZone) String() string { return fmt.Sprintf("GMT%+dh", tz) }
Deklarasi variabel
Deklarasi variabel membuat satu atau lebih variabel, mengikat pengidentifikasi yang berkorespondensi kepadanya, dan memberikan setiap tiap-tiapnya sebuah tipe dan nilai awal.
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] // pencarian map; hanya tertarik pada "found"
Jika sebuah daftar ekspresi diberikan, maka variabel diinisiasi dengan ekspresi mengikuti aturan-aturan penempatan. Selain itu, setiap variabel diinisiasi dengan nilai kosong nya.
Jika sebuah tipe diberikan, setiap variabel diberikan tipe tersebut.
Selain itu, setiap variabel diberikan tipe dari nilai inisiasi pada
penempatan.
Jika nilai tersebut sebuah konstan tak bertipe, maka pertama kali ia secara
implisit
dikonversi
ke
tipe defaultnya;
misalnya, jika variabel adalah nilai boolean tanpa tipe, maka ia secara
implisit dikonversi ke tipe bool
.
Nilai nil
tidak bisa digunakan untuk menginisiasi sebuah variabel tanpa tipe
eksplisit.
var d = math.Sin(0.5) // d adalah float64 var i = 42 // i adalah int var t, ok = x.(T) // t adalah T, ok adalah bool var n = nil // ilegal
Batasan implementasi: Compiler bisa mengilegalkan deklarasi variabel di dalam badan fungsi jika variabel tersebut tidak pernah digunakan.
Deklarasi variabel singkat
Deklarasi variabel singkat menggunakan sintaks:
ShortVarDecl = IdentifierList ":=" ExpressionList .
Ia merupakan cara cepat mendeklarasikan variabel dengan ekspresi inisiasi tanpa tipe:
"var" IdentifierList = ExpressionList .
i, j := 0, 10 f := func() int { return 7 } ch := make(chan int) r, w, _ := os.Pipe() // os.Pipe() mengembalikan pasangan File dan error _, y, _ := coord(p) // coord() mengembalikan tiga nilai; yang diambil hanya koordinat y
Tidak seperti deklarasi variabel, deklarasi variabel singkat bisa mendeklarasi ulang variabel setelah ia dideklarasikan sebelumnya di dalam blok yang sama (atau dari daftar parameter jika blok adalah badan fungsi) dengan tipe yang sama, dan paling tidak salah satu dari variabel yang tidak kosong adalah variabel baru. Akibatnya, deklarasi ulang hanya dapat muncul dalam sebuah deklarasi singkat multi-variabel. Deklarasi ulang tidak menyebabkan munculnya variabel baru; ia hanya menempatkan nilai baru ke variabel aslinya.
field1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // deklarasi ulang pada offset a, a := 1, 2 // ilegal: deklarasi ganda dari a // atau tidak ada variabel baru bila // a dideklarasikan sebelumnya.
Deklarasi fungsi
Deklarasi fungsi mengikat pengidentifikasi, nama fungsi, ke sebuah fungsi.
FunctionDecl = "func" FunctionName Signature [ FunctionBody ] . FunctionName = identifier . FunctionBody = Block .
Jika fungsi mengembalikan nilai, daftar perintah pada badan fungsi harus berakhir dengan perintah terminasi.
func IndexRune(s string, r rune) int { for i, c := range s { if c == r { return i } } // tidak valid: perintah return tidak ada. }
Deklarasi fungsi bisa tanpa badan. Deklarasi seperti ini menyediakan signature untuk sebuah fungsi yang diimplementasikan di luar Go, seperti rutin assembly.
func min(x int, y int) int { if x < y { return x } return y } func flushICache(begin, end uintptr) // diimplementasikan di luar.
Deklarasi method
Sebuah method yaitu sebuah fungsi dengan sebuah receiver (penerima). Deklarasi method mengikat pengidentifikasi, nama method, terhadap sebuah method, dan mengasosiasikan method tersebut dengan tipe dasar receiver.
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
Si receiver dispesifikasikan lewat bagian parameter sebelum nama method.
Bagian parameter tersebut harus mendeklarasikan sebuah parameter tunggal, yang
disebut juga dengan receiver.
Tipe receiver haruslah tipe
terdefinisi
T
atau sebuah pointer ke tipe terdefinisi T
.
T
disebut juga tipe dasar receiver.
Tipe dasar receiver tidak bisa berupa tipe pointer atau interface dan ia
harus didefinisikan di dalam paket yang sama dengan method.
Method tersebut dikatakan terikat dengan tipe dasar receiver dan nama
method hanya dapat dipanggil oleh
selector
untuk tipe T
atau *T
.
Pengidentifikasi receiver haruslah unik dalam penanda method. Jika nilai receiver tidak dipakai di dalam badan method, maka pengidentifikasinya bisa dihilangkan dalam deklarasi. Hal yang sama berlaku secara umum terhadap parameter dari fungsi dan method.
Untuk tipe dasar yang sama, nama-nama dari method haruslah unik. Jika tipe dasar adalah sebuah tipe struct, nama method dan field haruslah berbeda.
Diberikan sebuah tipe Point
, deklarasi berikut
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 }
mengikat method Length()
dan Scale()
, dengan receiver bertipe *Point
,
terhadap tipe dasar Point
.
Tipe dari sebuah method yaitu tipe dari fungsi dengan receiver sebagai
argumen yang pertama.
Misalnya, method Scale()
bertipe
func(p *Point, factor float64)
Namun, fungsi yang dideklarasikan seperti di atas bukanlah sebuah method.
Ekspresi
Sebuah ekspresi menentukan komputasi dari sebuah nilai dengan menerapkan operator dan fungsi terhadap operan.
#Operands === Operan-operan
Operan menyatakan nilai elementer dalam sebuah ekspresi. Sebuah operan bisa jadi literal, sebuah pengindentifikasi bukan- kosong (bisa saja /ref/spec#Qualified_identifiers[terbatas]) yang menyatakan sebuah konstan, variabel, atau fungsi, atau ekspresi dalam tanda kurung.
Pengidentifikasi kosong bisa muncul sebaga operan hanya pada bagian kiri dari sebuah penempatan.
Operand = Literal | OperandName | "(" Expression ")" . Literal = BasicLit | CompositeLit | FunctionLit . BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . OperandName = identifier | QualifiedIdent.
Pengidentifikasi terbatas
Sebuah pengidentifikasi terbatas yaitu sebuah pengidentifikasi yang dibatasi oleh awalan nama paket. Nama paket dan pengidentifikasi haruslah tidak kosong.
QualifiedIdent = PackageName "." identifier .
Pengidentifikasi terbatas mengakses pengidentifikasi di paket yang berbeda, yang harus diimpor. Si pengidentifikasi haruslah diekspor dan dideklarasikan dalam blok paket dari paket tersebut.
math.Sin // menyatakan fungsi Sin dalam paket math.
Literal komposit
Literal komposit membentuk nilai-nilai untuk struct, array, slice, dan map; dan membuat sebuah nilai baru setiap kali ia dievaluasi. Ia dibentuk dari tipe dari literal diikuti oleh daftar elemen yang dibatasi oleh kurung kurawal. Setiap elemen bisa diawali dengan kunci yang berkorespondensi.
CompositeLit = LiteralType LiteralValue . LiteralType = StructType | ArrayType | "[" "..." "]" ElementType | SliceType | MapType | TypeName . LiteralValue = "{" [ ElementList [ "," ] ] "}" . ElementList = KeyedElement { "," KeyedElement } . KeyedElement = [ Key ":" ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue .
Tipe dasar dari LiteralType haruslah sebuah tipe struct, array, slice, atau map (gramatika memaksa batasan ini kecuali bisa tipe diberikan sebagai sebuah TypeName). Tipe dari elemen dan kunci harus bisa ditempatkan ke tipe field, elemen, dan kunci dari tipe literal yang bersangkutan; tidak ada konversi tambahan. Kunci diinterpretasikan sebagai sebuah nama field untuk literal struct, sebuah indeks pada literal array dan slice, dan sebuah kunci untuk literal map. Untuk literal map, semua elemen harus memiliki sebuah kunci. Adalah sebuah kesalahan bila menspesifikasikan beragam lemen dengan nama field yang sama atau nilai key menggunakan konstan. Untuk kunci map yang tidak-konstan, lihat bagian pada urutan evaluasi.
Untuk literal struct aturan-aturan berikut berlaku:
-
Sebuah kunci haruslah nama field yang dideklarasikan dalam tipe struct.
-
Daftar elemen yang tidak memiliki kunci haruslah mendaftarkan setiap elemen pada field struct dengan urutan sebagaimana ia dideklarasikan.
-
Jika elemen memiliki key, maka semua elemen haruslah memiliki key.
-
Daftar elemen yang mengandung kunci tidak harus memiliki elemen untuk setiap field struct. Field yang diindahkan akan mendapatkan nilai kosong untuk field tersebut.
-
Literal bisa mengindahkan daftar elemen; literal tersebut dievaluasi menjadi nilai kosong untuk tipenya.
-
Adalah sebuah kesalahan bila menspesifikasikan sebuah elemen untuk field yang tidak diekspor dari sebuah struct yang dimiliki oleh paket yang berbeda.
Diberikan deklarasi berikut
type Point3D struct { x, y, z float64 } type Line struct { p, q Point3D }
kita bisa menulis
origin := Point3D{} // nilai kosong untuk Point3D line := Line{origin, Point3D{y: -4, z: 12.3}} // nilai kosong untuk line.q.x
Untuk array dan slice, aturan-aturan berikut berlaku:
-
Setiap elemen memiliki indeks integer yang menandakan posisinya dalam array.
-
Sebuah elemen dengan sebuah kunci menggunakan kunci tersebut sebagai indeksnya. Kunci tersebut haruslah konstan bukan-negatif yang dapat direpresentasikan oleh nilai bertipe
int
; dan jika kunci tersebut bertipe maka harus bertipe integer. -
Sebuah elemen tanpa kunci menggunakan indeks elemen sebelumnya ditambah satu. Jika elemen pertama tanpa kunci, indeksnya adalah nol.
Mengambil alamat dari literal komposit menghasilkan sebuah pointer ke sebuah variabel unik yang diinisiasi dengan nilai literal.
var pointer *Point3D = &Point3D{y: 1000}
Ingatlah bahwa nilai kosong dari sebuah tipe slice atau map tidak sama dengan slice atau yang map diinisiasi dengan nilai kosong dari tipe yang sama. Akibatnya, mengambil alamat dari literal komposit dari sebuah slice atau map yang kosong tidak sama efeknya dengan mengalokasikan slice atau map yang baru dengan new.
p1 := &[]int{} // p1 menunjuk ke slice kosong yang diinisiasi dengan nilai // []int{} dan panjang 0 p2 := new([]int) // p2 menunjuk ke slice yang belum diinisiasi dengan nilai // nil dan panjang 0.
Panjang dari literal array yaitu panjang yang dispesifikasikan pada literal tipe. Jika jumlah elemen yang diberikan kecil dari panjangnya, elemen-elemen yang hilang di set dengan nilai kosong dari tipe elemen array. Adalah sebuah kesalahan mengisi elemen dengan nilai indeks di luar rentang indeks dari array. Notasi … menspesifikasikan panjang array yang sama dengan maksimum elemen ditambah satu.
buffer := [10]string{} // len(buffer) == 10 intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6 days := [...]string{"Sat", "Sun"} // len(days) == 2
Literal slice mendeskripsikan keseluruhan literal array di belakangnya. Maka panjang dan kapasitas dari literal slice yaitu indeks elemen maksimum ditambah satu. Literal slice memiliki bentuk
[]T{x1, x2, … xn}
yang merupakan cara singkat untuk operasi slice yang diterapkan pada sebuah array:
tmp := [n]T{x1, x2, … xn} tmp[0 : n]
Dalam sebuah literal komposit dari array, slice, atau map bertipe T, elemen
atau kunci map yang juga literal komposit bisa mengindahkan literal tipe jika
ia identik dengan tipe elemen atau key dari T.
Hal yang sama, elemen atau kunci yang merupakan alamat dari literal komposit
bisa mengindahkan &T
bila tipe elemen atau kunci adalah *T
.
[...]Point{{1.5, -3.5}, {0, 0}} // sama dengan [...]Point{Point{1.5, -3.5}, Point{0, 0}} [][]int{{1, 2, 3}, {4, 5}} // sama dengan [][]int{[]int{1, 2, 3}, []int{4, 5}} [][]Point{{{0, 1}, {1, 2}}} // sama dengan [][]Point{[]Point{Point{0, 1}, Point{1, 2}}} map[string]Point{"orig": {0, 0}} // sama dengan map[string]Point{"orig": Point{0, 0}} map[Point]string{{0, 0}: "orig"} // sama dengan map[Point]string{Point{0, 0}: "orig"} type PPoint *Point [2]*Point{{1.5, -3.5}, {}} // sama dengan [2]*Point{&Point{1.5, -3.5}, &Point{}} [2]PPoint{{1.5, -3.5}, {}} // sama dengan [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}
Ambiguitas muncul saat literal komposit menggunakan bentuk TypeName dari LiteralType muncul sebagai operan antara kata kunci dan kurung buka dari blok perintah "if", "for", atau "switch", dan literal komposit tidak ditutup dalam tanda kurung, kurung siku, atau kurung kurawal. Dalam kasus langka ini, kurung buka dari literal dengan keliru dianggap sebagai yang membuka perintah blok. Untuk mengatasi kebingungan ini, literal komposit harus muncul dalam tanda kurung.
if x == (T{a,b,c}[i]) { … } if (x == T{a,b,c}[i]) { … }
Contoh literal array, slice, dan map yang valid:
// daftar bilangan prima primes := []int{2, 3, 5, 7, 9, 2147483647} // vowels[ch] adalah true jika ch adalah huruf vokal. vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true} // 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} // frekuensi dalam Hz untuk skala yang sama (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, }
Literal fungsi
Sebuah literal fungsi merepresentasikan sebuah fungsi anonim.
FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }
Sebuah literal fungsi bisa ditempatkan ke sebuah variabel atau langsung dipanggil.
f := func(x, y int) int { return x + y } func(ch chan int) { ch <- ACK }(replyChan)
Literal fungsi adalah closure: fungsi yang bisa mengacu ke variabel-variabel yang didefinisikan sebelum fungsi. Variabel tersebut dapat diakses antara fungsi dan literal fungsi, dan variabel tersebut bertahan selama mereka dapat diakses.
Ekspresi dasar
Ekspresi dasar yaitu operan-operan untuk ekspresi unary dan binari.
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
Untuk sebuah
ekspresi dasar
x
yang bukan sebuah
nama paket,
ekspresi selektor
x.f
menyatakan field atau method f
dari nilai x
(atau terkadang *x
; lihat di
bawah).
Pengidentifikasi f
disebut (field atau method) selector;
ia tidak boleh sebuah
pengidentifikasi kosong.
Tipe dari ekspresi selector yaitu tipe dari f
.
Jika x
adalah sebuah nama paket, lihat bagian
<<Qualified_identifiers[pengidentifikasi
terbatas].
Sebuah selector f
bisa menyatakan sebuah field atau method f
dari sebuah
tipe T
, atau ia bisa mengacu pada sebuah field atau method f
dari
field tertanam
dari T
.
Jumlah field tertanam yang dijajaki sampai ke f
disebut dengan kedalaman
dari T
.
Kedalaman dari sebuah field atau method f
yang dideklarasikan dalam T
adalah nol.
Kedalaman dari sebuah field atau method f
yang dideklarasikan dalam field
A
yang ditanam dalam T
adalah kedalaman f
dalam A
ditambah satu.
Aturan-aturan berikut berlaku pada selector:
-
Untuk nilai
x
bertipeT
atau*T
yang manaT
bukanlah sebuah tipe pointer atau interface,x.f
menyatakan field atau method pada kedalaman paling dangkal dalamT
yang manaf
tersebut ada. Jika tidak ada satu f pada kedalaman terdangkal, ekspresi selector tersebut adalah ilegal. -
Untuk sebuah nilai
x
bertipeI
yang manaI
adalah sebuah tipe interface,x.f
menyatakan method bernamaf
dari nilai dinamisx
. Jika tidak ada method bernamaf
dalam kumpulan method dariI
, ekspresi selector tersebut adalah ilegal. -
Sebagai pengecualian, jika tipe dari
x
merupakan tipe pointer terdefinisi dan(*x).f
adalah ekspresi selector yang valid yang menyatakan sebuah field (bukan sebuah method),x.f
adalah singkatan untuk(*x).f
. -
Pada kasus lainnya
x.f
adalah ilegal. -
Jika
x
adalah tipe pointer dan bernilainil
danx.f
menyatakan sebuah field pada struct, mengisi atau mengambil nilaix.f
mengakibatkan panik runtime. -
Jika
x
bertipe interface dan bernilainil
, memanggil atau mengevaluasi methodx.f
menyebakan panik runtime.
Sebagai contoh, dari deklarasi berikut:
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 // dengan t.T0 != nil var p *T2 // dengan p != nil dan (*p).T0 != nil var q Q = p
kita dapat menulis:
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 adalah selector field yang valid p.M0() // ((*p).T0).M0() M0 mendapatkan receiver *T0 p.M1() // ((*p).T1).M1() M1 mendapatkan receiver T1 p.M2() // p.M2() M2 mendapatkan receiver *T2 t.M2() // (&t).M2() M2 mendapatkan receiver *T2, lihat bagian tentang Pemanggilan
namun pernyataan berikut adalah invalid:
q.M0() // (*q).M0 valid namun bukan sebuah selector field
Ekspresi method
Jika M
ada dalam
kumpulan method
bertipe T
, T.M
adalah sebuah fungsi yang dapat dipanggil seperti fungsi
biasa dengan argumen yang sama dengan M
yang diawali dengan argumen tambahan
yaitu receiver dari method.
MethodExpr = ReceiverType "." MethodName . ReceiverType = Type .
Misalkan sebuah struct bertipe T
dengan dua method, Mv
, dengan receiver
bertipe T
; dan Mp
dengan receiver bertipe *T
.
type T struct { a int } func (tv T) Mv(a int) int { return 0 } // receiver berupa nilai func (tp *T) Mp(f float32) float32 { return 1 } // receiver berupa pointer var t T
Ekspresi
T.Mv
menghasilkan sebuah fungsi yang sama dengan Mv
namun dengan sebuah
receiver eksplisit sebagai argumen pertama; dengan penanda
func(tv T, a int) int
Fungsi tersebut bisa dipanggil secara normal dengan receiver eksplisit, sehingga kelima pemanggilan berikut adalah sama:
t.Mv(7) T.Mv(t, 7) (T).Mv(t, 7) f1 := T.Mv; f1(t, 7) f2 := (T).Mv; f2(t, 7)
Hal yang sama, ekspresi berikut
(*T).Mp
menghasilkan sebuah nilai fungsi yang merepresentasikan Mp
dengan penanda
func(tp *T, f float32) float32
Untuk sebuah method dengan receiver nilai, kita dapat menurunkan sebuah fungsi dengan receiver pointer yang eksplisit, sehingga
(*T).Mv
menghasilkan sebuah nilai fungsi merepresentasikan Mv
dengan penanda
func(tv *T, a int) int
Fungsi seperti ini secara tidak langsung lewat receiver untuk membuat sebuah nilai untuk dikirim sebagai receiver pada method dibaliknya; method tersebut tidak menimpa nilai yang alamatnya dikirim ke pemanggilan fungsi.
Kasus terakhir, fungsi dengan receiver berupa nilai untuk sebuah method dengan receiver berupa pointer, adalah ilegal karena method-method dengan receiver pointer tidak berada dalam kumpulan method dari tipe nilai.
Nilai-nilai fungsi yang diturunkan dari method dipanggil dengan sintaksis
pemanggilan fungsi biasa;
si receiver diberikan sebagai argumen pertama pada pemanggilan.
Maka, diberikan f := T.Mv
, f
dipanggil dengan cara f(t,7)
bukan
t.f(7)
.
Untuk membentuk sebuah fungsi yang mengikat receiver, gunakan
literal fungsi
atau
nilai method.
Adalah legal menurunkan sebuah nilai fungsi dari sebuah method bertipe interface. Hasil fungsinya menerima receiver eksplisit dari tipe interface tersebut.
Nilai method
Jika ekspresi x
memiliki tipe statis T
dan M
berada dalam
kumpulan method
dari tipe T
, x.M
disebut nilai method.
Nilai method x.M
yaitu sebuah nilai fungsi yang dapat dipanggil dengan
argumen yang sama seperti memanggil method dari x.M
.
Ekspresi x
dievaluasi dan disimpan selama evaluasi dari nilai method;
salinan yang disimpan kemudian digunakan sebagai receiver pada setiap
pemanggilan, yang bisa saja dieksekusi nantinya.
Tipe T
bisa berupa tipe interface atau bukan interface.
Seperti yang telah didiskusikan dalam
ekspresi method
di atas, misalnya ada sebuah struct bertipe T
dengan dua method, Mv
,
dengan receiver bertipe T
, dan Mp
, dengan receiver bertipe *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 var pt *T func makeT() T
Ekspresi dari
t.Mv
menghasilkan sebuah nilai fungsi bertipe
func(int) int
Pemanggilan berikut adalah sama:
t.Mv(7) f := t.Mv; f(7)
Hal yang sama, ekspresi berikut
pt.Mp
menghasilkan sebuah nilai fungsi bertip
func(float32) float32
Seperti halnya dengan
selector,
sebuah referensi ke method yang bukan interface dengan receiver nilai
menggunakan pointer akan secara otomatis membalikan pointer tersebut:
pt.Mv
sama dengan (*pt)Mv
.
Seperti halnya dengan
pemanggilan method,
sebuah referensi ke method yang bukan interface dengan receiver pointer
menggunakan nilai beralamat akan secara otomatis mengambil alamat dari nilai
tersebut: t.Mp
sama dengan (&t).Mp
.
f := t.Mv; f(7) // sama dengan t.Mv(7) f := pt.Mp; f(7) // sama dengan pt.Mp(7) f := pt.Mv; f(7) // sama dengan (*pt).Mv(7) f := t.Mp; f(7) // sama dengan (&t).Mp(7) f := makeT().Mp // tidak valid: hasil dari makeT() tidak beralamat.
Walaupun contoh di atas menggunakan tipe bukan interface, merupakan hal yang legal untuk membuat sebuah nilai method dari nilai bertipe interface.
var i interface { M(int) } = myVal f := i.M; f(7) // sama dengan i.M(7)
Ekspresi indeks
Ekspresi dasar dari bentuk
a[x]
menyatakan elemen dari array, pointer ke array, slice, atau map a
yang
diindeks oleh x
.
Nilai x
disebut dengan indeks atau map key.
Aturan-aturan berikut berlaku:
Jika a
bukan sebuah map:
-
indeks
x
haruslah bertipe integer atau konstanta tanpa tipe -
indeks berupa konstanta haruslah tidak negatif dan dapat direpresentasikan oleh sebuah nilai bertipe
int
-
indeks berupa konstanta yang tidak bertipe akan diberikan tipe
int
-
indeks
x
berada dalam rentang jika0 ⇐ x < len(a)
, selain itu di luar rentang
Untuk a
dengan
tipe Array
A:
-
sebuah indeks berupa konstanta haruslah dalam rentang
-
jika
x
di luar rentang pada saat runtime, sebuah panik runtime akan terjadi -
a[x]
adalah elemen array pada indeksx
dan tipe daria[x]
yaitu elemen bertipeA
Untuk a
sebagai
pointer
ke tipe array:
-
a[x]
adalah singkatan untuk(*a)[x]
Untuk a
dengan
tipe slice
S
:
-
jika
x
di luar rentang pada saat runtime, panik runtime akan terjadi -
a[x]
adalah elemen slice pada indeksx
dan tipe daria[x]
yaitu elemen bertipeS
Untuk a
dari
tipe string:
-
sebuah indeks berupa konstanta haruslah dalam rentang jika string
a
juga konstanta` -
jika
x
di luar rentang pada saat runtime, panik runtime akan terjadi -
a[x]
adalah nilai byte bukan konstanta pada indeksx
dan tipe daria[x]
adalah byte -
a[x]
tidak bisa ditimpa bilaa
adalah konstanta
Untuk a
dari
tipe map
M
:
-
tipe
x
haruslah bisa disimpan ke tipe key dariM
-
jika map berisi sebuah nilai dengan key
x
,a[x]
adalah elemen map dengan keyx
dan tipe daria[x]
yaitu tipe elemen dariM
-
jika map adalah
nil
atau tidak mengandung nilai dengan keyx
,a[x]
yaitu nilai kosong dari tipe elemen dariM
.
Selain itu a[x]
adalah ilegal.
Ekspresi indeks dapat sebuah map bertipe map[K]V
yang digunakan dalam sebuah
penempatan
atau inisiasi dari bentuk khusus
v, ok = a[x] v, ok := a[x] var v, ok = a[x]
menghasilkan sebuah nilai boolean tidak bertipe tambahan.
Nilai dari ok
adalah true
jika key x
ada dalam map, dan false
jika
tidak ada.
Menempatkan ke elemen map yang nil
akan mengakibatkan
panik runtime.
Ekspresi pemotongan
Ekspresi pemotongan membentuk sebuah substring atau slice dari sebuah string, array, pointer ke array, atau slice. Ada dua varian: bentuk sederhana yang menentukan batas bawah dan atas, dan bentuk penuh yang juga menentukan batas dari kapasitas.
Ekspresi sederhana pemotongan
Untuk sebuah string, array, pointer ke array, atau slice a
, ekspresi dasar
a[low : high]
membentuk sebuah substring atau slice.
Indeks low
dan high
memilih elemen mana dari operan a
yang akan
dikembalikan.
Hasil kembaliannya akan memiliki indeks yang berawal dari 0 dan panjang sama
dengan high - low
.
Setelah memotong array a
a := [5]int{1, 2, 3, 4, 5} s := a[1:4]
slice s
bertipe []int
, panjang 3, kapasitas 4, dan elemennya
s[0] == 2 s[1] == 3 s[2] == 4
Demi kemudahan, indeks-indeks tersebut bisa diindahkan.
Nilai indeks low
yang diindahkan yaitu nol;
Nilai indeks high
yang diindahkan yaitu panjang dari operan slice:
a[2:] // sama dengan a[2 : len(a)] a[:3] // sama dengan a[0 : 3] a[:] // sama dengan a[0 : len(a)]
Jika a
adalah pointer ke sebuah array, a[low : high]
adalah singkatan
untuk (*a)[low : high]
.
Untuk array atau string, indeks berada dalam rentang jika
0 ⇐ low ⇐ high ⇐ len(a)
, selain itu berarti di luar rentang.
Untuk slice, batas atas dari indeks yaitu kapasitas slice cap(a)
bukan
panjangnya.
Indeks berupa
konstanta
haruslah tidak negatif dan
dapat direpresentasikan
oleh nilai bertipe int
;
untuk array atau string konstanta, indeks dari konstanta haruslah berada dalam
rentang.
Jika kedua indeks adalah konstanta, keduanya haruslah memenuhi low ⇐ high
.
Jika indeks-indeks di luar rentang pada saat runtime,
panik runtime
terjadi.
Kecuali untuk
string tak bertipe,
jika operan pemotongan adalah sebuah string atau slice, hasil dari operasi
pemotongan adalah nilai non-konstanta bertipe string
.
Jika opera pemotongan adalah sebuah array, maka harus dapat
memiliki alamat
dan hasil dari operasi pemotongan yaitu sebuah slice dengan tipe elemen yang
sama dengan array.
Jika operan slice dari sebuah ekspresi pemotongan yang valid adalah
sebuah slice yang nil
, hasilnya yaitu sebuah slice nil
.
Selain itu, jika hasilnya adalah sebuah slice, ia akan memiliki array dasar
yang sama dengan operan.
var a [10]int s1 := a[3:7] // array dasar dari s1 yaitu array a; &s1[2] == &a[5] s2 := s1[1:4] // array dasar dari s2 yaitu array dasar dari s1 yaitu array a; // &s2[1] == &a[5] s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; semuanya mengacu pada elemen // array dasar yang sama
Ekspresi pemotongan penuh
Untuk sebuah array, pointer ke array, atau slice a
(tetapi bukan string),
ekspresi dasar
a[low : high : max]
membentuk sebuah slice dengan tipe yang sama, dan dengan panjang dan elemen
seperti pada ekspresi pemotongan sederhana a[low : high]
.
Sebagai tambahan, ia mengontrol kapasitas slice kembalian dengan mensetnya
menjadi max - low
.
Hanya indeks pertama yang boleh diindahkan;
yang bernilai dasar 0.
Setelah memotong array a
a := [5]int{1, 2, 3, 4, 5} t := a[1:3:5]
slice t
bertipe []int
, panjang 2, kapasitas 4, dan elemennya
t[0] == 2 t[1] == 3
Seperti pada ekspresi pemotongan sederhana, jika a
adalah sebuah pointer ke
array, a[low : high : max]
adalah singkatan dari (*a)[low : high :max]
.
Jika operan pemotongan adalah sebuah array, maka haruslah
memiliki alamat.
Indeks-indeksnya berada dalam rentang jika
0 ⇐ low ⇐ high ⇐ max ⇐ cap(a)
,
selain itu adalah di luar rentang.
Indeks berupa
konstanta
haruslah tidak negatif dan
direpresentasikan
oleh sebuah nilai bertipe int
;
untuk array, indeks konstanta haruslah juga berada dalam rentang.
Jika beberapa indeks adalah konstanta, konstanta tersebut haruslah dalam
rentang yang relatif terhadap satu sama lain.
Jika indeks-indeks di luar rentang pada saat runtime,
panik runtime
terjadi.
Asersi Tipe
Untuk sebuah ekspresi x
dari
tipe interface
dan sebuah tipe T
, ekspresi dasar berikut
x.(T)
menyatakan bahwa x
tidak nil
dan nilai yang tersimpan dalam x
bertipe
T
.
Notasi x.(T)
disebut dengan asersi tipe.
Lebih tepatnya, jika T
bukan bertipe interface, x.(T)
menyatakan bahwa
tipe dinamis dari x
adalah
identik
dengan tipe T
.
Dalam kasus ini, T
harus
mengimplementasikan
tipe (interface) dari x
;
sebaliknya asersi tipe tidak valid karena tidak mungkin bagi x
untuk
menyimpan nilai bertipe T
.
Jika T
adalah tipe interface, x.(T)
menyatakan bahwa tipe dinamis dari x
mengimplementasikan interface T
.
Jika asersi tipe berhasil, nilai dari ekspresi tersebut yaitu nilai yang
tersimpan dalam x
dan tipenya yaitu T
.
Jika asersi tipe gagal,
panik runtime
terjadi.
Dengan kata lain, walaupun tipe dinamis dari x
diketahui pada saat
runtime, tipe dari x.(T)
hanya diketahui sebagai T
dalam program yang
benar.
var x interface{} = 7 // x bertipe dinamis int dan nilainya 7 i := x.(int) // i bertipe int dan bernilai 7 type I interface { m() } func f(y I) { s := y.(string) // ilegal: string tidak mengimplementasikan // I (method m tidak ada) r := y.(io.Reader) // r bertipe io.Reader dan tipe dinamis dari y // harus mengimplementasikan I dan io.Reader … }
Asersi tipe yang digunakan dalam penempatan atau inisiasi dari bentuk khusus
v, ok = x.(T) v, ok := x.(T) var v, ok = x.(T) var v, ok T1 = x.(T)
menghasilkan nilai boolean tambahan.
Nilai dari ok
adalah true
jika asersi berhasil.
Sebaliknya ia akan false
dan nilai dari v
adalah
nilai kosong
untuk tipe T
.
Tidak ada
panik _runtime
yang akan terjadi dalam kasus ini.
Pemanggilan
Diberikan sebuah ekspresi f
dari tipe fungsi F
,
f(a1, a2, … an)
akan memanggil f
dengan argumen a1, a2, … an
.
Kecuali untuk kasus khusus, argumen-argumen haruslah ekspresi nilai-tunggal
yang dapat
disimpan
ke tipe parameter dari F
dan dievaluasi sebelum fungsi dipanggil.
Tipe dari ekspresi adalah tipe kembalian dari F
.
Pemanggilan sebuah method mirip namun method itu sendiri ditentukan sebagai
sebuah selector dari sebuah nilai dari tipe receiver untuk method
tersebut.
math.Atan2(x, y) // pemanggilan fungsi var pt *Point pt.Scale(3.5) // pemanggilan method dengan receiver pt
Dalam pemanggilan sebuah fungsi, nilai fungsi dan argumennya dievaluasi dengan urutan yang biasa. Setelah mereka dievaluasi, parameter-parameter dari pemanggilan dikirim dengan nilai ke dalam fungsi dan fungsi yang dipanggil tersebut mulai dieksekusi. Parameter kembalian dari fungsi dikirim dengan nilai ke pemanggil fungsi saat fungsi berakhir.
Memanggil fungsi yang nil
menyebabkan
panik runtime.
Kasus khusus, jika nilai kembalian dari sebuah fungsi atau method g
sama
jumlahnya dan dapat ditempatkan pada parameter-parameter di fungsi atau method
f
, maka pemanggilan f(g(parameter_dari_g))
akan memanggil f
setelah nilai kembalian dari g
diberikan sebagai parameter ke f
secara
berurutan.
Pemanggilan dari f
tidak boleh berisi parameter selain pemanggilan ke g
,
dan g
paling tidak harus mengembalikan satu nilai.
Jika f
memiliki parameter akhir …, ia akan diisi nilai kembalian dari g
yang tersisa setelah penempatan parameter biasa sebelumnya.
func Split(s string, pos int) (string, string) { return s[0:pos], s[pos:] } func Join(s, t string) string { return s + t } if Join(Split(value, len(value)/2)) != value { log.Panic("test fails") }
Pemanggilan sebuah method x.m()
valid jika
kumpulan method
dari (tipe) x
memiliki m
dan daftar argumen dapat diberikan ke daftar
parameter dari m
.
Jika x
adalah
alamat
dan kumpulan method &x
memiliki m
, x.m()
adalah singkatan untuk
(&x).m()
:
var p Point p.Scale(3.5)
Tidak ada perbedaan tipe method dan tidak ada literal method.
Mengirim argumen ke parameter …
Jika f
adalah
variadic
dengan parameter terakhir bertipe …T
, maka tipe dari p
dalam f
sama
dengan tipe []T
.
Jika f
dipanggil tanpa argumen untuk p
, maka nilai yang dikirim ke p
adalah nil
.
Sebaliknya, nilai yang dikirim adalah slice baru bertipe []T
dengan array
dasar yang elemennya adalah argumen-argumen aslinya, yang semuanya harus dapat
disimpan
ke T
.
Panjang dan kapasitas slice yaitu jumlah argumen dan bisa berbeda untuk
setiap pemanggilan.
Diberikan fungsi dan pemanggilan
func Greeting(prefix string, who ...string) Greeting("nobody") Greeting("hello:", "Joe", "Anna", "Eileen")
dalam Greeting
, who
akan bernilai nil
pada pemanggilan yang pertama, dan
[]string{"Joe", "Anna", "Eileen"}
pada pemanggilan kedua.
Jika argumen terakhir dapat disimpan ke slice bertipe []T
, maka akan dikirim
tanpa diubah sebagai nilai untuk parameter …T
jika argumen diikuti oleh
…
.
Dalam kasus ini tidak ada slice baru yang dibuat.
Diberikan sebuah slice s
dan pemanggilan
s := []string{"James", "Jasmine"} Greeting("goodbye:", s...)
dalam Greeting
, who
akan bernilai sama dengan s
dengan array dasar yang
sama.
Operator-operator
Operator menggabungkan operan menjadi ekspresi.
Expression = UnaryExpr | Expression binary_op Expression . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = "||" | "&&" | rel_op | add_op | mul_op . rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . add_op = "+" | "-" | "|" | "^" . mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
Operator pembandingan didiskusikan dibagian lain. Untuk operator binari lainnya, tipe operan haruslah identik kecuali pada operasi shift atau bila menggunakan konstanta tanpa tipe. Untuk operasi yang mengikutkan konstanta saja, lihat bagian pada ekspresi konstanta.
Kecuali untuk operasi shift, jika salah satu operan adalah konstanta dan operan lainnya bukan, konstanta tersebut secara implisit dikonversi ke tipe operan lainnya.
Operan sebelah kanan dalam ekspresi shift haruslah tipe integer atau
konstanta tak bertipe yang dapat
direpresentasikan oleh sebuah
nilai bertipe uint
.
Jika operan sebelah kiri dari ekspresi shift adalah konstanta tanpa tipe,
maka ia secara implisit dikonversi ke tipe yang diasumsikan jika ekspresi
shift diganti oleh operan kiri.
var s uint = 33 var i = 1<<s // 1 bertipe int var j int32 = 1<<s // 1 bertipe int32; j == 0 var k = uint64(1<<s) // 1 bertipe uint64; k == 1<<33 var m int = 1.0<<s // 1.0 bertipe int; m == 0 jika int berukuran // 32bit var n = 1.0<<s == j // 1.0 bertipe int32; n == true var o = 1<<s == 2<<s // 1 dan 2 bertipe int; o == true jika int // berukuran 32bit var p = 1<<s == 1<<33 // ilegal jika int berukuran 32bit: 1 bertipe // int, namun 1<<33 int overflow var u = 1.0<<s // ilegal: 1.0 bertipe float64, tidak bisa shift var u1 = 1.0<<s != 0 // ilegal: 1.0 bertipe float64, tidak bisa shift var u2 = 1<<s != 1.0 // ilegal: 1 bertipe float64, tidak bisa shift var v float32 = 1<<s // ilegal: 1 bertipe float32, tidak bisa shift var w int64 = 1.0<<33 // 1.0<<33 adalah ekspresi konstanta shift var x = a[1.0<<s] // 1.0 bertipe int; x == a,0>> jika int berukuran // 32bit var a = make([]byte, 1.0<<s) // 1.0 bertipe int; len(a) == 0 jika int // berukuran 32bit
Urutan operator
Operator unary memiliki urutan tertinggi.
Secara operator ` dan `--` membentuk perintah, bukan ekspresi, maka mereka
tidak berada dalam hierarki operator.
Akibatnya, perintah `*p
sama dengan (*p)
.
Ada lima tingkat urutan untuk operator binari.
Operator perkalian yang paling tinggi, diikuti oleh operator penambahan,
operator pembandingan, &&
(operasi logika AND), dan terakhir ||
(operator
logika OR):
Precedence Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
Operator binari pada tingkat yang sama akan di proses berurutan dari kiri ke
kanan.
Misalnya, x / y * z
sama dengan (x / y) * z
.
+x 23 + 3*x[i] x <= f() ^a >> b f() || g() x == y+1 && <-chanPtr > 0
Operator aritmetika
Operator aritmetika dipakai pada nilai numerik dan menghasilkan nilai dengan tipe yang sama dengan operan pertama. Keempat operator standar aritmetika (, -, *, /) berlaku pada tipe integer, _floating-point_, dan complex; `` juga berlaku pada string. Operator shift dan logika bitwise berlaku pada integer saja.
+ penjumlahan integer, float, nilai complex, string - pengurangan integer, float, nilai complex * perkalian integer, float, nilai complex / pembagian integer, float, nilai complex % sisa pembagian integer & bitwise AND integer | bitwise OR integer ^ bitwise XOR integer &^ bit clear (AND NOT) integer << shift kiri integer << unsigned integer >> shift kanan integer >> unsigned integer
Operator integer
Untuk dua integer bernilai x
dan y
, persamaan integer q = x / y
dan sisa
pembagian r = x % y
memenuhi hubungan berikut:
x = q*y + r dan |r| < |y|
dengan x / y
dibulatkan ke nol
(pembagian dibulatkan).
x y x / y x % y 5 3 1 2 -5 3 -1 -2 5 -3 -1 2 -5 -3 1 -2
Satu-satunya pengecualian dari aturan ini yaitu jika sisa pembagian x
adalah nilai negatif paling besar dari tipe x
, persamaan q = x / -1
sama
dengan x
(dan r = 0
) dikarenakan
integer overflow
dengan dua-komplemen:
x, q int8 -128 int16 -32768 int32 -2147483648 int64 -9223372036854775808
Jika penyebut adalah sebuah konstanta, ia tidak boleh nol. Jika penyebut adalah nol pada saat runtime, panik runtime terjadi. Jika pembilang tidak negatif dan penyebut adalah sebuah konstanta kelipatan 2, maka pembagian bisa diganti dengan shift kanan, dan penghitungan sisa pembagian bisa diganti dengan operasi bitwise AND:
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3 -11 -2 -3 -3 1
Operator shift menggeser operan sebelah kiri sebanyak jumlah yang
disebutkan pada operan sebelah kanan, yang haruslah non-negatif.
Jika bagian kanan operan shift negatif pada saat runtime,
panik runtime
terjadi.
Operator shift menerapkan penggeseran aritmetika jika operan kiri adalah
signed integer dan logika penggeseran jika ia adalah unsigned integer.
Tidak ada batas atas pada nilai penggeser.
Penggeseran berjalan seperti operan kiri digeser n
kali dengan 1 untuk
jumlah penggeser n
.
Hasilnya, x 1` sama dengan `x*2` dan `x 1
sama dengan x/2
tetapi
dibulatkan ke nilai negatif tak terhingga.
Untuk operan integer, operator unary +
, -
, dan ^
didefinisikan
sebagai berikut:
+x yaitu 0 + x -x negasi yaitu 0 - x ^x komplemen bitwise yaitu m ^ x dengan m = "semua bit diset ke 1" untuk unsigned x dan m = -1 untuk signed x
Integer overflow
Untuk nilai unsigned integer, operasi +
, -
, *
, dan yang mana `n` adalah lebar bit dari tipe
<
Untuk signed integer, operasi +
, -
, *
, /
dan operasi.
Compiler bisa saja tidak mengoptimalkan sebuah kode dengan asumsi bahwa
overflow tidak akan terjadi.
Misalnya, compiler tidak mengasumsikan bahwa `x < x + 1
selalu bernilai
true
.
Operator floating-point
Untuk bilangan floating-point dan complex, +x
sama dengan x
, sementara
-x
adalah negasi dari x
.
Hasil pembagian dari floating-point atau complex dengan nol tidak
dispesifikasikan oleh standar IEEE-754;
apakah
panik runtime
terjadi adalah spesifik pada implementasi.
Implementasi bisa menggabungkan beberapa operasi floating-point menjadi sebuah operasi gabungan, kemungkinan antara beberapa perintah, dan menghasilkan nilai yang berbeda bila dieksekusi dan dibulatkan masing-masing. Konversi tipe floating-point secara eksplisit membulatkan ke presisi dari tipe target, menghindari penggabungan yang menyebabkan pembulatan.
Misalnya, beberapa arsitektur menyediakan instruksi "fused multiply add" (FMA)
yang menghitung x*y + z
tanpa pembulatan dari hasil x*y
.
Contoh berikut memperlihatkan kapan implementasi Go menggunakan instruksi
tersebut:
// FMA dibolehkan menghitung r, karena x*y secara eksplisit tidak dibulatkan: r = x*y + z r = z; r += x*y t = x*y; r = t + z *p = x*y; r = *p + z r = x*y + float64(z) // FMA tidak dibolehkan menghitung r, karena akan menghilangkan pembulatan // dari x*y: r = float64(x*y) + z r = z; r += float64(x*y) t = float64(x*y); r = t + z
Penggabungan string
Beberapa string bisa digabungkan menggunakan operator ` atau operator
penempatan `=
:
s := "hi" + string(c) s += " and good bye"
Penambahan string menghasilkan string yang baru dengan menggabungkan operan-operannya.
Operator pembandingan
Operator pembandingan membandingkan dua opera dan menghasilkan nilai boolean tak bertipe.
== equal != not equal < less <= less or equal > greater >= greater or equal
Dalam setiap pembandingan, operan pertama haruslah dapat disimpan ke tipe dari operan kedua, atau sebaliknya.
Operator persamaan ==
dan !=
berlaku untuk operan-operan yang dapat
dibandingkan.
Operator pengurutan <
, ⇐
, >
, dan >=
berlaku untuk operan-operan yang
dapat diurutkan.
Berikut definisi aturan-aturan dan hasil dari pembandingan:
-
Nilai boolean dapat dibandingkan. Dua boolean adalah sama jika keduanya adalah
true
ataufalse
. -
Nilai integer dapat dibandingkan dan diurutkan, dengan cara yang biasa.
-
Nilai floating-point dapat dibandingkan dan diurutkan, seperti yang didefinisikan oleh standar IEEE-754
-
Nilai complex dapat dibandingkan. Dua nilai complex
u
danv
adalah sama jika keduanyareal(u) == real(v)
danimag(u) == imag(v)
. -
Nilai string dapat dibandingkan dan diurutkan, secara leksikal per byte.
-
Nilai pointer dapat dibandingkan. Dua nilai pointer adalah sama jika ia menunjuk ke variabel yang sama atau jika keduanya bernilai
nil
. Pointer ke variabel dengan ukuran nol bisa jadi sama atau tidak. -
Nilai channel dapat dibandingkan. Dua channel adalah sama jika keduanya dibuat oleh pemanggilan make yang sama atau jika keduanya bernilai
nil
. -
Nilai interface dapat dibandingkan. Dua interface dikatakan sama jika keduanya memiliki tipe dinamis yang identik dan memiliki nilai dinamis yang sama atau jika keduanya bernilai
nil
. -
Nilai
x
bertipe bukan interfaceX
dan nilait
bertipe interfaceT
dapat dibandingkan bila nilai dari tipeX
dapat dibandingkan danX
mengimplementasikanT
. Keduanya sama jika tipe dinamist
identik denganX
dan nilai dinamist
sama denganx
. -
Nilai struct dapat dibandingkan jika semua field dapat dibandingkan. Dua nilai struct adalah sama jika setiap field yang bukan kosong adalah sama.
-
Nilai array dapat dibandingkan jika nilai dari tipe elemen array dapat dibandingkan. Dua nilai array adalah sama jika kedua elemen array adalah sama.
Pembandingan dua nilai interface dengan tipe dinamis yang identik menyebabkan panik runtime jika nilai dari tipe tersebut tidak bisa dibandingkan. Perilaku ini berlaku tidak saja pada pembandingan nilai interface langsung namun juga saat membandingkan array dari nilai interface atau struct dengan field bernilai interface.
Nilai slice, map, dan fungsi tidak bisa dibandingkan.
Namun, sebagai kasus spesial, nilai slice, map, atau fungsi bisa jadi
dibandingkan dengan pengidentifikasi nil
.
Pembandingan nilai pointer, channel, dan interface dengan nil
juga
dibolehkan dan mengikuti aturan di atas.
const c = 3 < 4 // c adalah konstanta boolean tak bertipe true type MyBool bool var x, y int var ( // Hasil dari pembandingan a yaitu boolean tak bertipe. // Aturan penempatan biasa berlaku. b3 = x == y // b3 bertipe bool b4 bool = x == y // b4 bertipe bool b5 MyBool = x == y // b5 bertipe MyBool )
Operator logika
Operator logika berlaku untuk nilai boolean dan menghasilkan tipe yang sama dengan operan. Operan kanan dievaluasi berdasarkan kondisi.
&& kondisi AND p && q yaitu "jika p maka q selainnya false" || kondisi OR p || q yaitu "jika p maka true selainnya q" ! NOT !p yaitu "bukan p"
Operator alamat
Untuk sebuah operan x
bertipe T
, operasi alamat &x
mengembalikan sebuah
pointer bertipe *T
ke x
.
Operan tersebut haruslah memiliki alamat, yaitu, sebuah variabel, pointer
ke pointer, atau operasi pengindeksan slice;
atau sebuah selector field dari operan struct yang memiliki alamat;
atau sebuah operasi pengindeksan array dari array yang memiliki alamat;.
Salah satu pengecualian dari kebutuhan alamat ini yaitu x
mungkin bisa
sebuah (dalam tanda kurung)
literal komposit.
Jika evaluasi dari x
menyebabkan
panik runtime,
maka evaluasi dari &x
juga akan panik.
Untuk sebuah operan x
berupa pointer bertipe *T
, pointer ke pointer dari
*x
menyatakan
variabel
bertipe T
yang ditunjuk oleh x
.
Jika x
adalah nil
, evaluasi dari *x
akan menyebabkan
panik runtime.
&x &a[f(2)] &Point{2, 3} *p *pf(x) var x *int = nil *x // menyebabkan panik runtime &*x // menyebabkan panik runtime
Operator terima
Untuk sebuah operan ch
bertipe channel, nilai dari
operasi terima ←ch
adalah nilai yang diterima dari channel ch
.
Arah dari channel haruslah membolehkan operasi terima, dan tipe kembalian dari
operasi terima yaitu tipe elemen dari channel.
Ekspresi dari ←ch
akan diblok sampai sebuah nilai tersedia.
Operasi terima dari channel yang nil
akan diblok selamanya.
Operasi terima dari channel yang telah ditutup akan selalu
diproses langsung, menghasilkan nilai kosong dari tipe
elemen setelah semua nilai yang sebelumnya dikirim ke channel telah diterima
semuanya.
v1 := <-ch v2 = <-ch f(<-ch) <-strobe // tunggu sampai waktu habis dan hiraukan nilai yang diterima
Ekspresi terima yang digunakan dalam penempatan atau inisiasi dari bentuk khusus
x, ok = <-ch x, ok := <-ch var x, ok = <-ch var x, ok T = <-ch
menghasilkan nilai boolean yang melaporkan apakah komunikasi sukses atau
tidak.
Nilai dari ok
adalah true
jika nilai yang diterima dikirim oleh
sebuah operasi pengiriman ke channel yang sukses, atau false
jika
nilainya adalah nilai kosong karena channel telah ditutup dan kosong.
Konversi
Sebuah konversi mengubah tipe dari sebuah ekspresi ke tipe yang ditentukan oleh konversi. Sebuah konversi bisa muncul secara literal dalam sumber kode, atau ia bisa diimplikasikan oleh konteks di mana ekspresi muncul.
Konversi eksplisit yaitu ekspresi dalam bentuk T(x)
dengan T
adalah sebuah
tipe dan x
adalah sebuah ekspresi yang dapat dikonversi ke tipe T
.
Conversion = Type "(" Expression [ "," ] ")" .
Jika tipe diawali dengan operator *
atau ←
, atau jika tipe dimulai dengan
kata kunci func
dan tidak memiliki daftar kembalian, maka ia haruslah diberi
tanda kurung bila diperlukan untuk menghindari kebingungan:
*Point(p) // sama dengan *(Point(p)) (*Point)(p) // p dikonversi ke *Point <-chan int(c) // sama dengan <-(chan int(c)) (<-chan int)(c) // c dikonversi ke <-chan int func()(x) // penanda fungsi func() x (func())(x) // x dikonversi ke func() (func() int)(x) // x dikonversi ke func() int func() int(x) // x dikonversi ke func() int (tidak ambigu)
Sebuah konstanta bernilai x
dapat dikonversi ke tipe T
jika
x
dapat direpresentasikan oleh nilai dari T
.
Untuk kasus khusus, nilai konstanta x
dapat secara eksplisit dikonversi ke
sebuah tipe string menggunakan
aturan yang sama
dengan nilai non-konstanta x
.
Mengonversi sebuah konstanta menghasilkan konstanta bertipe.
uint(iota) // nilai iota bertipe uint float32(2.718281828) // 2.718281828 dengan tipe float32 complex128(1) // 1.0 + 0.0i bertipe complex128 float32(0.49999999) // 0.5 bertipe float32 float64(-1e-1000) // 0.0 bertipe float64 string('x') // "x" bertipe string string(0x266c) // "♬" bertipe string MyString("foo" + "bar") // "foobar" bertipe MyString string([]byte{'a'}) // bukan konstanta: []byte{'a'} bukan konstanta (*int)(nil) // bukan konstanta: nil bukan konstanta, *int bukan // sebuah tipe boolean, numerik, atau string int(1.2) // ilegal: 1.2 tidak dapat direpresentasikan sebagai int string(65.0) // ilegal: 65.0 bukan konstanta integer
Sebuah nilai yang bukan konstanta x
dapat dikonversi ke tipe T
dalam
kasus-kasus berikut:
-
x
dapat disimpan keT
-
mengindahkan tag dari struct (lihat bagian bawah), tipe
x
danT
memiliki tipe dasar yang identik.. -
mengindahkan tag dari struct (lihat bagian bawah), tipe
x
danT
adalah tipe pointer yang bukan tipe terdefinisi, dan tipe dasar pointer keduanya memiliki tipe dasar yang identik. -
tipe
x
danT
adalah tipe integer atau floating point. -
tipe
x
danT
adalah tipe complex. -
x
adalah integer atau slice byte atau rune danT
bertipe string -
x
adalah string danT
adalah sebuah slice byte atau rune.
tag dari struct diindahkan saat membanding tipe struct untuk identitas untuk tujuan konversi:
type Person struct { Name string Address *struct { Street string City string } } var data *struct { Name string `json:"name"` Address *struct { Street string `json:"street"` City string `json:"city"` } `json:"address"` } var person = (*Person)(data) // mengindahkan tag, tipe dasarnya identik
Aturan-aturan spesifik berlaku untuk konversi (non-konstanta) antara tipe
numerik atau dari dan ke tipe string.
Konversi ini bisa mengubah representasi dari x
dan membutuhkan biaya pada
saat runtime.
Konversi yang lainnya hanya mengubah tipe tetapi tidak representasi dari x
.
Tidak ada mekanisme linguistik untuk mengonversi antara pointer dan integer. Paket unsafe mengimplementasikan fungsionalitas tersebut dengan batasan-batasan tertentu.
Konversi antara tipe numerik
Untuk konversi nilai numerik yang bukan konstanta, aturan-aturan berikut berlaku:
1. Saat mengonversi antara tipe integer, jika nilai adalah signed integer,
maka sign -nya diperpanjang menjadi presisi tanpa batas;
selain dari itu dinolkan.
Nilainya kemudian dipotong untuk sesuai dengan ukuran tipe tujuan.
Sebagai contohnya, jika v := uint16(0x10F0)
, maka uint32(int8(v)) ==
0xFFFFFFF0
.
Konversi selalu menghasilkan nilai yang valid;
tidak ada indikasi overflow.
2. Saat mengonversi bilangan floating point ke sebuah integer, maka
pecahannya diindahkan (dibulatkan ke nol).
3. Saat mengonversi sebuah bilangan integer atau floating-point ke tipe
floating-point, atau sebuah bilangan complex ke tipe complex lainnya,
nilai akhirnya dibulatkan ke presisi yang ditentukan oleh tipe tujuan.
Misalnya, nilai variabel x
bertipe float32
disimpan menggunakan
presisi tambahan diluar bilangan IEEE-754 32-bit, namun float32(x)
merepresentasikan hasil dari pembulatan nilai x
ke presisi 32-bit.
Hal yang sama, x + 0.1
bisa memakai presisi lebih dari 32 bit, namun
float32(x + 0.1)
tidak.
Dalam semua konversi non-konstanta yang mengikutkan nilai floating-point atau complex, jika tipe tujuan tidak dapat merepresentasikan nilai maka konversi akan sukses namun nilai akhirnya bergantung pada implementasi.
Konversi ke dan dari tipe string
(1) Mengonversi nilai signed atau unsigned integer ke tipe string menghasilkan sebuah string yang berisi representasi UTF-8 dari integer. Nilai di luar rentang kode Unicode yang valid dikonversi ke "uFFFD".
string('a') // "a" string(-1) // "\ufffd" == "\xef\xbf\xbd" string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8" type MyString string MyString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
(2) Mengonversi sebuah slice byte ke tipe string menghasilkan sebuah string yang setiap rangkaian byte adalah elemen dari slice.
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" string([]byte{}) // "" string([]byte(nil)) // "" type MyBytes []byteConstant_expressions string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"
(3) Mengonversi sebuah slice rune ke tipe string menghasilkan sebuah string yang merupakan penggabungan dari setiap nilai rune yang dikonversi ke string.
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type MyRunes []rune string(MyRunes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
(4) Mengonversi sebuah nilai bertipe string ke slice byte menghasilkan sebuah slice yang rangkaian elemen-elemennya adalah byte dari string.
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} MyBytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
(5) Mengonversi nilai bertipe string ke slice rune menghasilkan sebuah slice yang berisi setiap individu kode point Unicode dari string.
[]rune(MyString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} MyRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
Ekspresi konstanta
Ekspresi konstanta bisa berisi hanya operan-operan konstanta dan dievaluasi saat di-compile.
Konstanta tanpa tipe dari boolean, numerik, dan string bisa digunakan sebagai operan di mana pun selama legal menggunakan operan bertipe boolean, numerik, atau string.
Pembandingan konstanta selalu menghasilkan konstanta boolean tak bertipe. Jika operan kiri dari ekspresi shift adalah konstanta tak bertipe, hasilnya adalah konstanta integer; sebaliknya jika operan kiri yaitu konstanta bertipe maka hasilnya konstanta dengan tipe yang sama dengan operan kiri, yang haruslah bertipe integer.
Operasi lain pada konstanta tanpa tipe menghasilkan konstanta tanpa tipe dari jenis yang sama: yaitu konstanta boolean, integer, floating-point, complex, atau string. Jika operan dari operasi binari tidak bertipe (selain shift) memiliki jenis yang berbeda, hasilnya yaitu jenis operan yang muncul terakhir dalam daftar: integer, rune, floating-point, complex. Misalnya, konstanta integer tanpa tipe dibagi dengan konstanta complex tanpa tipe menghasilkan konstanta complex tanpa tipe.
const a = 2 + 3.0 // a == 5.0 (konstanta floating-point tanpa tipe) const b = 15 / 4 // b == 3 (konstanta integer tanpa tipe) const c = 15 / 4.0 // c == 3.75 (konstanta floating-point tanpa tipe) const Θ float64 = 3/2 // Θ == 1.0 (tipe float64, 3/2 adalah pembagian integer) const Π float64 = 3/2. // Π == 1.5 (tipe float64, 3/2 adalah pembagian integer) const d = 1 << 3.0 // d == 8 (konstanta integer tanpa tipe) const e = 1.0 << 3 // e == 8 (konstanta integer tanpa tipe) const f = int32(1) << 33 // illegal (konstanta 8589934592 overflow int32) const g = float64(2) >> 1 // illegal (float64(2) adalah konstanta bertipe floating-point) const h = "foo" > "bar" // h == true (konstanta boolean tanpa tipe) const j = true // j == true (konstanta boolean tanpa tipe) const k = 'w' + 1 // k == 'x' (konstanta rune tanpa tipe) const l = "hi" // l == "hi" (konstanta string tanpa tipe) const m = string(k) // m == "x" (tipe string) const Σ = 1 - 0.707i // (konstanta complex tanpa tipe) const Δ = Σ + 2.0e-4 // (konstanta complex tanpa tipe) const Φ = iota*1i - 1/1i // (konstanta complex tanpa tipe)
Menerapkan fungsi bawaan complex
ke konstanta integer, rune, atau
floating-point tanpa tipe menghasilkan konstanta complex tanpa tipe.
const ic = complex(0, c) // ic == 3.75i (konstanta complex tak bertipe) const iΘ = complex(0, Θ) // iΘ == 1i (tipe complex128)
Ekspresi konstanta selalu dievaluasi seperti yang tertulis; Nilai sementara dan konstanta itu sendiri bisa membutuhkan presisi yang lebih besar dari tipe yang didukung dalam bahasa. Berikut ini deklarasi yang legal:
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (konstanta integer tak bertipe) const Four int8 = Huge >> 98 // Four == 4 (tipe int8)
Penyebut dari pembagian sebuah konstanta atau operasi penyisaan haruslah nol:
3.14 / 0.0 // ilegal: pembagian dengan nol
Nilai dari konstanta bertipe haruslah selalu secara akurat direpresentasikan oleh nilai dari tipe konstanta. Ekspresi konstanta berikut adalah ilegal:
uint(-1) // -1 tidak dapat direpresentasikan sebagai uint int(3.14) // 3.14 tidak dapat direpresentasikan sebagai int int64(Huge) // 1267650600228229401496703205376 tidak dapat direpresentasikan sebagai int64 Four * 300 // operan 300 tidak dapat direpresentasikan sebagai int8 (tipe Four) Four * 100 // hasil 400 tidak dapat direpresentasikan sebagai int8 (tipe Four)
Mask yang digunakan oleh operator komplemen unary bitwise ^
cocok dengan
aturan untuk non-konstanta: semua mask adalah 1 untuk konstanta integer dan -1
untuk signed dan konstanta tak bertipe.
^1 // konstanta integer tak bertipe, sama dengan -2 uint8(^1) // ilegal: sama dengan uint8(-2), -2 tidak dapat direpresentasikan sebagai uint8 ^uint8(1) // konstanta uint8 bertipe, sama dengan 0xFF ^ uint8(1) = uint8(0xFE) int8(^1) // sama dengan int8(-2) ^int8(1) // sama dengan -1 ^ int8(1) = -2
Batasan implementasi: compiler bisa melakukan pembulatan saat menghitung ekspresi konstanta floating-point atau complex yang tak bertipe; lihat batasan implementasi dalam bagian konstanta. Pembulatan ini bisa menyebabkan ekspresi konstanta floating-point menjadi tidak valid dalam konteks integer, walaupun menjadi integral saat dihitung menggunakan presisi tanpa batas, dan sebaliknya.
Urutan evaluasi
Pada tingkat paket, inisiasi kebergantungan menentukan urutan evaluasi dari ekspresi inisiasi individu dalam deklarasi variabel. Sebaliknya, saat mengevaluasi operan dari sebuah ekspresi, penempatan, atau perintah kembalian, semua pemanggilan fungsi, pemanggilan method, dan operasi komunikasi dievaluasi secara leksikal terurut dari kiri-ke-kanan.
Contohnya, pada penempatan
y[f()], ok = g(h(), i()+x[j()], <-c), k()
pemanggilan fungsi dan komunikasi terjadi dengan urutan f(), h(), i(), j(),
←c
, dan k()
.
Namun, urutan dari even-even tersebut dibandingkan dengan evaluasi dan
pengindeksan dari x
dan evaluasi dari y
tidak dispesifikasikan.
a := 1 f := func() int { a++; return a } x := []int{a, f()} // x bisa jadi [1, 2] atau [2, 2]: urutan // evaluasi antara a dan f() tidak ditentukan. m := map[int]int{a: 1, a: 2} // m bisa jadi {2: 1} atau {2: 2}: urutan // evaluasi antara kunci dan nilai tidak ditentukan. n := map[int]int{a: f()} // n bisa jadi {2: 3} atau {3: 3}: urutan // evaluasi antara kunci dan nilai tidak ditentukan.
Pada tingkat paket, inisiasi dependensi menimpa aturan kiri-ke-kanan untuk ekspresi inisiasi individu, tetapi tidak untuk operan-operan dalam setiap ekspresi:
var a, b, c = f() + v(), g(), sqr(u()) + v() func f() int { return c } func g() int { return a } func sqr(x int) int { return x*x } // fungsi u dan v independen terhadap semua variabel dan fungsi lainnya.
Pemanggilan fungsi terjadi dengan urutan u()
, sqr()
, v()
, f()
, v()
,
dan g()
.
Operasi floating-point dalam sebuah ekspresi dievaluasi menurut asosiatif
dari operator.
Tanda kurung secara eksplisit mempengaruhi evaluasi dengan menimpa asosiatif
baku.
Dalam ekspresi x+(y+z)
penambahan y+z
dilakukan sebelum menambahkan
dengan x
.
Perintah
Perintah-perintah mengontrol eksekusi.
Statement = Declaration | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
Perintah penghenti
Sebuah perintah penghenti mencegah eksekusi dari semua perintah yang muncul setelah perintah penghenti dalam blok yang sama. Perintah-perintah berikut adalah penghenti:
-
Sebuah pemanggilan ke fungsi bawaan panic
-
Sebuah blok yang mana daftar perintahnya berakhir dengan perintah penghenti.
-
Sebuah perintah "if" yang mana:
-
cabang "else" ada, dan
-
kedua cabang adalah perintah penghenti.
-
-
Sebuah perintah "for" yang mana:
-
tidak ada perintah "break" yang mengacu ke perintah "for", dan
-
kondisi pengulangan tidak ada.
-
-
Sebuah perintah "switch" yang mana:
-
tidak ada perintah "break" yang mengacu ke perintah "switch",
-
ada pilihan "default", dan
-
daftar perintah di setiap pilihan, termasuk "default", berakhir dengan sebuah perintah penghenti, atau bisa saja memiliki label perintah "fallthrough".
-
-
Sebuah perintah "select" yang mana:
-
tidak ada perintah "break" yang mengacu ke perintah "select", dan
-
daftar perintah di setiap pilihan, termasuk "default" jika ada, berakhir dengan perintah penghenti.
-
-
Sebuah perintah berlabel yang melabelkan perintah penghentian.
Semua perintah selain itu bukanlah penghenti.
Sebuah daftar perintah berakhir dalam sebuah perintah penghenti jika daftar tersebut tidak kosong dan perintah terakhirnya adalah penghenti.
Perintah berlabel
Sebuah perintah berlabel bisa menjadi target dari sebuah perintah goto
,
break
, atau continue
.
LabeledStmt = Label ":" Statement . Label = identifier .
Error: log.Panic("error encountered")
Perintah ekspresi
Dengan pengecualian dari fungsi bawaan khusus, pemanggilan fungsi dan method dan operasi menerima dapat muncul dalam konteks perintah. Perintah-perintah tersebut bisa diberi tanda kurung.
ExpressionStmt = Expression .
Fungsi-fungsi bawaan berikut tidak dibolehkan dalam konteks perintah:
append cap complex imag len make new real unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
h(x+y) f.Close() <-ch (<-ch) len("foo") // ilegal jika len adalah fungsi bawaan
Perintah kirim
Sebuah perintah kirim mengirim sebuah nilai ke sebuah channel. Ekspresi channel haruslah bertipe channel, arah channel haruslah membolehkan operasi kirim, dan tipe dari nilai yang akan dikirim haruslah bisa disimpan ke tipe elemen dari channel.
SendStmt = Channel "<-" Expression . Channel = Expression .
Kedua ekspresi channel dan nilai dievaluasi sebelum komunikasi dimulai.
Komunikasi akan ditahan sampai pengirim dan dilanjutkan.
Sebuah pengiriman pada channel tanpa buffer dapat dilanjutkan jika sebuah
penerima telah sedia.
Sebuah pengiriman ke channel dengan buffer dapat dilanjutkan selama ada ruang
dalam buffer.
Sebuah pengiriman ke channel yang telah ditutup dilanjutkan dengan
mengakibatkan panik runtime.
Sebuah pengiriman terhadap channel yang nil
akan ditahan selamanya.
ch <- 3 // kirim nilai 3 ke channel ch
Perintah IncDec
Perintah "" dan "—" menambah dan mengurangi operannya dengan konstanta tanpa tipe 1. Seperti halnya penempatan, operan tersebut haruslah beralamat atau ekspresi indeks dari sebuah map.
IncDecStmt = Expression ( "++" | "--" ) .
perintah penempatan berikut secara semantik sama:
Perintah IncDec Assignment x++ x += 1 x-- x -= 1
Penempatan
Assignment = ExpressionList assign_op ExpressionList . assign_op = [ add_op | mul_op ] "=" .
Setiap operan sisi kiri haruslah beralamat, sebuah ekspresi indeks dari sebuah map, atau (hanya untuk penempatan =) pengidentifikasi kosong. Operan-operan bisa diberi tanda kurung.
x = 1 *p = f() a[i] = 23 (k) = <-ch // sama dengan: k = <-ch
Sebuah operasi penempatan x op= y yang mana op yaitu
operator aritmetika binari yaitu sama dengan
x = x
op (y)
namun hanya mengevaluasi x
sekali saja.
Konstruksi op= yaitu sebuah token tunggal.
Dalam operasi penempatan, kedua daftar ekspresi kiri dan kanan haruslah berisi
ekspresi tunggal dengan nilai, dan ekspresi bagian kiri haruslah bukan
pengidentifikasi kosong.
a[i] <<= 2 i &^= 1<<n
Penempatan tuple memberi setiap individu elemen dari sebuah operasi
multi-nilai ke sebuah daftar variabel.
Ada dua bentuk.
Bentuk pertama, operan bagian kanan yaitu ekspresi multi-nilai seperti
pemanggilan fungsi, operasi channel atau map,
atau sebuah tipe asersi.
Jumlah operan pada sisi kiri harus sesuai dengan jumlah nilai.
Misalnya, jika f
adalah sebuah fungsi yang mengembalikan dua nilai,
x, y = f()
menempatkan nilai pertama ke x
dan yang kedua ke y
.
Dalam bentuk kedua, jumlah operan pada sisi kiri haruslah sama dengan jumlah
ekspresi di sisi kanan, setiap-tiapnya haruslah bernilai tunggal, dan ekspresi
ke-n di sisi kanan ditempatkan ke operan ke-n di sisi kiri:
one, two, three = '一', '二', '三'
Pengidentifikasi kosong menyediakan cara untuk mengindahkan nilai di sisi kanan dalam sebuah penempatan:
_ = x // mengevaluasi x namun mengindahkan nilainya x, _ = f() // mengevaluasi f() namun mengindahkan nilai kembalian kedua
Penempatan diproses dengan dua fase. Pertama, operan dari ekspresi indeks dan pembalikan pointer (termasuk pembalikan pointer implisit dalam selector) pada sisi kiri dan ekspresi di sisi kanan dievaluasi dengan urutan yang biasa. Kedua, penempatan dilakukan dengan urutan kiri-ke-kanan.
a, b = b, a // tukar nilai a dan 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, kemudian x[0] = 2 (sehingga x[0] == 2) x[1], x[3] = 4, 5 // set x[1] = 4, kemudian panic saat menset x[3] = 5. type Point struct { x, y int } var p *Point x[2], p.x = 6, 7 // set x[2] = 6, kemudian panic menset p.x = 7 i = 2 x = []int{3, 5, 7} for i, x[i] = range x { // set i, x[2] = 0, x[0] break } // setelah pengulangan, i == 0 and x == []int{3, 5, 3}
Dalam penempatan, setiap nilai haruslah dapat ditempatkan ke tipe dari operan yang mana ia ditempatkan, dengan kasus-kasus khusus berikut:
-
Setiap nilai bertipe bisa ditempatkan ke pengidentifikasi kosong.
-
Jika konstanta tak bertipe ditempatkan ke sebuah variabel bertipe interface atau pengidentifikasi kosong, maka konstanta tersebut secara implisit dikonversi ke tipe defaultnya.
-
Jika nilai boolean tak bertipe ditempatkan ke sebuah variabel bertipe interface atau pengidentifikasi kosong, maka pertama ia dikonversi secara implisit ke tipe
bool
.
Perintah If
Perintah "if" menentukan kondisi eksekusi dari dua cabang menurut nilai dari
sebuah ekspresi boolean.
Jika evaluasi ekspresi bernilai true
, maka cabang "if" yang dieksekusi,
sebaliknya, jika ada, cabang "else" yang dieksekusi.
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max { x = max }
Ekspresi tersebut bisa didahului oleh perintah sederhana, yang dieksekusi sebelum ekspresi dievaluasi.
if x := f(); x < y { return x } else if x > z { return z } else { return y }
Perintah switch
Perintah "switch" menyediakan eksekusi multi-cabang. Sebuah ekspresi atau spesifikasi tipe dibandingkan dengan setiap "case" dalam "switch" untuk menentukan cabang mana yang akan dieksekusi.
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
Ada dua bentuk: switch dengan ekspresi dan switch dengan tipe. Pada switch dengan ekspresi, bagian "case" berisi ekspresi yang dibandingkan dengan nilai dari ekspresi switch. Pada switch dengan tipe, bagian "case" berisi tipe-tipe yang dibandingkan dengan tipe dari ekspresi switch. Ekspresi switch dievaluasi sekali dalam sebuah perintah switch.
Switch ekspresi
Dalam sebuah switch ekspresi, ekspresi switch dievaluasi dan ekspresi "case",
yang tidak harus konstan, dievaluasi dari kiri-ke-kanan dan atas-ke-bawah;
"case" yang pertama kali bernilai sama dengan ekspresi "switch" memulai
eksekusi dari perintah-perintah yang berasosiasi dengan "case";
"case" yang lain akan dilewati.
Jika tidak ada "case" yang sama dan ada sebuah case "default", maka perintah
pada "default" lah yang akan dieksekusi.
Paling banyak hanya ada satu case "default" dan ia dapat ditulis dibagian
manapun dalam perintah "switch".
Ekspresi "switch" yang kosong sama dengan nilai boolean true
.
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" .
Jika ekspresi "switch" bernilai konstanta tanpa tipe, maka pertama kali secara
implisit dikonversi ke tipe default-nya;
jika nilainya ada boolean tanpa tipe, maka ia secara implisit dikonversi ke
tipe bool
.
Nilai nil
tidak bisa digunakan sebagai ekpsresi switch.
Jika sebuah ekspresi "case" tak bertipe, maka ia dikonversi secara implisit ke
tipe dari ekspresi "switch".
Untuk setiap ekspresi "case" x
dan nilai t
dari ekspresi "switch",
x == t
haruslah pembandingan yang valid.
Dengan kata lain, ekspresi "switch" diperlakukan untuk mendeklarasikan dan
menginisiasi variabel sementara t
tanpa tipe eksplisit;
maka nilai dari t
dibandingkan dengan setiap ekspresi "case" diuji untuk
kesamaan.
Dalam sebuah "case" atau klausa "default", perintah terakhir bisa berupa perintah fallthrough (bisa berlabel) untuk mengindikasikan bahwa kontrol harus mengalir dari akhir klausa ke perintah pertama dari klausa selanjutnya. Selain kontrol mengalir ke akhir perintah "switch". Perintah "fallthrough" bisa muncul sebagai perintah terakhir kecuali pada klausa terakhir dari sebuah ekspresi "switch".
Ekspresi "switch" bisa diawali dengan perintah sederhana, yang dieksekusi sebelum ekspresi dievaluasi.
switch tag { default: s3() case 0, 1, 2, 3: s1() case 4, 5, 6, 7: s2() } switch x := f(); { // tanpa ada ekspresi switch berarti "true" case x < 0: return -x default: return x } switch { case x < y: f1() case x < z: f2() case x == 4: f3() }
Batasan implementasi: compiler bisa saja tidak membolehkan beberapa ekspresi "case" yang bernilai konstanta yang sama. Misalnya, compiler yang sekarang tidak membolehkan konstanta integer, floating-point, atau string yang duplikat dalam ekspresi "case".
Switch bertipe
Switch bertipe membandingkan tipe bukannya nilai.
Perilakunya mirip dengan switch berekspresi.
Ia ditandai dengan sebuah ekspresi switch yang memiliki bentuk dari sebuah
asersi tipe
menggunakan kata kunci type
bukan tipe sebenarnya:
switch x.(type) { // cases }
Case kemudian dibandingkan dengan tipe sebenarnya T
dibandingkan dengan tipe
dinamis dari ekspresi x
.
Seperti pada asersi tipe, x
haruslah bertipe interface,
dan setiap tipe T
dalam setiap "case" harus mengimplementasikan tipe dari
x
.
Tipe-tipe yang terdaftar dalam "case"-"case" dari switch bertipe semuanya
harus berbeda.
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" . TypeList = Type { "," Type } .
TypeSwitchGuard bisa mengikutkan sebuah deklarasi variabel singkat. Bila bentuk tersebut digunakan, variabel dideklarasikan di akhir dari TypeSwitchCase dalam blok implisit dari setiap klausa. Dalam klausa-klausa dengan sebuah "case" yang berisi daftar hanya satu tipe, variabel memiliki tipe tersebut, sebaliknya, variabel memiliki tipe dari ekspresi dalam TypeSwitchGuard.
Selain tipe, sebuah "case" bisa menggunakan pengidentifikasi nil
;
"case" tersebut dipilih saat ekspresi dalam TypeSwitchGuard adalah nilai
interface bernilai nil
.
Setidaknya hanya ada satu "case" dengan nil
.
Diberikan sebuah ekspresi x
bertipe interface{}
, switch bertipe berikut:
switch i := x.(type) { case nil: printString("x yaitu nil") // tipe i yaitu tipe dari x (interface{}) case int: printInt(i) // tipe i yaitu int case float64: printFloat64(i) // tipe i yaitu float64 case func(int) float64: printFunction(i) // tipe i yaitu func(int) float64 case bool, string: printString("type yaitu bool atau string") // tipe i yaitu tipe x (interface{}) default: printString("type tidak diketahui") // tipe i yaitu tipe x (interface{}) }
dapat ditulis ulang:
v := x // x dievaluasi sekali if v == nil { i := v // tipe i yaitu x (interface{}) printString("x yaitu nil") } else if i, isInt := v.(int); isInt { printInt(i) // tipe i yaitu int } else if i, isFloat64 := v.(float64); isFloat64 { printFloat64(i) // tipe i yaitu float64 } else if i, isFunc := v.(func(int) float64); isFunc { printFunction(i) // tipe i yaitu func(int) float64 } else { _, isBool := v.(bool) _, isString := v.(string) if isBool || isString { i := v // tipe i yaitu tipe x (interface{}) printString("tipe yaitu bool atau string") } else { i := v // tipe i yaitu tipe x (interface{}) printString("tipe tidak diketahui") } }
Penjaga switch bertipe bisa diawali dengan perintah sederhana, yang dieksekusi sebelum penjaga dievaluasi.
Perintah "fallthrough" tidak dibolehkan dalam switch bertipe.
Perintah for
Sebuah perintah "for" menentukan eksekusi berulang dari sebuah blok. Ada tiga bentuk: iterasi bisa dikontrol oleh kondisi tunggal, sebuah klausa "for", atau sebuah klausa "range".
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression .
Perintah for dengan kondisi tunggal
Dalam bentuk sederhana, sebuah perintah "for" menentukan eksekusi berulang
dari sebuah blok selama kondisi boolean bernilai true.
Kondisi tersebut dievaluasi sebelum tiap iterasi.
Jika kondisi tidak ada, maka ia sama dengan nilai boolean true
.
for a < b { a *= 2 }
Perintah For dengan klausa for
Sebuah perintah "for" dengan ForClause juga dikontrol oleh kondisinya, namun sebagai tambahan bisa menspesifikasikan sebuah perintah init dan sebuah post, seperti sebuah penempatan, perintah peningkatan dan pengurangan. Perintah init bisa berupa sebuah deklarasi variabel singkat, namun perintah post tidak. Variabel yang dideklarasikan oleh perintah init digunakan pada setiap iterasi.
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] . InitStmt = SimpleStmt . PostStmt = SimpleStmt .
for i := 0; i < 10; i++ { f(i) }
Jika tidak kosong, perintah init dieksekusi sekali sebelum mengevaluasi
kondisi untuk iterasi pertama;
perintah post dieksekusi setelah setiap eksekusi dari blok (dan hanya bila
blok dieksekusi).
Elemen dari ForClause bisa kosong namun titik-koma dibutuhkan
kecuali bila hanya ada sebuah kondisi.
Jika kondisi tidak ada, maka ia sama dengan nilai boolean true
.
for cond { S() } is the same as for ; cond ; { S() } for { S() } is the same as for true { S() }
Perintah For dengan klausa range
Perintah "for" dengan klausa "range" mengiterasi semua isi dari array, slice, string atau map, atau nilai yang diterima pada channel. Untuk setiap isi ia akan menempatkan nilai iterasi ke variabel iterasi jika ada dan kemudian mengeksekusi blok.
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
Ekspresi sebelah kanan dalam klausa "range" disebut ekpsresi range, yang bisa saja sebuah array, pointer ke array, slice, string, map, atau channel yang membolehkan operasi menerima. Seperti halnya pada penempatan, jika ada maka operan sebelah kiri harusalah beralamat atau berupa ekspresi indeks map; ia menyatakan variabel iterasi. Jika ekspresi range adalah sebuah channel, maka hanya satu variabel iterasi yang dibolehkan, selain itu bisa jadi dua variabel. Jika variabel iterasi terakhir adalah pengidentifikasi kosong, klausa range sama dengan klausa tanpa pengidentifikasi tersebut.
Ekspresi range x
dievaluasi sekali sebelum memulai pengulangan, dengan satu
pengecualian: jika paling tidak satu variabel iterasi ada dan len(x)
adalah
konstanta, ekspresi range tidak dievaluasi.
Pemanggilan fungsi pada bagian kiri dievaluasi sekali per iterasi. Untuk setiap iterasi, nilai iterasi dihasilkan sebagai berikut jika variabel iterasi ada:
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
-
Untuk sebuah array, pointer ke array, atau slice bernilai
a
, nilai iterasi indeks dihasilkan secara meningkat, dimulai dari elemen indeks 0. Jika satu variabel iterasi ada, pengulangan range menghasilkan nilai iterasi dari 0 sampailen(a) - 1
dan tidak mengindeks pada array atau slice itu sendiri. Untuk slice yangnil
, jumlah iterasi adalah 0. -
Untuk nilai string, klausa "range" mengiterasi poin Unicode dalam string dimulai dari byte pada indeks ke 0. Pada iterasi yang sukses, nilai indeks yaitu indeks dari byte pertama dari kode poin UTF-8 dalam string, dan nilai kedua, bertipe
rune
, akan bernilai kode poin yang berkorespondensi. Jika iterasi menemui urutan UTF-8 yang tidak valid, nilai kedua yaitu0xFFFD
, yaitu karakter pengganti Unicode, dan iterasi selanjutnya akan meloncati sebuah byte dalam string. -
Urutan iterasi terhadap map tidak ditentukan dan tidak dijamin selalu sama dari satu iterasi dengan iterasi selanjutnya. Jika isi map yang belum diiterasi dihapus selama iterasi, nilai iterasi yang berkorespondensi tidak akan dihasilkan. Jika sebuah isi map dibuat selama iterasi, entri tersebut bisa saja muncul selama iterasi atau bisa saja dilewati. Pilihannya bisa beragam untuk setiap entri yang dibuat dan dari satu iterasi ke iterasi selanjutnya. Jika map adalah
nil
, jumlah iterasi adalah 0. -
Untuk channel, nilai iterasi yang dihasilkan adalah nilai yang dikirim ke channel sampai channel ditutup. Jika channel adalah
nil
, ekspresi range akan diblok selamanya.
Nilai iterasi ditempatkan ke variabel iterasi seperti pada perintah penempatan.
Variabel iterasi bisa dideklarasikan oleh klausa "range" menggunakan sebuah
bentuk deklarasi variabel singkat (:=
).
Dalam kasus ini tipenya diset ke tipe dari nilai iterasi dan
skop nya adalah blok dari perintah "for";
mereka dipergunakan dalam setiap iterasi.
Jika variabel iterasi dideklarasikan di luar perintah "for", setelah eksekusi
nilai mereka adalah nilai dari iterasi terakhir.
var testdata *struct { a *[7]int } for i, _ := range testdata.a { // testdata.a tidak pernah dievaluasi; len(testdata.a) adalah // konstanta // i memiliki rentang 0 to 6 f(i) } var a [10]string for i, s := range a { // tipe dari i yaitu int // tipe dari s yaitu string // s == a[i] g(i, s) } var key string var val interface {} // tipe elemen dari m ditempatkan ke 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 == kunci map terakhir yang ditemukan dalam iterasi // val == map[key] var ch chan Work = producer() for w := range ch { doWork(w) } // kosongkan channel for range ch {}
Perintah go
Sebuah perintah "go" memulai eksekusi dari pemanggilan fungsi sebagai thread konkuren yang berdiri sendiri, atau disebut _goroutine, dalam ruang alamat yang sama.
GoStmt = "go" Expression .
Expression haruslah sebuah pemanggilan fungsi atau method; tidak bisa dalam tanda kurung kurawal. Pemanggilan fungsi bawaan dibatasi untuk perintah ekspresi.
Nilai fungsi dan parameter dievaluasi seperti biasa dalam goroutine yang dipanggil, tetapi tidak seperti pemanggilan biasa, eksekusi program tidak menunggu fungsi yang dipanggil untuk selesai. Namun, fungsi tersebut mulai dieksekusi secara independen dalam sebuah goroutine yang baru. Saat fungsi selesai, goroutine-nya juga selesai. Jika fungsi memiliki nilai kembalian, nilainya akan diindahkan saat fungsi selesai.
go Server() go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Perintah select
Sebuah perintah "select" memilih sekumpulan operasi pengiriman atau penerimaan yang akan diproses. Ia mirip dengan perintah "switch" namun dengan semua "case" mengacu pada operasi komunikasi.
SelectStmt = "select" "{" { CommClause } "}" . CommClause = CommCase ":" StatementList . CommCase = "case" ( SendStmt | RecvStmt ) | "default" . RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr . RecvExpr = Expression .
Sebuah "case" dengan RecvStmt bisa menempatkan hasil dari RecvExpr ke satu atau dua variabel, yang bisa dideklarasikan menggunakan deklarasi variabel singkat. RecvExpr haruslah sebuah operasi menerima (bisa pakai tanda kurung). Hanya boleh ada satu case "default" dan ia bisa muncul di mana pun dalam daftar case.
Eksekusi dari perintah "select" diproses dalam beberapa langkah:
-
Untuk semua perintah case, operan channel dari operasi menerima dan ekspresi sebelah-kanan dan channel dari perintah pengiriman dievaluasi sekali, berurutan, saat memasuki perintah "select". Hasilnya yaitu sekumpulan channel yang akan menerima atau mengirim, dan nilai korespondensi yang akan dikirim. Efek samping dari evaluasi tersebut akan terjadi tanpa memperhatikan operasi komunikasi yang mana terpilih untuk diproses. Ekspresi di sebelah-kiri dari RecvStmt dengan deklarasi variabel singkat atau penempatan belum dievaluasi.
-
Jika satu atau lebih komunikasi dapat diproses, salah satu dipilih secara acak. Sebaliknya, jika ada case "default", maka "default" lah yang dipilih. Jika tidak ada "default", perintah "select" menahan sampai paling tidak satu komunikasi dapat diproses.
-
Kecuali bisa case yang terpilih adalah "default", operasi komunikasi yang diproses dieksekusi.
-
Jika case yang terpilih adalah sebuah RecvStmt dengan deklarasi variabel singkat atau sebuah penempatan, ekspresi sebelah kiri dievaluasi dan nilai penerima ditempatkan.
-
Daftar perintah dari case terpilih dieksekusi.
Secara komunikasi pada channel nil
tidak pernah diproses, sebuah "select"
dengan hanya channel-channel yang nil
tanpa case "default" akan ditahan
selamanya.
var a []int var c, c1, c2, c3, c4 chan int var i1, i2 int select { case i1 = <-c1: print("menerima ", i1, " dari c1\n") case c2 <- i2: print("mengirim ", i2, " ke c2\n") case i3, ok := (<-c3): // sama dengan: i3, ok := <-c3 if ok { print("menerima ", i3, " from c3\n") } else { print("c3 ditutup\n") } case a[f()] = <-c4: // sama dengan: // case t := <-c4 // a[f()] = t default: print("tidak ada komunikasi\n") } for { // kirim seurutan bit yang random ke c select { case c <- 0: // catatan: tidak ada perintah, tidak ada fallthrough. case c <- 1: } } select {} // ditahan selamanya
Perintah return
Sebuah perintah "return" dalam sebuah fungsi F
menghentikan eksekusi dari
F
, dan mengembalikan satu atau lebih nilai.
Setiap fungsi yang di-defer oleh F
dieksekusi sebelum
F
kembali ke pemanggilnya.
ReturnStmt = "return" [ ExpressionList ] .
Dalam sebuah fungsi tanpa tipe kembalian, perintah "return" tidak boleh menspesifikasikan nilai kembalian.
func noResult() { return }
Ada tiga cara untuk mengembalikan nilai dari sebuah fungsi dengan tipe kembalian:
-
Nilai kembalian bisa secara eksplisit didaftarkan dalam perintah "return". Setiap ekspresi haruslah bernilai tunggal dan dapat ditempatkan ke elemen yang berkorespondensi dengan tipe kembalian dari fungsi.
func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 }
-
Daftar ekspresi dalam perintah "return" bisa berupa sebuah pemanggilan ke fungsi dengan banyak kembalian. Efeknya adalah setiap nilai kembalian dari fungsi tersebut ditempatkan ke variabel sementara dengan tipe yang sama, diikuti dengan perintah "return" mendaftar variabel-variabel tersebut, yang mana aturan dari kasus sebelumnya berlaku.
func complexF2() (re float64, im float64) { return complexF1() }
-
Daftar ekspresi bisa kosong jika tipe kembalian fungsi memiliki nama untuk parameter kembalian. Parameter kembalian berlaku sebagai variabel lokal dan fungsi bisa menempatkan nilai kedalamnya bila diperlukan. Perintah "return" mengembalikan nilai dari variabel tersebut.
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 }
Bagaimanapun cara mereka dideklarasikan, semua nilai kembalian diinisiasi dengan nilai kosong untuk tipe mereka saat memasuki fungsi. Sebuah perintah "return" yang menentukan kembalian menset parameter-parameter kembalian sebelum fungsi-fungsi yang di-defer dieksekusi.
Batasan implementasi: compiler bisa tidak membolehkan daftar eksepresi yang kosong dalam sebuah perintah "return" jika entitas yang berbeda (konstanta, tipe, atau variabel) dengan nama yang sama dengan parameter kembalian berada dalam skop ditempat yang di "return".
func f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return // invalid return statement: err is shadowed } return }
Perintah break
Sebuah perintah "break" menghentikan eksekusi dari perintah "for", "switch", "select" dalam fungsi yang sama.
BreakStmt = "break" [ Label ] .
Jika diberi label, ia haruslah yang mengurung perintah "for", "switch", "select", dan itulah yang eksekusinya akan dihentikan.
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 } } }
Perintah continue
Sebuah perintah "continue" memulai iterasi selanjutnya dari pengulangan "for" di posisi perintah tersebut. Pengulangan "for" haruslah dalam fungsi yang sama.
ContinueStmt = "continue" [ Label ] .
Jika ada label, ia haruslah yang mengurung perintah "for", yang disanalah eksekusi akan dilanjutkan.
RowLoop: for y, row := range rows { for x, data := range row { if data == endOfRow { continue RowLoop } row[x] = data + bias(x, y) } }
Perintah goto
Sebuah perintah "goto" memindahkan kontrol ke perintah dengan label yang berkorespondensi dalam fungsi yang sama.
GotoStmt = "goto" Label .
goto error
Mengeksekusi perintah "goto" tidak boleh menyebabkan variabel menjadi skop yang belum ada dalam skop saat goto terjadi. Misalnya, contoh berikut
goto L // BAD v := 3 L:
adalah eror karena saat meloncat ke label L
melewati terbuatnya v
.
Sebuah perintah "goto" di luar blok tidak bisa meloncat ke label di dalam blok tersebut. Misalnya, contoh berikut:
if n%2 == 1 { goto L1 } for n > 0 { f() n-- L1: f() n-- }
adalah eror karena label L1
berada dalam blok perintah "for" namun goto
tidak dalam blok yang sama.
Perintah fallthrough
Sebuah perintah "fallthrough" memindahkan kontrol ke perintah pertama dari klausa "case" selanjutnya dalam sebuah ekspresi perintah "switch". Ia hanya bisa digunakan sebagai perintah terakhir dalam klausa tersebut.
FallthroughStmt = "fallthrough" .
Perintah defer
Sebuah perintah "defer" memanggil sebuah fungsi yang eksekusinya ditunda sampai fungsi yang mengurungnya selesai, baik karena fungsi tersebut mengeksekusi perintah "return", mencapai akhir dari badan fungsi, atau karena goroutine tersebut panik.
DeferStmt = "defer" Expression .
Si Expression haruslah sebuah pemanggilan fungsi atau method; tidak bisa di dalam tanda kurung. Pemanggilan fungsi bawaan terbatas untuk perintah ekspresi.
Setiap kali sebuah perintah "defer" dieksekusi, nilai dan parameter fungsi
dievaluasi secara normal dan disimpan namun fungsi yang sebenarnya
tidak dipanggil.
Namun, fungsi yang ditunda dipanggil langsung sebelum fungsi kembali, dengan
urutan terbalik dari saat mereka di-defer.
Artinya, jika fungsi selesai lewat perintah "return",
fungsi yang didefer dieksekusi setelah parameter kembalian diset oleh
perintah "return" tersebut namun sebelum fungsi kembali ke pemanggilnya.
Jika sebuah fungsi yang di-defer dievaluasi jadi nil
, maka eksekusi akan
panik saat fungsi dipanggil, bukan saat perintah "defer"
dieksekusi.
Misalnya, jika fungsi yang di defer adalah sebuah literal fungsi dan fungsi yang mengurungnya memiliki parameter kembalian bernama yang berada dalam skop dari literal fungsi, maka fungsi yang di-defer bisa mengakses dan mengubah parameter kembalian sebelum mereka dikembalikan. Jika fungsi yang ditunda memiliki nilai kembalian, mereka diindahkan saat fungsi selesai. (Lihat juga bagian tentang penanganan panic.
lock(l) defer unlock(l) // unlock terjadi sebelum fungsi selesai // cetak 3 2 1 0 sebelum fungsi selesai for i := 0; i <= 3; i++ { defer fmt.Print(i) } // f mengembalikan 42 func f() (result int) { defer func() { // result diakses setelah ia di set ke 6 oleh perintah return. result *= 7 }() return 6 }
Fungsi-fungsi bawaan
Fungsi-fungsi bawaan adalah pradeklarasi. Mereka dipanggil seperti halnya fungsi lainnya namun beberapa menerima tipe bukan ekspresi sebagai argumen pertamanya.
Fungsi-fungsi bawaan tidak memiliki tipe Go standar, jadi mereka hanya dapat muncul dalam ekspresi pemanggilan; mereka tidak bisa digunakan sebagai nilai fungsi.
Close
Untuk sebuah channel c
, fungsi bawaan close(c)
mencatat bahwa tidak ada
lagi nilai yang dapat dikirim ke channel.
Fungsi close
akan eror jika c
adalah channel yang bersifat menerima saja.
Mengirim ke atau menutup channel yang telah ditutup menyebabkan
panik run-time.
Menutup channel yang nil
juga menyebabkan
panik run-time.
Setelah memanggil close
, dan setelah nilai yang dikirim sebelumnya telah
diterima, operasi menerima akan mengembalikan nilai kosong dari tipe channel
tersebut tanpa ditangguhkan.
operasi menerima dengan beragam nilai mengembalikan
nilai yang diterima berikut dengan indikasi tentang apakah channel telah
ditutup.
Kapasitas dan panjang
Fungsi bawaan len
dan cap
menerima argumen berupa tipe dan mengembalikan
nilai bertipe int
.
Implementasi menjamin bahwa kembalian selalu masuk dengan int
.
Pemanggilan Tipe argumen Kembalian len(s) tipe string panjang string dalam sekumpulan byte [n]T, *[n]T panjang array (== n) []T panjang slice map[K]T panjang map (jumlah kunci terdefinisi) chan T jumlah elemen antrean dalam buffer channel cap(s) [n]T, *[n]T panjang array (== n) []T kapasitas slice chan T kapasitas buffer channel buffer
Kapasitas dari slice yaitu jumlah elemen yang telah dialokasi dalam array dibelakangnya. Relasi berikut selalu berlaku kapanpun:
0 <= len(s) <= cap(s)
Panjang dari slice, map atau channel yang nil
selalu 0.
Kapasitas dari slice atau channel yang nil
selalu 0.
Ekspresi len(s)
adalah konstanta jika s
adalah konstanta
string.
Ekspresi len(s)
dan cap(s)
adalah konstanta jika tipe dari s
adalah
array atau pointer ke array dan ekspresi s
tidak mengandung
penerimaan channel
atau pemanggilan fungsi (yang bukan konstanta);
dalam kasus tersebut s
tidak dievaluasi.
Sebaliknya, pemanggilan len
dan cap
bukanlah konstanta dan s
tidak
dievaluasi.
const ( c1 = imag(2i) // imag(2i) = 2.0 adalah konstanta c2 = len([10]float64{2}) // [10]float64{2} tidak mengandung // pemanggilan fungsi c3 = len([10]float64{c1}) // [10]float64{c1} tidak mengandung // pemanggilan fungsi c4 = len([10]float64{imag(2i)}) // imag(2i) adalah konstanta dan // tidak ada pemanggilan fungsi terjadi c5 = len([10]float64{imag(z)}) // invalid: imag(z) adalah // pemanggilan fungsi (bukan // konstanta) ) var z complex128
Alokasi
Fungsi bawaan new
menerima sebuah tipe T
, mengalokasikan penyimpanan untuk
sebuah variabel dengan tipe tersebut pada saat run-time, dan
mengembalikan sebuah nilai bertipe *T
yang menunjuk
kepadanya.
Variabel tersebut diinisiasi seperti yang dijelaskan dalam bagian
nilai awal.
new(T)
Misalnya
type S struct { a int; b float64 } new(S)
mengalokasikan penyimpanan untuk sebuah variabel bertipe S
, menginisiasinya
(a=0, b=0.0), dan mengembalikan sebuah nilai bertipe *S
yang berisi alamat
dari alokasi.
Membuat slice, map, dan channel
Fungsi bawaan make
menerima sebuah tipe T
, yang haruslah bertipe slice,
map, atau channel;
diikuti oleh daftar ekspresi berdasarkan tipe.
Ia mengembalikan sebuah nilai bertipe T
(bukan *T
).
Memory diinisiasi seperti yang dijelaskan dalam bagian
nilai awal.
Pemanggilan Tipe T Kembalian make(T, n) slice slice bertipe T dengan panjang n dan kapasitas n make(T, n, m) slice slice bertipe T dengan panjang n dan kapasitas m make(T) map map bertipe T make(T, n) map map bertipe T dengan alokasi mendekati n elemen make(T) channel channel tanpa buffer bertipe T make(T, n) channel channel dengan buffer bertipe T, buffer berukuran n
Setiap argumen ukuran n
dan m
haruslah bertipe integer atau
konstanta tak bertipe.
Argumen yang berupa konstanta haruslah tidak negatif dan
dapat direpresentasikan oleh nilai bertipe int
;
jika ia berupa konstanta tak bertipe ia akan bertipe int
.
Jika n
dan m
diberikan dan keduanya adalah konstanta, maka n
haruslah
besar dari m
.
Jika n
negatif atau besar dari m
pada saat run-time, maka
panik run-time akan terjadi.
s := make([]int, 10, 100) // slice dengan len(s) == 10, cap(s) == 100 s := make([]int, 1e3) // slice dengan len(s) == cap(s) == 1000 s := make([]int, 1<<63) // ilegal: len(s) tidak dapat // direpresentasikan oleh nilai bertipe int s := make([]int, 10, 0) // ilegal: len(s) > cap(s) c := make(chan int, 10) // channel dengan buffer berukuran 10 m := make(map[string]int, 100) // map dengan ruang awal sekitar 100 elemen
Memanggil make
dengan tipe map dan ukuran n
akan membuat sebuah map dengan
ruang awal yang dapat menyimpan n
map elemen.
Perilaku aslinya bergantung pada implementasi.
Menambah dan menyalin slice
Fungsi bawaan append
dan copy
membantu dalam operasi slice.
Untuk kedua fungsi, hasilnya selalu independen walaupun memory yang diacu oleh
argumen bertindihan.
Fungsi variadik append
menambahkan kosong atau lebih
nilai x
ke s
yang bertipe S
, yang haruslah bertipe slice, dan
mengembalikan hasil slice, yang juga bertipe S
.
Nilai x
dikirim sebagai parameter bertipe …T
yang mana T
adalah
tipe elemen dari S
dan
aturan pengiriman parameter
berlaku.
Sebagai kasus khusus, append
juga menerima argumen pertama yang dapat
ditempatkan ke tipe []byte
dengan argumen kedua bertipe string diikuti
dengan …
.
Bentuk ini menambahkan sejumlah byte ke string.
append(s S, x ...T) S // T adalah elemen bertipe S
Jika kapasitas s
tidak cukup besar untuk menampung nilai-nilai tambahan,
append
mengalokasikan sebuah array dasar yang baru yang lebih besar yang
dapat menampung slice elemen yang alam dan nilai yang baru.
Sebaliknya, append
menggunakan array dasar yang sama.
s0 := []int{0, 0} s1 := append(s0, 2) // menambahkan sebuah element // s1 == []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // menambahkan beberapa element // s2 == []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // menambahkan slice // s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // menambahkan slice yang tumpang tindih // s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // menambahkan isi string b == []byte{'b', 'a', 'r' }
Fungsi copy
menyalin elemen slice dari sumber src
ke tujuan dst
dan
mengembalikan jumlah elemen yang disalin.
Kedua argumen haruslah memiliki elemen yang
identik
bertipe T
dan harus
dapat ditempatkan
ke sebuah slice bertipe []T
.
Jumlah elemen yang disalin yaitu minium dari len(src)
dan len(dst)
.
Sebagai kasus khusus, copy
juga menerima argumen tujuan yang dapat
ditempatkan ke tipe []byte
dengan argumen sumber bertipe string.
Bentuk ini menyalin byte dari string ke slice byte.
copy(dst, src []T) int copy(dst []byte, src string) int
Contoh:
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 == []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5} n3 := copy(b, "Hello, World!") // n3 == 5, b == []byte("Hello")
Penghapusan elemen pada map
Fungsi bawaan delete
menghapus elemen dengan kunci k
dari sebuah
map m
.
Tipe dari k
haruslah dapat
dapat ditempatkan ke tipe kunci dari m
.
delete(m, k) // hapus elemen m[k] dari map m
Jika map m
adalah nil
atau elemen m[k]
tidak ada, delete
adalah
operasi kosong.
Manipulasi bilangan complex
Tiga fungsi menyusun dan mengurai bilangan complex.
Fungsi bawaan complex
membentuk sebuah nilai complex dari bagian real dan
imajiner floating-point, sementara real
dan imag
mengekstrak bagian real
dan imajiner dari sebuah nilai complex.
complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT
Tipe dari argumen dan nilai kembalian saling berhubungan.
Untuk complex
, kedua argumen haruslah tipe floating-point yang sama dan
tipe kembalian yaitu salah satu tipe complex berikut: complex64
untuk
argumen bertipe float32
, dan complex128
untuk argumen bertipe float64
.
Jika salah satu argumen adalah konstanta tak bertipe, maka ia secara implisit
dikonversi ke tipe dari argumen lainnya.
Jika kedua argumen dievaluasi ke konstanta tak bertipe, maka keduanya haruslah
bilangan non-complex atau bagian imajinernya haruslah nol, dan nilai kembalian
dari fungsi yaitu konstanta complex tak bertipe.
Untuk real
dan imag
, argumen haruslah bertipe complex, dan tipe kembalian
berkorespondensi dengan tipe floating-point: float32
untuk argumen
complex64
, dan float64
untuk argumen complex128
.
Jika argumen dievaluasi menjadi konstanta tak bertipe, maka ia haruslah berupa
angka, dan nilai kembalian dari fungsi yaitu konstanta floating-point tak
bertipe.
Fungsi real
dan imag
keduanya membentuk kebalikan dari complex
, sehingga
untuk nilai z
dari tipe complex Z, z == Z(complex(real(z), imag(z)))
.
Jika operan dari fungsi-fungsi tersebut semuanya adalah konstanta, maka nilai kembaliannya adalah konstanta.
var a = complex(2, -2) // complex128 const b = complex(1.0, -1.4) // konstanta complex tak bertipe 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = complex(5, -x) // complex64 var s int = complex(1, 0) // konstanta complex tak bertipe 1 + 0i // dapat dikonversi ke int _ = complex(1, 2<<s) // ilegal: 2 diasumsikan bertipe // floating-point, tidak bisa di- shift var rl = real(c64) // float32 var im = imag(a) // float64 const c = imag(b) // konstanta tak bertipe -1.4 _ = imag(3 << s) // ilegal: 3 diasumsikan bertipe complex, // tidak bisa di-shift
Menangani panik
Dua fungsi bawaan, panic
dan recover
, membantu dalam melaporkan dan
menangani panik run-time dan kondisi eror yang
terdefinisi pada program.
func panic(interface{}) func recover() interface{}
Saat mengeksekusi sebuah fungsi F
, pemanggilan panic
yang eksplisit atau
panik run-time menghentikan eksekusi dari F
.
Fungsi apa pun yang ditunda oleh F
kemudian
dieksekusi.
Selanjutnya, fungsi apa pun yang ditunda oleh pemanggil F
dijalankan, dan
begitu seterusnya sampai fungsi paling atas dalam goroutine.
Di titik tersebut, program dihentikan dan kondisi eror dilaporkan, termasuk
nilai argumen ke panic
.
Urutan penghentian ini disebut dengan panicking.
panic(42) panic("unreachable") panic(Error("cannot parse"))
Fungsi recover
membolehkan sebuah program mengatur perilaku dari goroutine
yang panik.
Misalkan sebuah fungsi G
menunda fungsi D
yang memanggil recover
dan
panik terjadi dalam sebuah fungsi pada goroutine yang sama yang mana G
dieksekusi.
Saat menjalankan fungsi-fungsi yang ditunda mencapai D
, nilai kembalian dari
pemanggilan recover
di dalam D
akan berisi argumen yang dikirim pada saat
panic
dipanggil.
Jika D
kembali secara normal, tanpa memulai panic
yang baru, maka urutan
panicking berhenti.
Dalam kasus ini, status dari fungsi yang dipanggil antara G
dan pemanggilan
panic
diindahkan, dan eksekusi normal dilanjutkan.
Fungsi apa pun yang ditunda oleh G
sebelum D
maka kemudian dijalankan dan
eksekusi dari G
dihentikan dengan mengembalikan ke pemanggilnya.
Nilai kembalian dari recover
adalah nil
jika salah satu kondisi berikut
berlaku:
-
argumen dari
panic
adalahnil
; -
goroutine tersebut tidak panik;
-
recover
tidak secara langsung dipanggil oleh fungsi yang di-defer.
Fungsi protect
dalam contoh di bawah memanggil fungsi g
dan melindungi
pemanggil dari panik run-time yang disebabkan oleh g
.
func protect(g func()) { defer func() { log.Println("done") // Println dieksekusi secara normal // bahkan jika ada panic if x := recover(); x != nil { log.Printf("run time panic: %v", x) } }() log.Println("start") g() }
Bootstrapping
Implementasi yang sekarang menyediakan beberapa fungsi bawaan yang berguna selama bootstrapping. Fungsi-fungsi tersebut didokumentasikan demi kelengkapan namun tidak dijamin akan tetap ada dalam bahasa Go. Mereka tidak memiliki nilai kembalian.
Fungsi Perilaku print mencetak semua argumen; format dari argumen bergantung pada implementasi println seperti print namun mencetak spasi antara argumen dan baris baru di akhir
Batasan implementasi: print
dan println
tidak perlu menerima tipe argumen
yang beragam, namun harus mendukung pencetakan tipe boolean,
numerik, dan string.
Paket
Program Go dibentuk dengan mengaitkan paket-paket. Sebuah paket dibentuk dari satu atau lebih berkas sumber yang berisi deklarasi konstanta, tipe, variabel, dan fungsi yang dimiliki oleh paket tersebut yang mana dapat diakses di semua berkas dari paket yang sama. Elemen-elemen tersebut bisa diekspor dan digunakan di paket lainnya.
Organisasi berkas sumber
Setiap berkas sumber terdiri dari sebuah klausa paket yang mendefinisikan paket di mana ia berada, diikuti oleh deklarasi import yang bisa saja kosong yang mendeklarasikan paket-paket yang isinya akan digunakan, diikuti oleh sekumpulan deklarasi fungsi, tipe, variabel, dan konstanta.
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
Klausa paket
Sebuah klausa paket memulai berkas sumber dan mendefinisikan paket yang mana berkas tersebut berada.
PackageClause = "package" PackageName . PackageName = identifier .
PackageName tidak boleh berupa pengidentifikasi kosong.
package math
Sekumpulan berkas yang berbagi PackageName yang sama membentuk implementasi dari sebuah paket. Implementasi Go bisa saja mengharuskan semua berkas sumber untuk sebuah paket berada dalam direktori yang sama.
Deklarasi import
Sebuah deklarasi import menyatakan bahwa berkas sumber yang berisi deklarasi tersebut bergantung pada fungsionalitas dari paket yang diimpor (Inisiasi dan eksekusi program) dan membolehkan akses ke pengidentifikasi yang diekspor dari paket tersebut. Deklarasi import bisa memberi nama pada pengidentifikasi (PackageName) untuk digunakan sebagai akses dan ImportPath yang menentukan paket yang akan diimpor.
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) . ImportSpec = [ "." | PackageName ] ImportPath . ImportPath = string_lit .
PackageName digunakan dalam pengidentifikasi nama untuk mengakses pengidentifikasi yang diekspor dari paket dalam berkas sumber yang mengimpor. Ia dideklarasikan dalam blok berkas. Jika PackageName tidak ada, maka default-nya adalah pengidentifikasi yang dispesifikasikan dalam klausa paket dari paket yang diimpor. Jika tanda titik (.) digunakan bukan sebuah nama, semua pengidentifikasi yang diekspor yang dideklarasikan dalam blok paket tersebut akan dideklarasikan dalam blok berkas sumber yang mengimpor dan harus diakses tanpa nama.
Interpretasi dari ImportPath bergantung pada implementasi namun biasanya berupa sebuah substring dari nama berkas penuh dari paket yang di-compile dan bisa jadi relatif terhadap repositori dari paket yang dipasang.
Batasan implementasi: Compiler bisa membatasi ImportPath menjadi string yang
tidak kosong menggunakan hanya karakter yang berada dalam kategori umum
Unicode
L, M, N, P, dan S (karakter grafik tanpa spasi) dan bisa juga tidak
mengikutkan karaketer !"#$%&'()*,:;⇐>?[]^
{|}` dan karakter pengganti
Unicode U+FFFD.
Asumsi kita telah mengkompilasi sebuah paket yang berisi klausa paket
package math
, yang mengekspor fungsi Sin, dan memasang paket yang telah
dikompilasi dalam berkas yang diidentifikasi dengan "lib/math".
Tabel berikut mengilustrasikan bagaimana Sin
diakses dalam berkas yang
mengimpor paket tersebut setelah deklarasi impor yang beragam.
Deklarasi impor Nama lokal dari Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin
Deklarasi import menyatakan sebuah relasi dependensi antara paket yang mengimpor dan yang diimpor. Paket tidak boleh mengimpor dirinya sendiri, baik secara langsung maupun tidak langsung, atau secara langsung mengimpor sebuah paket tanpa menggunakan pengidentifikasi yang diekspornya. Untuk mengimpor sebuah paket hanya untuk efek sampingnya saja (inisiasi), gunakan pengidentifikasi kosong sebagai nama paket yang eksplisit:
import _ "lib/math"
Sebuah contoh paket
Berikut sebuah paket Go yang mengimplementasikan saringan bilangan prima secara konkuren.
package main import "fmt" // Kirim seurutan 2, 3, 4, … ke channel 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Salin nilai dari channel 'src' ke channel 'dst', // menghapus angka yang dapat dibagi dengan 'prime'. func filter(src <-chan int, dst chan<- int, prime int) { for i := range src { // Pengulangan terhadap nilai yang diterima dari 'src'. if i%prime != 0 { dst <- i // Kirim 'i' ke channel 'dst'. } } } // Penyaring bilangan prima: Proses filter Daisy-chain berjalan bersamaan. func sieve() { ch := make(chan int) // Buat channel baur. go generate(ch) // Jalankan generate() sebagai goroutine. for { prime := <-ch fmt.Print(prime, "\n") ch1 := make(chan int) go filter(ch, ch1, prime) ch = ch1 } } func main() { sieve() }
Inisiasi dan eksekusi program
Nilai kosong
Saat tempat dialokasikan untuk sebuah variabel, baik lewat
deklarasi atau pemanggilan new
, atau saat sebuah nilai baru dibuat, baik
lewat literal komposit atau sebuah pemanggilan ke make
, dan tidak ada
inisiasi eksplisit yang diberikan, maka variabel atau nilai diberikan nilai
default.
Setiap elemen dari variabel tersebut di set ke nilai kosong bagi tipenya:
false
untuk boolean, 0
untuk tipe numerik, ""
untuk string, dan nil
untuk pointer, fungsi, interface, slice, channel, dan map.
Inisiasi ini berlaku secara rekursif, jadi misalkan setiap elemen dari struct
array akan memiliki field-filed yang di-nolkan jika tidak ada nilai yang
dispesifikasikan.
Dua deklarasi berikut adalah sama:
var i int var i int = 0
Setelah
type T struct { i int; f float64; next *T } t := new(T)
hal berikut berlaku:
t.i == 0 t.f == 0.0 t.next == nil
Hal yang sama juga benar untuk
var t T
Inisiasi paket
Dalam sebuah paket, inisiasi variabel di tingkat paket diproses secara bertahap, dengan setiap tahap memilih variabel paling awal dalam urutan deklarasi yang tidak memiliki dependensi terhadap variabel-variabel yang belum diinisiasi.
Lebih tepatnya, variabel di tingkat paket dianggap siap untuk inisiasi jika ia belum diinisiasi dan juga tidak punya ekspresi inisiasi atau ekspresi inisiasinya tidak memiliki kebergantungan terhadap variabel yang belum diinisiasi. Inisiasi diproses dengan secara berulang menginisiasi variabel tingkat-paket selanjutnya yang dideklarasikan di awal dan siap untuk inisiasi, sampai tidak ada lagi variabel siap untuk diinisiasi.
Jika ada variabel yang tetap belum diinisiasi saat proses selesai, variabel tersebut adalah bagian dari satu atau lebih putaran inisiasi, dan program tidak valid.
Sejumlah variabel pada bagian kiri dari sebuah deklarasi variabel yang diinisiasi oleh ekspresi tunggal (multi-nilai) pada bagian kanan, diinisiasi secara bersamaan: Jika ada variabel di sisi kiri diinisiasi, maka semua variabel tersebut diinisiasi dalam tahap yang sama.
var x = a var a, b = f() // a dan b diinisiasi bersamaan, sebelum x diinisiasi.
Variabel kosong diperlakukan seperti halnya variabel lainnya dalam deklarasi.
Urutan deklarasi dari variabel yang dideklarasikan dalam beberapa berkas ditentukan oleh urutan yang mana berkas tersebut diberikan ke compiler: Variabel yang dideklarasikan dalam berkas yang pertama dideklarasikan sebelum variabel lain dideklarasikan di berkas yang kedua, dan seterusnya.
Analisis dependensi tidak bergantung pada nilai sebenarnya dari variabel,
hanya pada referensi leksikal dalam sumber berkas, dianalisis secara
transitif.
Misalnya, jika ekspresi inisiasi variabel x
mengacu ke sebuah fungsi yang
isinya mengacu pada variabel y
, maka x
bergantung pada y
.
Secara spesifik:
-
Sebuah referensi ke sebuah variabel atau fungsi adalah sebuah pengidentifikasi yang menyatakan variabel atau fungsi tersebut.
-
Sebuah referensi ke sebuah method
m
yaitu sebuah nilai method atau ekspresi method dalam bentukt.m
, yang mana tipe (statik) darit
bukanlah tipe interface, dan methodm
berada dalam kumpulan method darit
. Apakah nilai fungsi yang dihasilkan darit.m
dipanggil tidak dipedulikan. -
Sebuah variabel, fungsi, atau method
x
bergantung pada sebuah variabely
jika ekspresi inisiasix
atau badan (untuk fungsi dan method) berisi referensi key
atau ke fungsi atau method yang bergantung key
.
Sebagai contoh, diberikan deklarasi berikut
var ( a = c + b // == 9 b = f() // == 4 c = f() // == 5 d = 3 // == 5 after initialization has finished ) func f() int { d++ return d }
urutan inisiasi adalah d
, b
, c
, a
.
Ingatlah bahwa urutan sub-ekspresi dalam ekspresi inisiasi tidak penting:
a = c + b
dan a = b + c
menghasilkan urutan inisiasi yang sama dalam
contoh tersebut.
Analisis dependensi dilakukan per paket; hanya referensi yang mengacu ke variabel, fungsi, dan method (bukan interface) yang dideklarasikan dalam paket yang sekarang yang diperhatikan. Jika ada kebergantungan data tersembunyi ada antara variabel, urutan inisiasi antara variabel tersebut tidak ditentukan.
Misalnya, diberikan deklarasi berikut
var x = I(T{}).ab() // x memiliki dependensi tersembunyi pada a dan b var _ = sideEffect() // tidak berkaitan dengan x, a, atau b var a = b var b = 42 type I interface { ab() []int } type T struct{} func (T) ab() []int { return []int{a, b} }
Variabel a
akan diinisiasi setelah b
namun apakah x
diinisiasi sebelum
b
, antara b
dan a
, atau setelah a
, atau pada saat sideEffect()
dipanggil (sebelum atau setelah x
diinisiasi) tidak ditentukan.
Variabel juga dapat diinisiasi menggunakan fungsi bernama init
yang
dideklarasikan dalam blok paket, tanpa argumen dan parameter kembalian.
func init() { ... }
Satu atau lebih fungsi tersebut bisa didefinisikan per paket, bahkan dalam
berkas sumber yang sama.
Dalam blok paket, pengidentifikasi init
hanya dapat digunakan untuk
mendeklarasikan fungsi init
, namun pengidentifikasi tersebut tidak
dideklarasikan.
Oleh sebab itu fungsi init
tidak dapat diacu di mana pun dalam sebuah
program.
Sebuah paket tanpa import diinisiasi dengan menempatkan nilai awal ke semua
variabel di tingkat paket diikuti oleh pemanggilan fungsi init
dengan urutan
sebagaimana mereka muncul dalam sumber, bisa jadi dalam banyak berkas, saat
diberikan kepada compiler.
Jika sebuah paket memiliki import, paket-paket yang diimpor diinisiasi sebelum
menginisiasi paket itu sendiri.
Jika banyak paket mengimpor sebuah paket, maka paket yang diimpor hanya
diinisiasi sekali.
Saat mengimpor paket, secara konstruksi, menjamin bahwa tidak ada
dependensi siklis.
Inisiasi paket —inisiasi variabel dan pemanggilan fungsi init
— terjadi
dalam sebuah goroutine, secara sekuensial, satu paket satu per satu.
Fungsi init
bisa meluncurkan goroutine yang lain, yang dapat berjalan secara
konkuren dengan kode inisiasi.
Namun, inisiasi selalu berurutan untuk fungsi init
: ia tidak akan memanggil
init
selanjutnya sebelum yang sebelumnya selesai.
Untuk memastikan perilaku inisiasi yang dapat direproduksi, sistem pembangunan dianjurkan memberikan beberapa berkas dalam paket yang sama dengan urutan nama berkas secara leksikal kepada compiler.
Eksekusi program
Sebuah program yang lengkap dibuat dengan mengaitkan sebuah paket yang tidak
diimpor bernama paket main dengan semua paket yang diimpornya, secara
transitif.
Paket main haruslah memiliki nama paket main
dan mendeklarasikan sebuah
fungsi main
yang tidak menerima argumen dan tidak memiliki kembalian.
func main() { … }
Eksekusi program dimulai dengan menginisiasi paket main dan kemudian memanggil
fungsi main
.
Bila pemanggilan fungsi tersebut selesai, maka program keluar.
Ia tidak menunggu goroutine yang lain untuk selesai.
Eror
Tipe error
pradeklarasi didefinisikan sebagai
type error interface { Error() string }
Interface error
adalah konvensi untuk merepresentasikan kondisi eror, dengan
nilai nil
merepresentasikan tidak ada eror.
Misalnya, sebuah fungsi yang membaca data dari sebuah berkas bisa
didefinisikan:
func Read(f *File, b []byte) (n int, err error)
Panik run-time
Eror eksekusi seperti mencoba membaca indeks array di luar batas memicu
panik run-time sama seperti dengan memanggil fungsi bawaan
panic dengan nilai tipe interface runtime.Error
.
Tipe tersebut memenuhi tipe interface pradeklarasi
error.
Nilai error sebenarnya yang merepresentasikan kondisi eror run-time tidak
ditentukan.
package runtime type Error interface { error // dan mungkin method-method lainnya. }
Konsiderasi sistem
Packet unsafe
Paket bawaan unsafe
, dikenal oleh compiler dan dapat diakses lewat
path import "unsafe", menyediakan fasilitas untuk
pemrograman tingkat-rendah termasuk operasi yang melanggar sistem tipe.
Sebuah paket yang menggunakan unsafe
haruslah diperiksa secara manual demi
keamanan tipe dan bisa jadi tidak portabel.
Paket tersebut menyediakan interface berikut:
package unsafe type ArbitraryType int // singkatan untuk tipe Go beragam; bukan tipe sebenarnya. type Pointer *ArbitraryType func Alignof(variable ArbitraryType) uintptr func Offsetof(selector ArbitraryType) uintptr func Sizeof(variable ArbitraryType) uintptr
Sebuah Pointer
adalah tipe pointer namun nilai Pointer
tidak boleh di-acu ulang.
Pointer atau nilai dari tipe dasar uintptr
dapat dikonversi ke
tipe dari tipe dasar Pointer
dan sebaliknya.
Efek dari mengonversi antara Pointer
dan uintptr
adalah terdefinisi secara
implementasi.
var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f)) var p ptr = nil
Fungsi Alignof
dan Sizeof
menerima sebuah ekspresi x
dengan tipe apa pun
dan mengembalikan ukuran tipe dari sebuah variabel hipotesis v
seolah-olah
v
dideklarasikan lewat var v = x
.
Fungsi Offsetof
menerima sebuah selector s.f
, menandakan
field f
dari struct yang bernotasi s
atau *s
, dan mengembalikan posisi
dari field dalam byte yang relatif terhadap alamat struct.
Jika f
adalah field yang ditanam, ia haruslah dapat
diakses tanpa pointer lewat field-field dari struct.
Untuk sebuah struct s
dengan field f
:
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
Arsitektur komputer bisa jadi membutuhkan alamat memory supaya diratakan;
supaya alamat sebuah variabel merupakan kelipatan dari sebuah faktor,
perataan tipe dari variabel.
Fungsi Alignof
menerima ekspresi yang menyatakan sebuah variabel tipe apa
pun dan mengembalikan perataan dari (tipe dari) variabel dalam sekumpulan
byte.
Untuk sebuah variabel x
:
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
Pemanggilan dari Alignof
, Offsetof
, dan Sizeof
adalah ekspresi konstanta
bertipe uintptr
.
Jaminan ukuran dan perataan
Untuk tipe-tipe numerik, ukuran-ukuran berikut dijamin:
tipe ukuran dalam byte byte, uint8, int8 1 uint16, int16 2 uint32, int32, float32 4 uint64, int64, float64, complex64 8 complex128 16
Properti perataan minimum berikut selalu berlaku:
-
Untuk sebuah variabel
x
tipe apa pun:unsafe.Alignof(x)
paling tidak 1. -
Untuk sebuah variabel
x
bertipe struct:unsafe.Alignof(x)
adalah nilai terbesar dari semuaunsafe.Alignof(x.f)
untuk setiap fieldf
darix
, namun paling tidak 1. -
Untuk sebuah variabel
x
bertipe array:unsafe.Alignof(x)
sama dengan perataan dari variabel dari tipe elemen dari array.
type T struct {} type T2 struct { b byte i int64 } var t T fmt.Println(unsafe.Alignof(t)) // 1 var t2 T2 fmt.Println(unsafe.Alignof(t2)) // 8, ukuran terbesar yaitu dari field i. var si []int32 fmt.Println(unsafe.Alignof(si)) // 4, ukuran elemen dari si yaitu int32.
Sebuah tipe struct atau array memiliki ukuran nol jika ia tidak mengandung field (atau elemen) yang memiliki ukuran lebih besar dari nol. Dua variabel berukuran nol yang berbeda bisa saja memiliki alamat yang sama di memory.