Tanya Jawab

Table of Contents

Asal mula

Apakah tujuan dari proyek ini?

Pada saat Go lahir, dunia pemrograman berbeda dari sekarang. Perangkat lunak di industri biasanya ditulis dengan C++ atau Java, Github belum ada, kebanyakan komputer belum multi prosesor, dan selain Visual Studio dan Eclipse hanya ada beberapa IDE atau perkakas pembantu lainnya yang tersedia, dan tidak semuanya gratis di Internet.

Sementara itu, kami telah frustasi dengan kompleksnya kebutuhan bahasa yang kita gunakan untuk mengembangkan perangkat lunak server. Komputer telah menjadi begitu cepat semenjak bahasa seperti C, C++, dan Java pertama kali dikembangkan tapi pemrograman itu sendiri tidak berkembang begitu banyak. Dan juga, jelas bahwa multi prosesor akan menjadi universal tapi kebanyakan bahasa menyediakan sedikit dukungan untuk dapat memprogramnya secara efisien dan aman.

Kami memutuskan untuk berpikir tentang isu-isu mayor apa saja yang akan mendominasi pembangunan perangkat lunak pada beberapa tahun ke depan saat teknologi berkembang, dan bagaimana sebuah bahasa baru bisa membantu mengatasinya. Misalnya, munculnya CPU dengan multi core bisa menjadi alasan bahwa sebuah bahasa sebaiknya menyediakan dukungan utama untuk konkurensi dan paralelisme. Dan untuk membuat manajemen sumber daya mudah dikerjakan dalam sebuah program yang konkuren, garbage collection, atau setidaknya suatu model manajemen memory yang otomatis dan aman setidaknya dibutuhkan.

Pertimbangan-pertimbangan ini mengarah ke beberapa diskusi lanjutan yang menyebabkan lahirnya Go, pertama sebagai kumpulan ide dan keinginan, kemudian sebagai sebuah bahasa. Tujuan secara keseluruhan yaitu supaya Go lebih membantu pemrogram dengan menyediakan perkakas, mengotomasi pekerjaan-pekerjaan biasa seperti pemformatan, dan menghapus permasalahan saat bekerja dalam basis kode yang besar.

Deskripsi yang lebih luas dari tujuan dibuatnya Go dan bagaimana ia dicapai, atau setidaknya bagaimana ia dikembangkan, tersedia dalam artikel https://talks.golang.org/2012/splash.article[Go di Google: Rancangan Bahasa dalam Melayani Rekayasa Perangkat Lunak (Inggris)].

Bagaimana sejarah proyek ini?

Robert Griesemer, Rob Pike, dan Ken Thompson mulai membuat sketsa beberapa sasaran dari sebuah bahasa baru di sebuah papan tulis pada 21 September, 2007. Dalam beberapa hari, sasaran-sasaran tersebut terkumpul menjadi sebuah rencana untuk melakukan sesuatu dan gambaran bagaimana bentuknya nanti. Perancangan dilanjutkan paruh waktu, paralel dengan pekerjaan lainnya. Pada Januari 2008, Ken mulai bekerja menulis sebuah compiler untuk mengeksplorasi ide tersebut; compiler tersebut menghasilkan kode C. Pada pertengahan tahun, bahasa tersebut menjadi proyek penuh dan telah siap untuk dicoba sebagai sebuah compiler. Pada Mei 2008, Ian Taylor secara independen mulai membuat antar muka untuk Go pada GCC menggunakan spesifikasi draf yang ada. Russ Cox bergabung pada akhir 2008 dan membantu mendorong bahasa dan pustakanya dari prototipe menjadi realitas.

Go menjadi proyek sumber terbuka pada 10 November 2009. Orang-orang yang tak terhitung banyaknya dari komunitas telah mengkontribusikan ide, diskusi, dan kode mereka.

Sekarang ada jutaan pemrogram Go--gopher--diseluruh dunia, dan akan bertambah setiap hari. Kesuksesan Go telah melampau ekspektasi kita.

Apakah asal mula dari maskot gopher?

Maskot dan logo dirancang oleh Renée French, yang juga merancang Glenda, kelincinya Plan 9. Sebuah blog tentang gopher menjelaskan bagaimana ia diturunkan dari rancangan bajunya WFMU yang ia buat beberapa tahun lalu. Logo dan maskot berlisensikan Creative Commons Attribution 3.0.

Gopher memiliki lembar model yang mengilustrasikan karakteristiknya dan bagaimana merepresentasikannya dengan benar. Lembar model tersebut pertama kali diperlihatkan dalam sebuah presentasi oleh Renée pada Gophercon 2016. Maskot tersebut memiliki fitur unik; ia adalah gopher -nya Go, bukan hanya gopher yang dulu.

Apakah bahasanya disebut Go atau Golang?

Bahasanya disebut Go. Orang-orang menyebutnya "golang" karena situsnya bernama "golang.org", bukan "go.org", yang mana tidak tersedia saat itu. Banyak yang menggunakan nama golang, dan cukup berguna sebagai sebuah label. Misalnya, tag pada Twitter untuk bahasa Go adalah "#golang". Nama dari bahasa itu sendiri adalah Go saja.

Sebagai catatan: Walaupun logo resmi menggunakan dua huruf besar, nama bahasa tetap ditulis Go, bukan GO.

Kenapa membuat sebuah bahasa baru?

Go lahir dari frustasi terhadap bahasa dan lingkungan pekerjaan yang kita lakukan di Google. Pemrograman menjadi semakin sukar dan salah satu yang harus disalahkan yaitu pilihan dari bahasa. Seseorang harus memilih salah satu dari kompilasi yang efisien, eksekusi yang efisien, atau mudah diprogram; ketiganya tidak tersedia dalam bahasa-bahasa pemrograman yang terkenal. Para pemrogram memilih kemudahan daripada keamanan dan efisiensi, dengan pindah ke bahasa bertipe dinamis seperti Python dan JavaScript daripada C++ atau Java.

Yang sadar hal ini tidak kita saja. Setelah beberapa tahun sunyinya bahasa pemrograman, Go adalah salah satu dari beberapa bahasa baru--Rust, Elixir, Swift, dan lebih banyak lagi--yang telah membuat pengembangan bahasa pemrograman menjadi bidang yang aktif, hampir terkenal lagi.

Go menyelesaikan masalah-masalah tersebut dengan mencoba menggabungkan kemudahan pemrograman dari bahasa yang diinterpretasi, bertipe dinamis dengan efisiensi dan keamanan dari bahasa bertipe statis dan dikompilasi. Ia juga berusaha lebih moderen, dengan dukungan komputasi jaringan dan multi- core. Terakhir, bekerja dengan Go memang ditujukan supaya cepat: ia seharusnya butuh beberapa detik untuk membuat sebuah program yang besar dalam sebuah komputer. Untuk mencapai sasaran tersebut dibutuhkan penanganan sejumlah isu lingustik: sebuah sistem tipe yang ekspresif namun ringan; konkurensi dan garbage collection; spesifikasi dependensi yang kokoh; dan lainnya. Hal ini tidak bisa diatasi dengan mudah oleh pustaka-pustaka atau perkakas yang ada; sebuah bahasa baru dibutuhkan.

Artikel Go di Google (bahasa Inggris) mendiskusikan latar belakang dan motivasi dibalik rancangan bahasa Go, dan juga menyediakan rincian lebih lanjut tentang banyak jawaban-jawaban yang ada dalam dokumen Tanya-Jawab ini.

Apa saja leluhur-leluhur Go?

Go umumnya berada dalam keluarga dari C (sintaks dasarnya), dengan masukan yang cukup signifikan dari Pascal/Modula/Oberon (deklarasi, paket), ditambah beberapa ide dari bahasa yang diinspirasi oleh CSP-nya Tony Hoare, seperti Newsqueak dan Limbo (konkurensi). Namun, Go adalah bahasa baru secara keseluruhan. Dalam segala hal bahasa Go dirancang dengan memikirkan tentang apa yang pemrogram lakukan dan bagaimana melakukan pemrograman, setidaknya pemrograman yang kami lakukan, lebih efektif, yang berarti lebih menyenangkan.

Apa saja prinsip panduan dalam rancangan?

Saat Go dirancang, Java dan C++ adalah bahasa yang paling umum digunakan untuk membuat program server, setidaknya di Google. Kami merasakan bahwa bahasa tersebut membutuhkan terlalu banyak pencatatan dan repetisi. Beberapa pemrogram pindah ke bahasa yang lebih dinamis seperti Python, dengan mengorbankan efisiensi dan keamanan tipe. Kami merasakan bahwa adalah memungkinkan untuk memiliki efisiensi, keamanan, dan kecairan dalam sebuah bahasa.

Go mencoba mengurangi jumlah pengetikan dan tipe data. Selama perancangannya, kita mencoba mengurangi kekusutan dan kompleksitas. Tidak ada deklarasi penerus dan tidak ada berkas-berkas header; semuanya dideklarasikan cukup sekali saja. Inisialiasi dibuat ekspresif, otomatis, dan mudah digunakan. Sintaksnya bersih dan ringan dengan kata kunci. Kegagapan (foo.Foo* myFoo = new(foo.Foo)) dikurangi dengan tipe turunan sederhana menggunakan konstruksi `:= deklarasi-dan-inisialisasi. Dan yang paling radikal, tidak ada tipe hirarki: tipe hanyalah tipe, mereka tidak perlu memberitahukan keterkaitannya (dengan tipe lain). Penyederhanaan ini membuat Go menjadi ekspresif dan tetap mudah dibaca tanpa mengorbankan kecanggihan.

Prinsip utama lainnya yaitu menjaga konsepnya tetap ortogonal. Method dapat diimplementasikan untuk semua tipe; struct merepresentasikan data, sementara interface merepresentasikan abstraksi; dan seterusnya. Sifat ortogonal ini membuatnya mudah untuk memahami apa yang terjadi saat beberapa hal digabungkan.

Penggunaan

Apakah Google menggunakan Go secara internal?

Ya. Go digunakan secara luas dalam production di Google. Salah satu contoh sederhana yaitu server dibalik golang.org. Ia adalah server dokumentasi dari godoc yang berjalan dalam sebuah konfigurasi production di Google App Engine.

Contoh yang lebih signifikan yaitu server unduh Google, dl.google.com, yang melayani pengunduhan program Chrome dan berbagai paket seperti dari apt-get.

Go bukan satu-satunya bahasa yang digunakan di Google, jauh dari itu, tapi ia merupakan bahasa penting untuk sejumlah wilayah termasuk Site Reliability engineering (SRE) dan pemrosesan data berukuran besar.

Perusahaan apa saja yang menggunakan Go?

Penggunaan Go meningkat diseluruh dunia, khususnya, tapi bukan berarti secara ekslusif, dalam ruang komputasi cloud. Beberapa proyek infrastruktur cloud besar yang ditulis dengan Go adalah Docker dan Kubernetes, namun masih banyak yang lainnya.

Tidak hanya pada cloud saja. Halaman pengguna pada Go Wiki, yang cukup sering diperbarui, berisi daftar dari beberapa perusahaan yang menggunakan Go.

Go Wiki juga memiliki halaman tentang cerita-cerita sukses perusahaan dan proyek yang menggunakan Go.

C dan Go bisa saja digunakan dalam ruang memory yang sama, namun hal ini bukanlah suatu hal yang cocok secara alamiah dan membutuhkan antar muka perangkat lunak yang spesial. Menghubungkan C dengan kode Go berarti mengorbankan keamanan memory dan properti manajemen stack yang Go sediakan. Terkadang perlu menggunakan pustaka C untuk menyelesaikan sebuah masalah, namun melakukan hal tersebut selalu menimbulkan risiko yang mana tidak akan muncul bila menggunakan kode Go yang murni, jadi lakukanlah dengan hati-hati.

Jika anda benar butuh menggunakan C dengan Go, cara melakukannya bergantung kepada implementasi compiler Go. Ada tiga implementasi compiler Go yang didukung secara resmi. Diantaranya gc, compiler bawaan, gccgo yang menggunakan GCC, dan gollvm yang kurang stabil, yang menggunakan infrasruktur LLVM.

gc menggunakan konvensi pemanggilan dan linker yang berbeda dari C dan oleh karena itu tidak bisa dipanggil langsung dari program C, atau sebaliknya. Program cgo menyediakan mekanisme untuk sebuah "antarmuka fungsi asing" (foreign function interface) untuk membolehkan pemanggilan pustaka C secara aman dalam kode Go. SWIG memperluas kapabilitas ini ke pustaka C++.

Kita juga bisa menggunakan cgo dan SWIG dengan gccgo dan gollvm. Secara mereka menggunakan API tradisional, ia juga memungkinkan, namun dengan sangat hati-hati, untuk mengaitkan kode dari compiler tersebut secara langsung dengan program C atau C++ yang di- compile dengan GCC/LLVM. Namun, melakukan hal tersebut secara aman membutuhkan pemahaman konvensi pemanggilan dari semua bahasa, dan juga perhatian terhadap batas stack saat memanggil C atau C++ dari Go.

IDE apa saja yang mendukung Go?

Proyek Go tidak menyediakan kostum IDE, namun bahasa dan pustakanya telah dirancang untuk membuatnya mudah untuk menganalisis sumber kode. Akibatnya, banyak editor dan IDE terkenal yang mendukung Go, baik secara langsung atau lewat sebuah plugin.

Daftar IDE dan editor yang mendukung Go diantaranya Emacs, Vim, VSCode, Atom, Eclipse, Sublime, IntelliJ (lewat kostum varian bernama Goland), dan lebih banyak lagi.

Apakah Go mendukung protokol buffers?

Proyek sumber terbuka lain menyediakan plugin compiler dan pustaka yang dibutuhkan. Ia tersedia di github.com/golang/protobuf.

Bolehkah saya menerjemahkan situs Go ke bahasa lain?

Tentu saja. Kami mendorong pengembang untuk membuat situs Bahasa Go dengan bahasanya sendiri. Namun, bila anda ingin menambahkan logo atau brand Google ke situs anda (yang mana tidak ada dalam situs golang.org), anda harus patuh pada aturan di https://www.google.com/permissions/guidelines.html.

Perancangan

Apakah Go memiliki sebuah runtime?

Go memiliki sejumlah pustaka yang luas, yang disebut runtime, yang merupakan bagian dari setiap program Go. Pustaka runtime mengimplementasikan garbage collection, konkurensi, manajemen stack, dan fitur penting lainnya dari bahasa Go. Walaupun lebih terpusat pada bahasa itu sendiri, runtime pada Go analoginya sama dengan libc, pustaka bahasa C.

Harus juga dipahami, bahwa runtime Go tidak mengikutkan mesin virtual, seperti yang disediakan oleh runtime Java. Program Go di- compile diawal menjadi kode mesin (atau JavaScript atau WebAssembly, untuk beberapa implementasi varian). Oleh karena itu, walaupun istilah tersebut seringkali digunakan untuk mendeskripsikan lingkungan virtual di mana program Go berjalan, dalam dunia Go "runtime" adalah nama yang diberikan ke pustaka yang menyediakan layanan-layanan penting dari bahasa.

Ada apa dengan pengidentifikasi Unicode?

Saat merancang Go, kami memastikan bahwa ia tidak harus berpusat pada ASCII, yang artinya memperluas ruang pengidentifikasi dari batas-batas 7-bit ASCII. Aturan Go--karakter pengidentifikasi haruslah huruf atau angka yang didefinisikan oleh Unicode--sangat mudah dipahami dan diimplementasikan namun memiliki batasan. Misalnya, kombinasi karakter tidak dibolehkan, seperti pada bahasa Devanagari.

Aturan ini menyebabkan konsekuensi lainnya. Karena pengidentifikasi yang diekspor harus diawali dengan huruf besar, pengidentifikasi yang dibuat dari karakter pada bahasa-bahasa tertentu bisa saja, secara definisi, tidak diekspor. Untuk saat sekarang, satu-satunya solusi yaitu menggunakan awalan seperti X日本語, yang mana kurang memuaskan.

Sejak dari versi awal, kami telah mempertimbangkan bagaimana cara terbaik memperluas ruang pengidentifikasi untuk mengakomodasi pemrogram yang menggunakan bahasa ibunya. Hal-hal apa saja yang harus dilakukan saat ini masih menjadi topik diskusi yang aktif, dan versi selanjutnya dari bahasa Go bisa saja lebih terbuka dalam definisi dari pengidentifikasi. Misalnya, ia mungkin mengadopsi rekomendasi untuk pengidentifikasi dari organisasi Unicode. Apapun yang terjadi, ia harus kompatibel dan menjaga (atau mungkin mengembangkan) bagaimana huruf menentukan visibilitas dari pengidentifikasi, yang mana merupakan fitur favorit dari Go.

Untuk saat ini, kita memiliki aturan sederhana yang dapat dikembangkan nanti di masa depan, tanpa mengganggu program, salah satunya untuk menghindari bug yang bisa saja muncul dari aturan yang menggunakan pengidentifikasi yang ambigu.

Kenapa Go tidak memiliki fitur X?

Setiap bahasa memiliki fitur-fitur baru yang mengindahkan fitur kesukaan orang lain. Go dirancang demi kenyamanan pemrograman, kecepatan compile, konsep ortogonal, dan kebutuhan untuk mendukung fitur seperti konkurensi dan garbage collection. Fitur kesukaan anda bisa saja tidak ada karena tidak cocok, karena ia mempengaruhi kecepatan compile atau memperumit rancangan, atau karena ia akan membuat model sistem fundamentalnya menjadi terlalu sukar.

Jika anda merasa terganggu dengan tidak adanya fitur X pada Go, mohon maafkan kami dan cobalah fitur-fitur yang Go miliki. Anda bisa saja menemukan mereka cukup memenuhi, dengan cara tertentu, dari ketidakadaannya fitur X.

Kenapa Go tidak memiliki tipe generik?

Tipe generik mungkin akan ditambahkan pada suatu waktu. Kami tidak merasakan urgensi dari fitur tersebut, walaupun kami paham beberapa programmer butuh itu.

Go ditujukan sebagai bahasa untuk menulis program server yang mudah untuk dipelihara sepanjang waktu. (Lihat artikel berikut untuk latar belakang lebih lanjut.) Rancangannya berkonsentrasi pada hal-hal seperti mudah di- scale, mudah dibaca, dan konkurensi. Pemrograman polymorphic tampak tidak terlalu penting untuk sasaran bahasa Go pada saat itu, sehingga sengaja ditinggalkan demi kesederhanaan.

Bahasa Go sekarang lebih matang, sehingga ada ruang untuk mempertimbangkan sebuah bentuk pemrograman generik. Namun, ada beberapa yang keberatan.

Generik sebenarnya baik tapi harus dibayar dengan kompleksitas pada sistem tipe dan runtime. Kami belum menemukan rancangan yang memberikan nilai yang sebanding dengan kompleksitasnya, namun kita terus memikirkan hal tersebut. Untuk sementara, tipe bawaan Go map dan slice, ditambah dengan interface kosong untuk membentuk sebuah penampung (dengan konversi eksplisit) artinya pada banyak kasus dimungkinkan untuk menulis kode seperti pada pemrograman generik, walau sedikit kurang mulus.

Topik generik ini tetap dibuka. Untuk melihat percobaan yang gagal merancang solusi generik yang bagus pada Go, lihat proposal ini.

Kenapa Go tidak memiliki eksepsi (exception)?

Kami percaya bahwa mengikutkan eksepsi pada sebuah struktur kontrol, seperti idiom try-catch-finally, menghasilkan kode yang kusut. Ia juga mendorong pemrogram untuk terlalu banyak melabeli eror yang biasa, seperti gagal membuka berkas, sebagai sebuah eksepsi.

Go menggunakan pendekatan yang berbeda. Untuk penanganan eror biasa, kembalian dengan multi nilai pada Go membuatnya mudah untuk melaporkan kesalahan tanpa membebani nilai kembalian. Tipe error kanonis digabungkan dengan fitur Go lainnya, membuat penanganan eror mudah namun cukup berbeda dengan bahasa lainnya.

Go juga memiliki beberapa fungsi bawaan untuk memberi sinyal dan pemulihan dari kondisi yang benar-benar eksepsi. Mekanisme pemulihan dieksekusi sebagai bagian dari fungsi, yang cukup untuk menangani bencana dan tidak membutuhkan struktur kontrol tambahan dan, bila digunakan dengan baik, bisa menghasilkan sebuah kode penanganan eror yang bersih.

Lihat artikel Defer Panic dan Recover (bahasa Inggris) untuk lebih rinci. Blog tentang Eror adalah nilai (bahasa Inggris) menjelaskan salah satu pendekatan untuk menangani eror dengan bersih pada Go dengan mendemonstrasikan bahwa, secara eror hanyalah nilai, fitur-fitur yang ada dapat digunakan untuk menangani eror.

Kenapa Go tidak memiliki fungsi assert?

Go tidak menyediakan fungsi untuk assert. Memang fungsi tersebut cukup masuk akal, tapi berdasarkan pengalaman kami pemrogram menggunakannya sebagai pembantu untuk menghindari berpikir tentang penanganan dan pelaporan eror yang lebih baik. Penanganan eror yang baik artinya server seharusnya terus beroperasi bukan berhenti setelah eror yang non fatal terjadi. Pelaporan error yang baik artinya eror jelas dan langsung keintinya, menghindari pemrogram dari menginterpretasikan hasil crash dari program yang besar. Eror yang presisi sangat penting bila pemrogram melihat eror yang tidak lazim dalam program mereka.

Kami paham bahwa hal ini adalah sesuatu yang sering diperdebatkan. Ada banyak hal dalam pustaka dan bahasa Go yang berbeda dengan praktik modern, alasannya sederhana, karena kami merasa terkadang pantas mencoba pendekatan yang berbeda.

Kenapa membuat konkurensi berdasarkan ide dari CSP?

Pemrograman multi threading dan konkurensi selama ini memiliki reputasi sebagai sesuatu yang rumit. Kami percaya hal ini disebabkan karena rancangan yang kompleks seperti pthreads dan sebagian karena terlalu menekankan rincian level-bawah seperti mutex, variabel kondisi, dan pembatasan memory. Antarmuka yang lebih tinggi membuat kode lebih sederhana, walaupun tetap masih ada mutex dan lainnya di belakangnya.

Salah satu model yang paling sukses dalam mendukung linguistik tingkat-tinggi untuk konkurensi datang dari Communicating Sequential Process, atau CSP, dari Hoare. Occam dan Erlang adalah dua dari bahasa terkenal yang mengimplementasikan CSP. Konkurensi primitif dari Go diturunkan dari bagian pohon keluarga yang berbeda (dari Occam dan Erlang) yang kontribusi utamanya yaitu channel sebagai objek kelas satu. Pengalaman dengan bahasa-bahasa sebelumnya telah memperlihatkan bahwa model CSP sesuai dengan kerangka bahasa pemrograman prosedural.

Kenapa goroutine bukannya thread?

Goroutine adalah bagian yang membuat konkurensi mudah digunakan. Idenya, yang mana telah ada sebelumnya, adalah dengan melakukan multipleks saat mengeksekusi fungsi secara independen--coroutine--ke dalam kumpulan thread. Saat sebuah coroutine diblok, seperti saat melakukan pemanggilan ke sistem, runtime secara otomatis memindahkan coroutine yang lain dalam thread yang sama ke thread yang berbeda yang dapat berjalan sehingga ia tidak ikut terblok. Programmer tidak melihat hal ini, itu intinya. Hasilnya, yang kita sebut goroutine, bisa sangat murah: mereka memiliki sedikit pengeluaran tambahan selain memory untuk stack, yaitu beberapa kilobyte.

Untuk membuat supaya stack -nya kecil, runtime Go menggunakan stack yang terbatas dan bisa berubah ukuran. Goroutine yang baru dibuat diberikan beberapa kilobyte, yang biasanya cukup. Bila tidak, runtime memperbesar (atau mengecilkan) memory untuk menyimpan stack secara otomatis, membolehkan banyak goroutine untuk berjalan di dalam sejumlah memory yang berukuran sedang. Ongkos pada CPU rata-rata sekitar tiga instruksi per pemanggilan fungsi. Cukup praktis untuk membuat ratusan ribu goroutine dalam ruang alamat yang sama. Jika goroutine adalah thread, sumber daya sistem akan habis dengan jumlah (routine) yang lebih sedikit.

Kenapa operasi map tidak atomic?

Setelah diskusi yang lama diputuskan bahwa penggunaan umum dari map dari beberapa goroutine tidak membutuhkan akses yang aman, dan pada kasus yang membutuhkan akses yang aman, map kemungkinan adalah bagian dari sebuah struktur data atau komputasi yang besar yang telah disinkronisasi. Oleh karena itu mengharuskan semua operasi map menggunakan sebuah mutex akan memperlambat hampir semua program dan hanya mengamankan beberapa program saja. Hal ini bukanlah keputusan yang mudah, akan tetapi, akses terhadap map yang tidak dikontrol dapat membuat program crash.

Bahasa Go sendiri tidak menghalangi pembaruan pada map yang atomic. Bila dibutuhkan, seperti saat menjalankan program yang tidak dipercaya, implementasi bisa saja saling mengunci akses map.

Akses map akan tidak aman bila pembaruan terjadi. Selama semua goroutine hanya membaca--melihat elemen dalam map, termasuk iterasi menggunakan pengulangan for range--dan tidak mengubah map dengan menempatkan elemen baru atau menghapusnya, maka akan aman untuk mengakses mereka secara konkuran tanpa sinkronisasi.

Untuk membantu penggunaan map yang benar, beberapa implementasi bahasa memiliki pemeriksaan khusus yang secara otomatis melaporkan pada saat runtime bila sebuah map diubah secara tidak aman oleh eksekusi yang konkuren.

Apakah anda akan menerima perubahan bahasa dari saya?

Orang terkadang menyarankan perbaikan terhadap bahasa -- milis banyak berisi sejarah diskusi ini--namun sangat sedikit dari perubahan tersebut yang diterima.

Walaupun Go adalah proyek sumber terbuka, bahasa dan pustakanya dilindungi oleh perjanjian kompatibilitas yang mencegah perubahan yang dapat membuat program tidak dapat di- compile, setidaknya pada tingkat sumber kode (program mungkin harus di compile ulang sewaktu-waktu). Jika proposal anda melanggar spesifikasi Go 1 kita tidak dapat menerima ide anda, terlepas dari kelebihannya. Rilis mayor selanjutnya dari Go bisa saja tidak kompatibel dengan Go 1, namun diskusi tentang topik tersebut baru saja dimulai dan satu hal yang pasti: hanya ada sedikit ketaksesuaian yang diperkenalkan dalam proses tersebut. Lebih lanjut lagi, perjanjian kompatibilitas mendorong kita untuk menyediakan sebuah cara otomatis kedepannya supaya program-program yang lama dapat beradaptasi jika situasi tersebut muncul.

Bahkan jika proposal anda kompatibel dengan spesifikasi Go 1, ia mungkin tidak sesuai dengan jiwa dari sasaran rancangan Go. Artikel Go di Google menjelaskan asal mula Go dan motivasi dibalik rancangannya.

Tipe

Apakah Go bahasa berorientasi-objek?

Ya dan tidak. Walaupun Go memiliki tipe dan method dan membolehkan pemrograman bergaya orientasi-objek, Go tidak memiliki hirarki tipe. Konsep "interface" dalam Go menyediakan pendekatan berbeda yang kami percaya lebih mudah digunakan dan dalam beberapa hal lebih umum. Ada cara untuk menanam tipe ke dalam tipe lain untuk membentuk analogi yang sama--tapi tidak identik--dengan subclass. Method dalam Go lebih umum daripada C++ atau Java: mereka dapat didefinisikan untuk data apapun, bahkan tipe bawaan seperti integer. Method tidak hanya terbatas pada struct (class).

Selain itu, dengan tidak adanya hirarki tipe membuat "objek" dalam Go lebih ringan daripada bahasa seperti C++ atau Java.

Bagaimana cara mengirim method secara dinamis?

Satu-satunya cara untuk mengirim method secara dinamis adalah lewat interface. Method pada struct atau tipe konkret lainnya selalu dikonversi secara statis.

Kenapa tidak ada tipe turunan?

Pemrograman berorientasi-objek, setidaknya pada bahasa-bahasa yang terkenal, selalu mendiskusikan hubungan antara tipe, hubungan yang sering kali bisa diturunkan secara otomatis. Go mengambil pendekatan berbeda.

Pemrogram tidak perlu mendeklarasikan bahwa dua tipe saling berelasi, melainkan dalam Go sebuah tipe otomatis memenuhi interface apapun yang menspesifikasikan sub bagian dari method nya. Selain mengurangi pencatatan, pendekatan ini memiliki kelebihan. Tipe dapat memenuhi banyak interface, tanpa adanya kompleksitas dari multipel turunan tradisional. Interface bisa sangat ringan--sebuah interface dengan satu atau bahkan tanpa method dapat mengekspresikan konsep yang berguna. Interface dapat ditambahkan setelah sebuah ide baru muncul atau untuk pengujian--tanpa mempengaruhi tipe asilnya. Karena tidak adanya relasi eksplisit antara tipe dan interface, maka tidak ada hirarki tipe yang harus diatur atau didiskusikan.

Ide ini bisa digunakan untuk membentuk sebuah analogi dari Unix pipe. Misalnya, lihat bagaimana fmt.Fprintf membolehkan pencetakan berformat ke keluaran apapun, tidak hanya berkas, atau bagaimana paket bufio dapat terpisah sepenuhnya dari berkas I/O, atau bagaimana paket image membangkitkan berkas gambar yang terkompres. Semua ide-ide ini datang dari sebuah interface (io.Writer) yang merepresentasikan sebuah method (Write). Dan kita baru hanya menyentuh bagian luar dari interface. Interface pada Go memiliki pengaruh yang kuat tentang bagaimana sebuah program dibangun.

Butuh beberapa waktu untuk terbiasa namun dengan model dependensi tipe seperti ini adalah salah satu hal yang produktif dari Go.

Kenapa len adalah sebuah fungsi bukan method?

Kami memperdebatkan masalah ini namun kemudian memutuskan mengimplementasikan len dan teman-temannya sebagai fungsi karena dalam praktiknya tidak mempersulit masalah tentang tipe dasar interface.

Kenapa Go tidak mendukung overloading method dan operator?

Pengiriman method menjadi sederhana jika ia tidak memerlukan pencocokan tipe. Pengalaman kami dengan bahasa-bahasa pemrograman lain menyimpulkan bahwa memiliki beragam method dengan nama yang sama tapi dengan signature yang berbeda terkadang berguna namun pada praktiknya bisa membingungkan dan rapuh. Pencocokan hanya dengan nama dan konsistensi pada tipe adalah keputusan penyederhanaan utama dalam sistem tipe Go.

Perihal overloading operator, sebenarnya lebih pada kenyamanan daripada kebutuhan yang absolut. Sekali lagi, hal-hal menjadi lebih sederhana tanpa adanya kebutuhan tersebut.

Kenapa Go tidak memiliki deklarasi "implement"?

Sebuah tipe memenuhi sebuah interface dengan mengimplementasikan method-method pada interface tersebut, cukup itu saja. Properti ini membolehkan interface didefinisikan dan digunakan tanpa harus mengubah kode yang ada. Ia membolehkan semacam penulisan struktural yang mempromosikan pemisahan antara kebutuhan-kebutuhan dan meningkatkan penggunaan ulang pada kode, dan mempermudah membangun pola-pola yang muncul pada saat membangun kode. Semantik dari interface adalah salah satu alasan utama dari kecepatan dan keringanan pada Go.

Lihat pertanyaan tentang turunan untuk informasi lebih rinci.

Bagaimana saya menjamin tipe memenuhi sebuah interface?

Kita bisa menggunakan compiler untuk memeriksa apakah tipe T mengimplementasikan interface I dengan mencoba menempatkan nilai kosong dari T atau pointer ke T, sebagai berikut:

type T struct{}
var _ I = T{}       // Memverifikasi bahwa T mengimplementasikan I.
var _ I = (*T)(nil) // Memverifikasi bahwa *T mengimplementasikan I.

Jika T (atau *T) tidak mengimplementasikan I, kesalahan tersebut akan terdeteksi saat kode di- compile.

Jika kita menginginkan pengguna dari interface secara eksplisit mengimplementasikannya, kita bisa menambahkan sebuah method dengan nama yang deskriptif. Sebagai contohnya:

type Fooer interface {
	Foo()
	ImplementsFooer()
}

Sebuah tipe harus mengimplementasikan method ImplementsFooer supaya bisa menjadi Fooer, dengan jelas mendokumentasikan fakta tersebut dan memunculkannya pada go doc.

type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}

Umumnya kode tidak menggunakan batasan seperti itu, karena membatasi utilitas dari ide tentang interface. Terkadang, mereka diperlukan juga untuk mengatasi masalah ambigu di antara interface-interface yang mirip.

Kenapa tipe T tidak memenuhi interface Equal?

Anggaplah interface sederhana berikut merepresentasikan sebuah objek yang dapat dibandingkan dengan nilai yang lain:

type Equaler interface {
	Equal(Equaler) bool
}

dan tipe T berikut:

type T int
func (t T) Equal(u T) bool { return t == u } // tidak memenuhi Equaler

Tidak seperti sistem bertipe polymorphic, dalam Go, T tidak mengimplementasikan Equaler. Tipe dari argumen T.Equal adalah T, bukan tipe yang dibutuhkan oleh Equaler.

Dalam Go, sistem tipe tidak mempromosikan argumen dari Equal; hal tersebut merupakan tanggung jawab dari pemrogram, seperti yang digambarkan oleh tipe T2, yang mengimplementasikan Equaler:

type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) }  // memenuhi Equaler

Walaupun hal ini tidak seperti sistem tipe lainnya, karena pada Go semua tipe yang memenuhi Equaler dapat dikirim sebagai argumen ke T2.Equal, dan pada saat runtime kita harus memeriksa bahwa argumen benar bertipe T2. Beberapa bahasa mengatur supaya hal tersebut terjamin pada saat di- compile.

Contoh lain yang berhubungan:

type Opener interface {
	Open() Reader
}

func (t T3) Open() *os.File

Dalam Go, T3 tidak memenuhi Opener, walaupun dalam bahasa pemrogram lain ia bisa saja terpenuhi.

Memang benar bahwa sistem tipe Go bekerja kurang bagi programmer pada kasus-kasus tersebut, tidak adanya sub-tipe membuat aturan tentang pemenuhan sebuah interface sangat mudah ditulis: apakah nama fungsi dan argumen-argumennya sama dengan yang di interface? Aturan Go ini sangat mudah diimplementasikan secara efisien. Kami merasakan bahwa keuntungan ini mengimbangi kekurangan dari tipe otomatis. Bila Go suatu saat nanti mengadopsi sebuah bentuk penulisan polymorphic, kami mengharapkan ada suatu cara mengekspresikan ide dari contoh-contoh tersebut dan juga membuatnya supaya diperiksa secara statis.

Bisakah mengkonversi []T ke []interface{}?

Tidak secara langsung. Hal ini tidak dibolehkan oleh spesifikasi bahasa karena kedua tipe tersebut tidak memiliki representasi yang sama dalam memory. Maka diperlukan penyalinan elemen secara tersendiri ke slice tujuan. Contoh berikut mengkonversi sebuah slice int ke slice interface{}:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
	s[i] = v
}

Bisakah mengkonversi []T1 ke []T2 jika T1 dan T2 memiliki tipe dasar yang sama?

Baris terakhir dari contoh kode berikut tidak bisa di compile.

type T1 int
type T2 int
var t1 T1
var x = T2(t1) // OK
var st1 []T1
var sx = ([]T2)(st1) // NOT OK

Dalam Go, tipe-tipe berkaitan dengan method, sehingga setiap tipe bernama memiliki sebuah (bisa kosong) kumpulan method. Aturan umumnya adalah anda bisa mengubah nama dari tipe yang dikonversi (sehingga bisa mengubah kumpulan method nya) tapi kita tidak bisa mengubah nama (dan kumpulan method) dari elemen-elemen dari sebuah tipe komposit. Go mengharuskan kita mengkonversi tipe secara eksplisit.

Kenapa nilai error nil tidak sama dengan nil?

Di balik layar, interface diimplementasikan sebagai dua elemen, sebuah tipe T dan sebuah nilai V. V adalah nilai konkret seperti sebuah int, struct atau pointer, bukan interface, dan memiliki tipe T. Misalnya, jika kita menyimpan nilai int 3 ke dalam sebuah interface, kembalian dari interface memiliki, secara semantik, (T=int, V=3). Nilai V dikenal juga dengan nilai dinamis dari interface, secara variabel interface tersebut bisa saja menyimpan nilai V yang berbeda (yang berkorespondensi dengan tipe T) selama berjalannya program.

Sebuah interface bernilai nil jika dan hanya V dan T tidak diset, (T=nil, `V tidak di set). Lebih rincinya, sebuah interface nil selalu menyimpan tipe nil. Jika kita menyimpan pointer nil bertipe *int dalam sebuah nilai interface, maka tipe di dalamnya adalah *int berapapun nilai dari pointer tersebut: (T=*int, V=nil). Nilai interface tersebut akan selalu non-nil walaupun nilai pointer V adalah nil.

Situasi seperti ini bisa membingungkan, dan muncul bila nilai nil disimpan di dalam sebuah nilai interface seperti dalam nilai kembalian error.

func returnsError() error {
	var p *MyError = nil
	if bad() {
		p = ErrBad
	}
	return p // Akan selalu mengembalikan nilai error non-nil.
}

Jika semua berjalan dengan baik, fungsi di atas mengembalikan p yang nil, jadi nilai kembalian adalah sebuah nilai interface error yang menyimpan (T=*MyError, V=nil). Jika pemanggil fungsi membandingkan nilai kembalian error dengan nil, ia akan selalu bernilai true walaupun tidak ada eror yang terjadi. Untuk mengembalikan nil error yang benar ke pemanggil, fungsi tersebut harus mengembalikan nil secara eksplisit:

func returnsError() error {
	if bad() {
		return ErrBad
	}
	return nil
}

Fungsi yang mengembalikan error sebaiknya selalu menggunakan tipe error dalam signature -nya (seperti contoh di atas) bukan dengan tipe konkret seperti *MyError, untuk menjamin error dibuat dengan benar. Sebagai contoh, os.Open mengembalikan error walaupun, jika tidak nil, ia selalu bertipe konkret https://golang.org/pkg/os/#PathError.

Situasi yang sama seperti yang dijelaskan di sini dapat muncul kapan pun interface digunakan. Cukup diingat, jika nilai konkret disimpan dalam interface, maka interface tersebut tidak akan bernilai nil. Untuk informasi lebih lanjut, lihat Hukum refleksi.

Kenapa tidak ada union, seperti pada C?

Union akan melanggar jaminan keamanan memory pada Go.

Kenapa Go tidak memiliki tipe varian?

Tipe varian, dikenal juga dengan tipe aljabar, menyediakan suatu cara untuk menspesifikasikan bahwa sebuah nilai bisa memiliki salah satu dari sekumpulan tipe, namun hanya tipe-tipe yang didefinisikan saja. Salah satu contoh umum yaitu pada pemrograman sistem yang menspesifikasikan bahwa sebuah eror adalah eror jaringan, eror keamanan, atau eror pada aplikasi dan membolehkan pemanggil untuk membedakan sumber dari permasalahan dengan membedah tipe dari eror. Contoh lainnya yaitu pohon sintaks yang mana setiap node dapat memiliki tipe yang berbeda: deklarasi, perintah, penempatan, dan lainnya.

Kami mempertimbangkan menambahkan tipe varian ke dalam Go, namun setelah berdiskusi lebih lanjut kami memutuskan untuk mengindahkannya karena tumpang tindih dengan interface. Apa yang terjadi jika elemen dari sebuah tipe varian adalah interface dari dirinya sendiri?

Selain itu, beberapa permasalahan dari tipe varian telah dipenuhi oleh bahasa Go. Contohnya pada penanganan eror yang bisa diekspresikan dengan menggunakan nilai interface yang menyimpan eror dan switch bertipe untuk membedakannya. Pohon sintaks juga bisa diterapkan dengan model yang sama, walaupun kurang elegan.

Kenapa Go tidak memiliki tipe kembalian covariant?

Tipe kembalian covariant artinya sebuah interface seperti

type Copyable interface {
	Copy() interface{}
}

akan dipenuhi oleh method

func (v Value) Copy() Value

karena Value mengimplementasikan interface kosong. Dalam Go, tipe method harus sesuai secara eksak, jadi Value tidak mengimplementasikan Copyable. Go memisahkan antara apa yang sebuah tipe dapat lakukan--method -nya--dari apa yang diimplementasinya. Jika dua method mengembalikan tipe yang berbeda, mereka berarti tidak melakukan hal yang sama. Pemrogram yang menginginkan tipe kembalian covariant terkadang mencoba mengekspresikan hirarki tipe lewat interface. Dalam Go lebih wajar untuk memiliki pemisahan yang jelas antara interface dan implementasinya.

Nilai

Kenapa Go tidak menyediakan konversi numerik implisit?

Kenyamanan dari konversi otomatis antara tipe numerik dalam C lebih dirugikan oleh kebingungan yang disebabkannya. Kapan sebuah ekspresi menjadi unsigned? Berapa besar nilainya? Apakah nilainya overflow? Apakah hasilnya portabel, independen terhadap mesin dimana program dieksekusi? Ia juga memperuit compiler; "konversi aritmatika biasa" tidaklah mudah diimplementasikan dan tidak konsisten antara arsitektur (mesin). Dengan alasan portabilitas, kami memutuskan untuk membuat konversi lebih jelas dan lugas dengan biaya beberapa konversi eksplisit dalam kode. Definisi dari konstanta dalam Go--nilai presisi acak bebas dari notasi ukuran dan signed--memperbaiki banyak hal.

Rincian yang berhubungan dengan ini adalah, tidak seperti C, int dan int64 adalah tipe yang berbeda walaupun jika int bertipe 64-bit. Tipe int adalah generik; jika anda ingin tahu berapa bits dalam sebuah integer, Go menyarankan kita untuk eksplisit.

Bagaimana konstanta bekerja dalam Go?

Walaupun Go ketat dalam konversi antara variabel dari tipe numerik yang berbeda, kontansta lebih fleksibel. Konstanta harfiah seperti 23, 3.14159, dan math.Pi menggunakan ruang yang sama, dengan presisi yang berubah dan tanpa overflow atau underflow. Misalnya, nilai math.Pi dispesifikasikan memakai 63 bit dalam sumber kode, dan ekspresi konstanta yang mengikutkan nilai tersebut menjaga presisi tetap sesuai dengan batas maksimum yang dapat disimpan dalam float64. Hanya pada saat konstanta atau ekspresi konstanta diberikan ke sebuah variabel--sebuah lokasi memory dalam program--ia menjadi sebuah angka "komputer" dengan properti dan presisi nilai float.

Secara mereka adalah angka, bukan nilai bertipe, konstanta dalam Go dapat digunakan lebih bebas daripada variabel, sehingga meringankan beberapa kecanggungan antar aturan-aturan konversi yang baku. Kita dapat menulis ekspresi berikut

sqrt2 := math.Sqrt(2)

tanpa ada keluhan dari compiler karena angka 2 bisa dikonversi secara aman dan akurat ke float64 untuk pemanggilan math.Sqrt.

Sebuah blog berjudul Konstanta (Inggris) mengeksplorasi topik ini lebih rinci.

Kenapa map merupakan tipe bawaan?

Dengan alasan yang sama seperti string: karena map adalah struktur data yang penting dan kuat sehingga dengan menyediakan implementasi yang istimewa dengan dukungan sintaktis membuat pemrograman lebih menyenangkan. Kami percaya implementasi map pada Go cukup kuat sehingga ia dapat digunakan untuk hampir semua kasus. Jika aplikasi tertentu lebih menguntungkan dari implementasi kostum, maka memungkinan untuk membuatnya namun tidak akan lebih nyaman secara sintaks; hal ini pengorbanan masuk akal.

Kenapa map tidak membolehkan slice sebagai key?

Pencarian key pada map membutuhkan operator ekualitas, yang mana tidak diimplementasikan pada slice. Slice tidak memiliki ekualitas karena ekualitas tidak terdefinisi dengan baik pada tipe tersebut; ada beberapa pertimbangan yang mengikutkan perbandingan shallow dan deep, perbandingan pointer vs. nilai, bagaimana berurusan dengan tipe rekursif, dan lainnya. Kami mungkin akan menelaah kembali isu ini--dan mengimplementasikan ekualitas pada slice tidak akan merubah program yang sudah ada--namun tanpa adanya ide yang jelas tentang apa itu ekualitas pada slice, maka lebih mudah untuk mengindahkannya untuk saat sekarang.

Pada Go 1, tidak seperti rilis sebelumnya, ekualitas didefinisikan untuk struct dan array, sehingga tipe tersebut dapat digunakan sebagai key dari map. Slice masih belum memiliki definisi tentang ekualitas.

Kenapa map, slice, dan channel menggunakan referensi sementara array dengan nilai?

Sejarah mengenai topik ini cukup panjang. Pada awalnya, map dan channel secara sintaks adalah pointer dan memungkinan untuk mendeklarasikan atau menggunakan instansi yang bukan pointer (pada map dan channel). Dan juga, kami agak susah dengan mendefinisikan bagaimana array seharusnya bekerja dalam Go. Akhirnya kami memutuskan bahwa pemisahan yang baku antara pointer dan nilai membuat bahasa sulit untuk digunakan. Mengubah tipe tersebut untuk bersifat referensi terhadap asosiasi, struktur data berbagi, mengatasi masalah tersebut. Perubahan ini menambah kompleksitas yang disesalkan pada bahasa namun memiliki efek yang besar pada kebergunaan: Go menjadi lebih produktif, bahasa yang nyaman saat dikenalkan ke dunia luar.

Menulis Kode

Bagaimana pustaka didokumentasikan?

Sebuah program, godoc, ditulis dengan Go, mengekstrak dokumentasi paket dari sumber kode dan membuatnya dapat dibukan dalam sebuah halaman web dengan tautan ke deklarasi, berkas, dan lainnya. Salah satu instansinya berjalan di golang.org/pkg. Pada kenyataannya, godoc mengimplementasikan keseluruhan situs golang.org.

Instansi dari godoc bisa dikonfigurasi untuk menyediakan analisis interaktif yang kaya dari simbol dalam program; rinciannya ada dalam daftar berikut.

Untuk mengakses dokumentasi dari baris perintah, perkakas go memiliki sub-perintah doc yang menyediakan antarmuka tekstual dari informasi yang sama.

Apakah ada panduan gaya pemrograman Go?

Tidak ada aturan gaya yang eksplisit, meskipun ada beberapa "Gaya Go" yang cukup dikenal.

Go telah menetapkan konvensi untuk membantu melakukan penamaan, susunan, dan pengorganisasian berkas. Dokumentasi Efektif Go memiliki beberapa saran mengenai topik ini. Lebih lanjut, program gofmt bertujuan untuk mencetak sumber kode dengan aturan-aturan yang baku; ia menggantikan aturan tertulis yang memboleh interpretasi. Semua kode Go dalam repositori, dan hampir mayoritas dalam dunia _open source_, telah dijalankan lewat gofmt.

Dokumen berjudul Komentar Pemeriksaan Kode Go berisi kumpulan esai ringkas tentang rincian idiom dari Go yang terkadang luput oleh para pemrogram. Dokumen tersebut adalah referensi yang membantu bagi yang ingin memeriksa kode untuk proyek Go.

Bagaimana cara mengirim patch untuk pustaka Go?

Sumber pustaka ada di dalam direktori src dari repositori. Jika anda ingin membuat perubahan yang signifikan, mohon diskusikan terlebih dahulu di milis sebelum memulai.

Lihat dokumentasi Berkontribusi pada proyek Go untuk informasi lebih lanjut.

Kenapa "go get" menggunakan HTTPS saat menyalin repositori?

Perusahaan terkadang hanya membolehkan trafik TCP keluar pada port standar 80 (HTTP) dan 443 (HTTPS), dan memblok trafik lainnya, termasuk port 9418 pada TCP (git) dan port TCP 22 (SSH). Bila menggunakan HTTPS, git mengharuskan validasi sertifikat, menyediakan perlindungan terhadap man in the middle, penyadapan, dan perusakan. Perintah go get oleh karena itu menggunakan HTTPS untuk keamanan.

Git bisa dikonfigurasi untuk melakukan otentikasi lewat HTTP atau menggunakan SSH. Untuk otentikasi lewat HTTP, kita bisa menambahkan baris berikut dalam berkas $HOME/.netrc yang akan dibaca oleh git:

machine github.com login _USERNAME_ password _APIKEY_

Untuk akun Github, kata kunci (password) bisa berupa token akses personal.

Git juga bisa dikonfigurasi menggunakan SSH bukan HTTPS untuk URL yang cocok dengan prefiks tertentu. Misalnya, untuk menggunakan SSH untuk semua akses ke Github, tambahkan baris berikut ke ~/.gitconfig:

[url "ssh://git@github.com/"]
	insteadOf = https://github.com/

Bagaimana cara mengatur versi paket menggunakan "go get"?

Sejak awal proyek, Go tidak memiliki konsep versi paket, namun hal ini berubah. Versioning (paket dengan versi) adalah sebuah kompleksitas yang signifikan, terutama dalam basis kode yang besar, dan butuh waktu yang cukup lama untuk mengembangkan sebuah pendekatan yang bekerja dalam skala yang cukup besar untuk beragam situasi yang cocok bagi semua pengguna Go.

Rilis Go 1.11 menyediakan dukungan eksperimental untuk paket dengan versi terhadap perintah go, dalam bentuk modul. Untuk informasi lebih lanjut, lihat catatan rilis Go 1.11 dan dokumentasi perintah go.

Bagaimanapun teknologi manajemen paketnya, "go get" dan perkakas Go lainnya menyediakan isolasi paket dengan path import yang berbeda. Misalnya, pustaka standar html/template dan text/template hidup berdampingan walaupun keduanya adalah "paket untuk template". Hal ini mengarah pada beberapa anjuran untuk penulis paket dan pengguna paket.

Paket yang ditujukan untuk digunakan secara umum sebaiknya mencoba untuk menjaga kompatibilitas selama berkembang. pedoman kompatibilitas Go 1 adalah sebuah referensi yang baik: jangan menghapus nama-nama yang diekspor, menganjurkan komposit dengan tag, dan lainnya. Jika fungsionalitas berbeda dibutuhkan, tambahkan nama baru bukan dengan mengubah nama yang sudah ada. Jika perubahan besar benar-benar tidak dapat dielakan, buatlah paket baru dengan path impor yang baru.

Jika menggunakan paket luar dan khawatir ia akan berubah secara tidak terduga, namun belum menggunakan Go module, solusi termudah yaitu menyalinnya ke dalam repositori anda. Pendekatan ini digunakan oleh Google secara internal dan didukung oleh perkakas go lewat sebuah teknik yang disebut "vendoring". Hal ini mengikutkan penyimpanan semua salinan dependensi dibawah path import yang mengidentifikasi mereka sebagai salinan lokal. Lihat dokumen rancangan untuk lebih rinci.

Pointer dan Alokasi

Kapan parameter fungsi dikirim dengan nilai?

Semua parameter pada fungsi dikirim dengan nilai pada Go. Fungsi selalu menerima salinan dari apa yang dikirim. Misalnya, mengirim nilai int ke sebuah fungsi membuat salinan dari int, dan mengirim nilai pointer membuat salinan dari pointer, tapi tidak menyalin data yang diacu. (Lihat bagian selanjutnya untuk diskusi bagaimana hal ini mempengaruhi penerima method.)

Nilai map dan slice memiliki perilaku yang sama dengan pointer: mereka adalah struktur data yang mengandung pointer ke bagian dalam data map dan slice. Mengirim sebuah nilai map atau slice tidak akan menyalin data yang diacunya. Mengirim nilai interface membuat salinan dari apa yang disimpan dalam nilai interface. Jika nilai interface menyimpan sebuah struct, mengirim nilai interface akan membuat salinan dari struct. Jika interface menyimpan pointer, mengirim nilai interface berarti membuat salinan dari pointer, namun sekali lagi tidak membuat salinan dari data yang diacu.

Ingatlah bahwa diskusi ini lebih kepada semantik dari operasi. Implementasi sebenarnya bisa saja memiliki optimisasi untuk menghindari penyalinan selama optimisasi tersebut tidak merubah semantik.

Kapan sebaiknya menggunakan pointer ke interface?

Hampir tidak pernah. Pointer ke nilai interface muncul hanya pada situasi yang rumit dan unik yang mengikutkan penyembunyian tipe nilai interface untuk evaluasi yang ditunda.

Kesalahan yang sering terjadi yaitu mengirim pointer ke sebuah nilai interface terhadap fungsi yang mengharapkan sebuah interface. compiler akan melaporkan kesalahan ini, namun situasi ini terkadang juga membingungkan, karena terkadang pointer diperlukan untuk memenuhi sebuah interface. Intinya adalah pointer ke tipe kongkret akan memenuhi sebuah interface, namun pointer ke sebuah interface tidak akan pernah memenuhi interface.

Misalnya deklarasi variabel berikut,

var w io.Writer

Fungsi pencetakan fmt.Fprintf menerima argumen pertama yang memenuhi io.Writer--apapun yang mengimplementasikan method Write. Maka kita dapat menulis

fmt.Fprintf(w, "hello, world\n")

Jika kita mengirim alamat dari w, program tidak akan bisa di- compile.

fmt.Fprintf(&w, "hello, world\n") // Compile-time error.

Satu-satunya pengecualian yaitu nilai apapun, bahkan sebuah pointer ke interface, dapat ditempatkan ke sebuah variabel bertipe interface kosong (interface{}). Namun demikian, sudah pasti sebuah kesalahan bila nilainya adalah pointer ke interface; hasilnya bisa membingungkan.

Apakah method sebaiknya didefinisikan dengan nilai atau pointer?

func (s *MyStruct) pointerMethod() { } // method dengan pointer
func (s MyStruct)  valueMethod()   { } // method dengan nilai

Bagi pemrogram yang tidak terbiasa dengan pointer, perbedaan antara kedua contoh di atas bisa membingungkan, tapi sebenarnya situasinya cukup sederhana. Saat mendefinisikan method pada sebuah tipe, penerima (s pada contoh di atas) berlaku seperti sebuah argumen terhadap method.

func pointerMethod(s *MyStruct) { } // ilustrasi method dengan pointer
func valueMethod(s MyStruct)   { }  // ilustrasi method dengan nilai

Apakah sebaiknya mendefinisikan penerima dalam bentuk nilai atau pointer adalah pertanyaan yang sama dengan apakah sebuah argumen dari fungsi sebaiknya dengan nilai atau pointer. Ada beberapa pertimbangan di sini.

Pertama, dan yang paling penting, apakah method butuh mengubah penerimanya? Jika iya, si penerima harus berupa pointer. (Slice dan map berlaku sebagai referensi, perilakunya sedikit berbeda, misalnya untuk mengubah panjang dari sebuah slice dalam sebuah method, si penerima harus berupa pointer.) Pada contoh di atas, jika pointerMethod mengubah field dari s, pemanggil akan melihat perubahannya, namun valueMethod dipanggil dengan salinan dari argumen pemanggil (ini adalah definisi dari pengiriman dengan nilai), sehingga perubahan yang terjadi tidak terlihat bagi pemanggil.

Penerima method pada Java selalu pointer, walaupun bentuk pointer-nya disamarkan (dan sekarang ada proposal untuk menambahkan penerima dengan nilai ke bahasa Java). Penerima dengan nilai-lah sebenarnya yang tidak umum di Go.

Yang kedua yaitu pertimbangan efisiensi. Jika struktur data si penerima cukup besar, akan lebih efisien bila menggunakan pointer sebagai penerima.

Selanjutnya yaitu konsistensi. Jika beberapa penerima method dari tipe harus berupa pointer, sisanya juga sebaiknya sama, sehingga kumpulan method dari tipe tersebut konsisten walau bagaimanapun tipe tersebut digunakan. Lihat bagian kumpulan method untuk lebih rinci.

Untuk tipe dasar, slice, dan struct yang kecil, penerima dengan nilai sangatlah murah, jadi bila semantik dari method membutuhkan pointer, penerima dengan nilai bisa efisien dan lebih jelas.

Apakah perbedaan antara new dan make?

Singkat kata: new mengalokasikan memory, sementara make menginisialisasi tipe slice, map, dan channel.

Lihat bagian terkait pada Efektif Go untuk lebih rincinya.

Berapakah ukuran int pada mesin 64-bit?

Ukuran dari int dan uint spesifik pada implementasi namun ukurannya sama pada semua platform. Demi portabilitas, kode yang bergantung pada ukuran nilai tertentu sebaiknya menggunakan ukuran tipe yang eksplisit, seperti int64. Pada mesin 32-bit, compiler menggunakan 32-bit integer secara bawaan, sementara pada mesin 64-bit, integer memiliki ukuran 64-bit. (Dalam sejara bahasa pemrograman, hal ini tidak selalu benar.)

Di lain sisi, tipe skalar floating point dan complex selalu memiliki ukuran (tidak ada tipe dasar float atau complex), karena pemrogram seharusnya memperhatikan presisi saat menggunakan bilangan floating point. Tipe standar untuk konstanta floating point (tanpa tipe) adalah float64. Maka foo := 3.0 mendeklarasikan sebuah variabel foo bertipe float64. Untuk variabel bertipe float32 yang diinisialisasi dengan konstanta, tipe variabel haruslah dispesifikasikan saat deklarasi:

var foo float32 = 3.0

Cara lain, yaitu mendeklarasikan kontanta tersebut menggunakan konversi tipe seperti berikut foo := float32(3.0).

Bagaimana mengetahui variabel dideklarasikan dalam heap atau stack?

Dari sisi penggunaan, anda tidak perlu tahu. Setiap variabel di Go akan disimpan selama ada yang mengacunya. Lokasi penyimpanan yang dipilih oleh implementasi tidak relevan dengan semantik dari bahasa.

Lokasi penyimpanan memang berpengaruh pada efisiensi program. Bila memungkinkan, Go compiler akan mengalokasikan variabel lokal dari fungsi dalam stack frame dari fungsi tersebut. Namun, jika compiler tidak dapat membuktikan bahwa variabel tersebut tidak diacu setelah fungsi selesai, maka compiler akan mengalokasikan variabel dalam heap (garbage collected) untuk menghindari adanya kesalahan pointer. Jika variabel lokal sangat besar, maka akan lebih masuk akal bila disimpan dalam heap bukan dalam stack.

Pada Go compiler saat ini, jika sebuah variabel diambil alamatnya, variabel tersebut merupakan kandidat untuk alokasi dalam heap. Namun, analisis pelepasan (analytic escape) dasar mengenali beberapa kasus saat variabel seperti itu tidak akan hidup sampai fungsi selesai sehingga dapat disimpan dalam stack.

Kenapa proses Go menggunakan virtual memory yang besar?

Pengalokasi memory pada Go memesan sejumlah besar wilayah virtual memory sebagai arena alokasi. Virtual memory ini adalah lokal terhadap proses Go tertentu; pemesanan ini tidak menghilangkan memory pada proses yang lain.

Untuk mengetahui alokasi memory sebenarnya dari sebuah proses Go, gunakan perintah top pada Unix dan perhatikan kolom RES (Linux) atau RSIZE (macOS).

Konkurensi

Operasi apa saja yang atomic? Bagaimana dengan mutex?

Penjelasan dari operasi atomic dalam Go dapat ditemukan dalam dokumen Model memory Go (Inggris).

Sinkronisasi tingkat-rendah dan primitif atomic tersedia dalam paket sync dan sync/atomic. Paket-paket tersebut cukup bagus untuk pekerjaan sederhana seperti penghitungan referensi (reference counts) atau menjamin mutual eksklusi (mutual exclusion) dalam skala kecil.

Untuk operasi tingkat-tinggi, seperti koordinasi antara server, Go mendukungnya dengan pendekatan lewat goroutine dan channel. Misalnya, kita dapat menyusun program supaya hanya satu goroutine yang bertanggung jawab untuk suatu data tertentu. Pendekatan ini diringkas dalam Pepatah Go (Video - Inggris).

"Jangan berkomunikasi dengan berbagi memory. Namun, bagilah memory dengan berkomunikasi"

Lihat contoh kode Berbagi Memory dengan Berkomunikasi dan artikel terkait untuk diskusi lebih rinci mengenai konsep ini.

Program besar yang konkuren biasanya menggunakan kedua metode ini.

Kenapa program tidak lebih cepat dengan CPU yang banyak?

Apakah sebuah program berjalan lebih cepat dengan banyaknya CPU bergantung pada permasalahan yang ditanganinya. Bahasa Go menyediakan konkurensi primitif, seperti goroutine dan channel, namun konkurensi hanya mengaktifkan paralelisme saat permasalahan yang ditangani secara intrinsik benar paralel. Permasalahan yang secara intrinsik sekuensial tidak akan bisa dipercepat dengan menambahkan CPU, sementara kode yang dipecah menjadi lebih kecil dan dapat dieksekusi secara paralel bisa jadi mempercepat program.

Terkadang menambah CPU malah memperlambat program. Dalam praktiknya, program yang menghabiskan waktu lebih banyak untuk sinkronisasi dan berkomunikasi, daripada melakukan komputasi, bisa mengalami penurunan performansi saat menggunakan beberapa thread di OS. Hal ini disebabkan karena pengiriman data antara thread mengikutkan perpindahan konteks (context switching), yang memiliki biaya yang signifikan, dan biaya tersebut dapat bertambah dengan menambah jumlah CPU. Misalnya, contoh penyaringan bilangan prima dalam spesifikasi Go tidak memiliki performansi paralelisme yang signifikan walaupun ia menjalankan banyak goroutine; menambah jumlah thread (CPU) akan lebih memungkinkan memperlambatnya daripada mempercepatnya.

Untuk rincian lanjut tentang topik ini lihatlah wicara yang berjudul Konkurensi bukanlah Paralelisme (Inggris).

Bagaimana cara mengatur jumlah CPU?

Jumlah CPU yang tersedia secara simultan yang mengeksekusi goroutine dikontrol oleh variabel lingkungan GOMAXPROCS, yang nilai bakunya adalah jumlah core CPU yang tersedia. Program yang berpotensi untuk dieksekusi paralel akan secara bawaan dieksekusi secara paralel pada mesin dengan multipel CPU. Untuk mengubah jumlah CPU yang digunakan, atur nilai variabel lingkungan atau gunakan fungsi dengan nama yang sama dari paket runtime untuk mengatur supaya runtime menggunakan jumlah thread yang berbeda. Mensetnya dengan 1 berarti mengindahkan kemungkinan paralelisme, memaksa setiap goroutine untuk dieksekusi secara bergantian.

runtime bisa mengalokasikan thread lebih banyak daripada nilai GOMAXPROCS untuk melayani beberapa permintaan I/O yang tertunda. GOMAXPROCS hanya mempengaruhi berapa banyak goroutine yang dieksekusi secara bersamaan; bisa saja banyak yang diblok saat pemanggilan ke sistem dilakukan.

Penjadwal goroutine pada Go tidak selalu bagus, walaupun dari waktu ke waktu mengalami perbaikan dan peningkatan. Di suatu saat, ia mungkin lebih mengoptimasi penggunaan thread OS. Untuk saat sekarang, jika ada masalah performansi, menset GOMAXPROCS pada aplikasi mungkin cukup membantu.

Kenapa tidak ada ID untuk goroutine?

Goroutine tidak memiliki nama; mereka hanyalah pekerja yang anonim. Mereka juga tidak membuka identifikasi unik, nama, atau struktur data ke pemrogramnya. Beberapa orang cukup terkejut dengan hal ini, mengharapkan perintah go mengembalikan sebuah item yang dapat digunakan untuk mengakses dan mengontrol goroutine.

Alasan fundamental kenapa goroutine anonim adalah supaya bahasa Go sepenuhnya tersedia untuk kode pemrograman konkuren. Sebaliknya, pola penggunaan yang berkembang saat thread dan goroutine memiliki nama dapat membatasi apa yang pustaka dasar dapat gunakan.

Berikut ilustrasi dari beberapa permasalahan tersebut. Saat kita memberi nama sebuah goroutine dan membangun model disekitarnya, ia menjadi spesial, dan kita tergoda untuk mengasosiasikan semua komputasi terhadap goroutine tersebut, mengindahkan kemungkinan menggunakan multipel, atau bisa saja goroutine yang berbeda untuk pemrosesan. Jika paket net/http mengasosiasikan state per permintaan dengan sebuah goroutine, maka klien tidak akan bisa menggunakan lebih banyak goroutine untuk melayani permintaan.

Lebih lanjut, pengalaman kami dengan pustaka-pustaka seperti sistem grafis yang mengharuskan semua pemrosesan terjadi dalam "main thread" telah memperlihatkan bagaimana kaku dan terbatasnya pendekatan tersebut saat dikembangkan pada bahasa yang konkuren. Adanya thread atau goroutine khusus memaksa pemrogram memutar-balikan program untuk menghindari crash dan permasalahan lain yang disebabkan karena tidak sengaja beroperasi pada thread yang salah.

Untuk kasus-kasus dimana goroutine tertentu benar-benar spesial, bahasa Go menyediakan fitur seperti channel yang dapat digunakan dengan cara yang fleksibel.

Fungsi dan method

Kenapa T dan *T memiliki kumpulan method yang berbeda?

Seperti yang ditulis dalam spesifikasi Go, kumpulan method dari tipe T terdiri dari semua method yang penerimanya adalah T, sementara tipe dengan pointer *T mengikutkan semua method yang penerimanya adalah *T atau T. Ini artinya semua method *T mengikutkan T, tapi tidak sebaliknya.

Perbedaan ini muncul karena jika sebuah nilai interface berisi *T, pemanggilan method pada T dapat dilakukan dengan mengembalikan referensi kepada pointer, namun jika nilai interface mengandung nilai T, tidak ada cara yang aman untuk melakukan pemanggilan method lewat pointer. (Cara tersebut akan menyebabkan method dapat mengubah nilai di dalam interface, yang mana tidak dibolehkan oleh spesifikasi bahasa.)

Bahkan apabila compiler dapat mengambil alamat dari sebuah nilai untuk dikirim ke method, jika method tersebut mengubah nilai maka perubahannya akan hilang pada sisi pemanggil. Sebagai contoh, jika method Write dari bytes.Buffer menggunakan penerima dengan nilai bukan pointer, kode berikut:

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

akan menyalin standar input ke dalam salinan dari buf, bukan ke dalam buf itu sendiri. Hal seperti ini bukanlah yang kita inginkan.

Apa yang terjadi bila goroutine dijalankan secara closure?

Beberapa kesalahan bisa muncul saat menggunakan closure dengan konkurensi. Misalnya pada contoh program berikut:

func main() {
	done := make(chan bool)

	values := []string{"a", "b", "c"}
	for _, v := range values {
		go func() {
			fmt.Println(v)
			done <- true
		}()
	}

	// Tunggu sampai semua goroutine selesai sebelum keluar.
	for _ = range values {
		<-done
	}
}

Seseorang mengharapkan mendapatkan a, b, c sebagai keluaran. Apa yang tercetak bisa saja c, c, c. Hal ini disebabkan karena setiap iterasi dari pengulangan menggunakan instansi variabel v yang sama, sehingga setiap closure berbagi variabel yang sama. Saat closure dijalankan, ia akan mencetak nilai dari v saat fmt.Println dieksekusi, namun v bisa saja telah diubah sejak goroutine diluncurkan. Untuk membantu mendeteksi permasalahan seperti ini, gunakan go vet.

Untuk menempatkan nilai dari v ke setiap closure saat dijalankan, kita harus mengubah pengulangan supaya membuat variabel baru untuk setiap iterasi. Salah satu caranya yaitu dengan mengirim variabel sebagai argumen dari closure:

	for _, v := range values {
		go func(u string) {
			fmt.Println(u)
			done <- true
		}(v)
	}

Pada contoh ini, nilai v dikirim sebagai argumen ke fungsi anonim. Nilai tersebut diakses di dalam fungsi sebagai variabel u.

Cara yang lebih mudah lagi untuk membuat variabel baru yaitu menggunakan deklarasi, yang mungkin tampak aneh tapi bekerja pada Go:

	for _, v := range values {
		v := v // buat variabel 'v' yang baru.
		go func() {
			fmt.Println(v)
			done <- true
		}()
	}

Perilaku bahasa Go seperti ini, yang mana tidak mendefinisikan variabel baru untuk setiap iterasi, kalau dilihat kembali mungkin adalah suatu kesalahan pada rancangan bahasa. Ia mungkin akan diatasi pada versi selanjutnya, tapi demi kompatibilitas, hal ini tidak bisa diubah pada Go versi 1.

Alur kontrol

Kenapa Go tidak punya operator ?:?

Tidak ada operasi ternari (?:) dalam Go. Kita bisa menggunakan kode berikut untuk mendapatkan hal yang sama:

if expr {
	n = trueVal
} else {
	n = falseVal
}

Alasan kenapa ?: tidak ada dalam Go yaitu karena perancang bahasa terlalu sering melihat operator tersebut digunakan untuk menulis ekspresi yang kompleks. Bentuk if-else, walau lebih panjang, tidak dapat disangsikan lagi lebih jelas. Sebuah bahasa hanya perlu satu konstruksi alur kontrol kondisi.

Paket dan Pengujian

Bagaimana cara membuat paket dengan banyak berkas?

Simpan semua berkas kode untuk paket tersebut di dalam sebuah direktori. Sebuah berkas sumber kode dapat mengacu ke item di berkas yang berbeda; tidak perlu membuat deklarasi acuan atau berkas header (seperti pada C/C++).

Selain dapat dipisahkan menjadi beberapa berkas, paket akan di compile dan di tes seperti dibuat dalam berkas yang tunggal.

Bagaimana membuat unit tes?

Buat berkas baru yang berakhiran _test.go di direktori yang sama dengan sumber paket. Di dalam berkas tersebut, import "testing" dan tulis fungsi dengan bentuk

func TestFoo(t *testing.T) {
	...
}

Jalankan go test di direktori tersebut. Program tersebut akan mencari fungsi yang berawalan Test, membuat binari tes, dan menjalankannya.

Lihat dokumentasi Cara Menulis Kode Go, paket testing dan perintah go test untuk lebih rinci.

Dimana atau apa saja fungsi pembantu untuk pengujian?

Paket standar testing pada Go memudahkan menulis unit tes, namun ia tidak memiliki fitur yang pada kerangka bahasa lain seperti fungsi untuk assert. Seperti yang telah dijelaskan pada bagian sebelumnya, Go tidak memiliki assert, dan argumen yang sama juga berlaku untuk fungsi assert pada pengujian. Penanganan eror yang baik artinya membolehkan tes yang lain berjalan saat salah satu darinya gagal, sehingga orang yang melakukan debugging terhadap kegagalan tersebut mendapatkan gambaran secara keseluruhan. Akan lebih berguna bila unit tes melaporkan bahwa fungsi isPrime memberikan jawaban yang salah untuk 2, 3, 5, dan 7 (atau untuk 2, 4, 8, dan 16) daripada melaporkan bahwa isPrime mengembalikan jawaban yang salah untuk 2 sehingga tidak ada pengujian yang berjalan lagi. Pemrogram yang menjalankan tes dan gagal bisa saja tidak paham dengan kode yang melaporkan kesalahan. Waktu yang dihabiskan untuk menulis sebuah pesan eror yang baik nanti akan dibayar saat mendapatkan pengujian gagal.

Alasan lainnya yaitu kerangka pengujian condong berkembang menjadi sebuah bahasa-kecil sendiri, dengan mekanisme kontrol, kondisi, dan pencetakan tersendiri, namun Go sudah memiliki semua kemampuan tersebut; kenapa dibuat lagi? Kita lebih baik menulis unit tes dengan Go; lebih sedikit bahasa yang dipelajari dan pendekatan ini menjaga pengujian tetap mudah digunakan dan dipahami.

Jika jumlah kode yang dibutuhkan untuk menulis eror yang baik tampak repetitif dan terlalu besar, pengujian mungkin lebih baik ditulis dalam bentuk tabel, mengiterasi sebuah daftar masukan dan keluaran yang didefinisikan dalam sebuah struktur data (Go mendukung struktur data anonim). Penulisan tes dan pesan eror yang baik nantinya akan diamortisasi oleh banyaknya kasus-kasus pengujian. Pustaka standar Go mengandung banyak contoh unit tes, seperti pengujian pemformatan pada paket fmt.

Kenapa X tidak ada dalam pustaka standar?

Tujuan dari pustaka standar yaitu untuk mendukung runtime, menghubungkan sistem operasi, dan menyediakan fungsionalitas utama yang dibutuhkan oleh banyak program Go, seperti pemformatan I/O dan jaringan. Ia juga mengandung elemen-elemen penting untuk pemrograman web, termasuk kriptografi dan dukungan untuk standar seperti HTTP, JSON, dan XML.

Tidak ada kriteria yang jelas yang menentukan apa saja yang diikutkan dalam pustaka standar karena selama ini, hanya inilah pustaka Go. Namun ada kriteria yang mendefinisikan apa yang dapat ditambahkan untuk saat sekarang.

Penambahkan pada pustaka standar biasanya sangat jarang dan syarat supaya dapat ditambah biasanya cukup tinggi. Kode yang dimasukan ke dalam pustaka standar membutuhkan biaya pemeliharaan (bahkan ditanggung oleh selain penulis awalnya), subjek terhadap perjanjian kompatibilitas Go 1 (menahan perbaikan dari kesalahan dalam API), dan juga subjek terhadap jadwal rilis Go, yang mencegah perbaikan bug tersedia secara langsung terhadap pengguna dengan cepat.

Umumnya kode yang baru sebaiknya ada di luar pustaka standar dan dapat diakses lewat perkakas go dengan perintah go get. Kode tersebut memiliki pemelihara, siklus rilis, dan jaminan kompatibilitasnya sendiri. Pengguna Go dapat mencari paket dan membaca dokumentasinya di godoc.org.

Walaupun sebenarnya ada beberapa bagian dalam pustaka standar yang seharusnya tidak diikutkan, seperti log/syslog, kami terus memelihara semua yang ada dalam pustaka standar karena perjanjian kompatibilitas Go. Namun kami menyarankan untuk kode yang baru supaya dibuat ditempat yang lain.

Implementasi

Teknologi compiler apa saja yang digunakan untuk membangun compiler Go?

Ada beberapa compiler untuk Go yang siap digunakan, dan ada juga yang dalam tahap pengembangan untuk platform yang beragam.

Compiler bawaan, gc, disertakan dalam distribusi Go untuk mendukung perintah go. Gc awalnya ditulis dalam bahasa C karena kesulitan dalam melakukan bootstrapping--proses yang mana sebuah compiler Go diperlukan untuk menyiapkan lingkungan Go (compiler yang menghasilkan compiler). Namun beberapa hal telah berkembang dan sejak rilis Go 1.5, compiler tersebut sudah dalam bentuk program Go. Compiler tersebut dikonversi dari C ke Go menggunakan perangkat translasi otomatis, seperti yang dijelaskan dalam dokumen rancangan dan wicara berikut. Sekarang compiler bersifat "self-hosting", yang artinya dibutuhkan untuk mengatasi permasalahan bootstrapping. Solusinya yaitu dengan memiliki lingkungan Go yang telah terpasang, seperti halnya seperti pada lingkungan C yang siap digunakan. Sejarah dan dokumentasi untuk cara membangun lingkungan Go yang baru dari sumber dijelaskan di sini dan di sini.

Gc ditulis dalam Go menggunakan recursive descent parser dan menggunakan kostum loader, yang juga ditulis dengan Go berdasarkan loader pada Plan 9, untuk menghasilkan binari dalam format ELF/Mach-O/PE.

Pada awal proyek kami mempertimbangkan menggunakan LLVM untuk gc namun tidak jadi karena terlalu besar dan lambat untuk sesuai dengan tujuan dari performansi yang ingin kami capai. Alasan utama lainnya, dengan LLVM akan membuat lebih sukar untuk melakukan perubahan pada tingkat ABI (Application Binary Interface) dan sekitarnya, seperti manajemen stack, yang dibutuhkan oleh Go namun bukan bagian dari standar C. Implementasi LLVM yang baru mulai merintis hal tersebut sekarang.

Compiler gccgo adalah antar muka yang ditulis dalam bahasa C++ dengan recursive descent parser digabung dengan GCC sebagai back end nya.

Go ternyata menjadi bahasa yang bagus untuk mengimplementasikan compiler Go, walaupun itu bukan tujuan awalnya. Dengan tidak menggunakan self-hosting sejak awal, membolehkan perancangan Go fokus pada tujuan penggunaan awalnya, yaitu sebagai server dalam jaringan. Bila kami memutuskan Go supaya dapat meng- compile dirinya sendiri sedari awal, kami mungkin berakhir dengan bahasa yang berfokus pada konstruksi compiler, yang mana merupakan tujuan yang berguna namun bukan yang dari awal kita harapkan.

Walaupun gc belum menggunakannya, parser dan lexer untuk Go tersedia dalam paket go dan juga ada paket untuk pemeriksaan tipe.

Bagaimana dukungan runtime diimplementasikan?

Sekali lagi karena permasalahan bootstrapping, kode runtime awalnya ditulis dalam C (dengan sedikit assembler) namun telah diterjemahkan ke Go (kecuali bagian assembler). Dukungan runtime gccgo menggunakan glibc. Compiler gccgo mengimplementasikan goroutine menggunakan teknik yang dikenal dengan stack tersegmentasi, yang didukung oleh modifikasi terbaru dari gold linker. Hal yang sama, Gollvm dibangun berdasarkan infrastruktur LLVM.

Kenapa program sederhana saya ukurannya sangat besar?

Linker pada gc membuat binari yang secara statis terkait (statically linked) secara bawaan. Semua program Go oleh karena itu mengikutkan runtime Go, bersamaan dengan informasi tipe runtime yang dibutuhkan untuk mendukung pemeriksaan tipe, refleksi, dan pelacakan stack saat panic.

Sebuah program "hello, world" dengan C yang secara statis terkait menggunakan gcc pada Linux ukurannya sekitar 750 kB, mengikutkan implementasi printf. Sebuah program Go yang sama menggunakan fmt.Printf ukurannya sekitar beberapa megabyte, namun berisi dukungan runtime dan tipe yang lebih kuat dan informasi debugging.

Program Go yang di- compile dengan gc dapat dikaitkan dengan -ldflags=-w untuk menonaktifkan pembangkit DWARF, sehingga menghapus informasi debugging pada binari tanpa ada fungsi yang hilang. Cara ini bisa mengurangi ukuran binari yang cukup besar.

Apakah bisa menghentikan eror dari variabel/import yang tidak digunakan?

Adanya variabel yang tidak terpakai menandakan sebuah bug, sementara import yang tak terpakai memperlambat kompilasi, efeknya bisa besar saat kode program bertambah sepanjang waktu. Karena itu, Go menolak meng- compile program yang memiliki import atau variabel yang tak terpakai, mengganti kenyamanan sementara demi kecepatan dan kejelasan program untuk waktu yang panjang.

Tetap saja, saat menulis kode, sangat umum situasi seperti ini terjadi dan terkadang mengganggu karena harus disunting dahulu sebelum program dapat di- compile.

Beberapa orang meminta supaya menyediakan opsi untuk mematikan pemeriksaan tersebut atau setidaknya menjadi peringatan (warning). Opsi tersebut belum ditambahkan, karena opsi compiler seharusnya tidak mempengaruhi semantik dari bahasa dan karena compiler Go tidak melaporkan peringatan, hanya eror yang mencegah kompilasi.

Ada dua alasan kenapa tidak adanya peringatan, tapi hanya ada eror. Pertama, jika layak untuk dikeluhkan, maka ia patut diperbaiki di dalam kode. (Dan jika tidak patut diperbaiki, maka tidak layak dikeluhkan.) Kedua, membuat compiler yang mencetak peringatan mendorong implementasi yang melaporan kasus-kasus lemah yang membuat kompilasi menjadi berisik, menutup adanya eror yang sebenarnya yang seharusnya diperbaiki.

Cukup mudah untuk mengatasi masalah ini sebenarnya. Cukup gunakan pengidentifikasi kosong untuk membiarkan variabel atau import yang tak terpakai tetap ada saat anda membangun program.

import "unused"

// Deklarasi ini menandai import sedang digunakan dengan mengacu item dari
// paket tersebut.
var _ = unused.Item  // TODO: Hapus sebelum di- commit!

func main() {
	debugData := debug.Profile()
	_ = debugData // Digunakan hanya saat debugging.
	...
}

Sekarang, kebanyakan pemrogram Go menggunakan perkakas goimports, yang secara otomatis menulis ulang sumber kode supaya memiliki import yang benar, menghapus permasalahan import yang tidak terpakai. Program ini dengan mudah dapat disambungkan ke editor supaya dijalankan secara otomatis saat sumber berkas Go disimpan.

Kenapa anti-virus menganggap binari Go terinfeksi?

Hal ini sering terjadi, khususnya pada mesin Windows, dan biasanya itu adalah peringatan yang salah. Program anti-virus komersial terkadang bingung dengan struktur binari Go, yang jarang mereka lihat dari binari yang di- compile pada bahasa lain.

Jika anda baru saja memasang distribusi Go dan sistem melaporkan bahwa ia terinfeksi, itu adalah kesalahan. Untuk lebih teliti, kita bisa memverifikasi berkas yang diunduh dengan membandingkan checksum yang ada pada halaman unduhan.

Jika anda percaya bahwa laporan tersebut salah, silahkan laporkan sebagai bug ke pembuat anti-virus. Mungkin suatu saat nanti anti-virus akan belajar untuk memahami program Go.

Performansi

Kenapa benchmark X berjalan buruk dengan Go?

Salah satu tujuan dari rancangan Go yaitu mendekati performansi C dari program yang sama, namun beberapa benchmark Go berjalan cukup buruk, termasuk beberapa benchmark dalam golang.org/x/exp/shootout. Benchmark yang paling lambat bergantung kepada pustaka yang mana versi performansi yang lebih baik tidak tersedia pada Go. Misalnya, pidigits.go bergantung pada paket multi-presisi math, dan versi C -nya menggunakan GMP (yang ditulis dengan assembler dan dioptimisasi). Benchmark yang bergantung pada ekspresi regular (misalnya, regex-dna.go ) membandingkan paket regex natif dari Go dengan pustaka regex yang lebih matang dan sangat dioptimasi seperti PCRE.

Benchmark game dimenangkan oleh penyetelan lebih lanjut (dari kode yang di benchmark) dan versi Go dari kebanyakan benchmark membutuhkan perhatian tambahan. Jika anda membandingkan program C dan Go (misalnya reverse-complement.go), kita akan melihat kedua bahasa saling berdekatan performansinya daripada yang diindikasikan oleh benchmark game.

Tentu saja, masih ada ruang untuk peningkatan. Compiler yang ada sekarang cukup bagus namun bisa lebih baik lagi, banyak pustaka membutuhkan peningkatan performansi, dan garbage collector belum cukup cepat.

Pada kasus lainnya, Go seringkali sangat kompetitif. Telah ada beberapa peningkatan pada banyak program selama bahasa dan perkakas dikembangkan. Lihat blog tentang profiling program Go untuk contoh yang informatif.

Perbedaan dari C

Kenapa sintaks Go sangat berbeda dengan C?

Selain sintaks deklarasi, perbedaannya tidak terlalu banyak dan berangkat dari dua keinginan. Pertama, sintaks seharusnya ringan, tanpa terlalu banyak kata kunci, pengulangan, atau misteri. Kedua, bahasa Go dirancang supaya mudah dianalisis dan dapat diurai tanpa tabel simbol. Hal ini membuatnya mudah untuk membuat perkakas seperti debugger, penganalisis dependensi, pengekstrak dokumentasi otomatis, plugin untuk IDE, dan lainnya. C dan turunannya terkenal sangat sukar dalam hal ini.

Kenapa deklarasinya terbalik?

Ia tampak terbalik bila kita terlalu terbiasa dengan C. Dalam C, notasinya adalah sebuah variabel dideklarasikan dengan ekspresi yang mengacu tipenya, yang mana merupakan ide yang bagus, tapi tata-bahasa dari tipe dan ekspresi tidak tergabung dengan baik dan hasilnya terkadang membingungkan; yang dapat kita lihat pada pointer ke fungsi. Go pada umumnya memisahkan ekspresi dan sintaks dari tipe dan hal ini menyederhanakan beberapa hal (menggunakan prefiks * untuk pointer adalah pengecualian yang membuktikan aturan ini). Dalam C, deklarasi

	int* a, b;

mendeklarasikan a sebagai pointer tapi tidak b; dalam Go

	var a, b *int

mendeklarasikan keduanya sebagai pointer. Hal ini lebih jelas dan lebih umum. Juga, bentuk deklarasi singkat := menyatakan bahwa deklarasi variabel secara keseluruhan harusnya sama seperti dengan adanya := sehingga

	var a uint64 = 1

memiliki efek yang sama dengan

	a := uint64(1)

Penguraian juga disederhanakan dengan memiliki tata-bahasa yang jelas pada tipe-tipe bukan saja pada tata-bahasa ekspresi; kata kunci seperti func dan chan membuat hal-hal tertentu lebih jelas.

Lihat artikel tentang Sintaks Deklarasi Go (Inggris) untuk lebih rinci.

Kenapa tidak ada operasi aritmatika pada pointer?

Keamanan. Tanpa adanya operasi aritmatika pada pointer maka memungkinkan membuat sebuah bahasa yang tidak pernah memperoleh alamat memory yang salah. Teknologi perangkat keras dan compiler telah berkembang di mana pengulangan menggunakan indeks array bisa seefisien seperti pengulangan dengan aritmatika pointer. Dan juga, tidak adanya aritmatika pointer mempermudah implementasi dari garbage collector.

Kenapa ++ dan -- merupakan penempatan bukan ekspresi? Dan kenapa hanya posfiks, tidak prefiks?

Tanpa adanya aritmatika pointer, kenyamanan dari peningkatan menggunakan operator pre- dan posfiks dihilangkan. Dengan menghapus mereka dari hirarki ekspresi, sintaks ekspresi menjadi mudah dan masalah rumit mengenai urutan evaluasi dari ++ dan -- (misalkan f(i++) dan p[i] = q[++i]) menjadi hilang juga. Penyederhanaan ini signifikan. Untuk posfiks vs. prefiks, salah satunya akan bekerja dengan baik, tapi versi yang posfiks lebih tradisional; adanya prefiks timbul dari STL, pustaka dari sebuah bahasa yang namanya mengandung, secara ironis, peningkatan posfiks (C++).

Kenapa ada tanda kurung kurawal tapi tidak titik-koma? dan kenapa kurawal buka tidak bisa dibaris berikutnya?

Go menggunakan kurung kurawal untuk pengelompokan perintah, sintaks yang cukup akrab bagi yang sering bekerja dengan bahasa dari keluarga C. Titik-koma, sebenarnya untuk parser bukan untuk pemrogram, dan kami ingin menghilangkannya sebisa mungkin. Untuk mencapai tujuan ini, Go meminjam trik dari BCPL: titik-koma yang memisahkan perintah ada dalam tata-bahasa formal namun disisipkan secara otomatis, tanpa perlu melihat (lookahead), oleh lexer disetiap akhir baris yang bisa mengakhiri sebuah perintah. Hal ini bekerja cukup baik pada praktiknya tapi memiliki efek yang memaksa gaya penggunaan kurung kurawal. Misalnya, kurawal buka dari fungsi tidak bisa muncul pada baris selanjutnya.

Beberapa orang memperdebatkan bahwa lexer seharusnya melakukan lookahead supaya kurawal buka bisa ada dibaris selanjutnya. Kami tidak setuju. Secara kode Go ditujukan untuk diformat secara otomatis oleh gofmt, beberapa gaya harus dipilih. Gaya tersebut mungkin berbeda dengan apa yang anda sering gunakan di C atau Java, tapi Go bahasa yang berbeda dan gaya gofmt sama baiknya dengan yang lainnya. Lebih penting lagi, keuntungan dari format (gaya kode) yang tunggal, yang diatur secara programmatis, untuk semua program Go menjadi lebih penting dari kekurangan terhadap gaya kode tertentu.

Kenapa garbage collection? Bukannya itu mahal?

Salah satu sumber pencatatan terbesar dalam program yaitu mengatur waktu-hidup dari objek yang dialokasi. Pada bahasa seperti C, waktu-hidup dari objek dilakukan secara manual, yang bisa menghabiskan waktu pemrogram yang cukup signifikan dan terkadang menyebabkan bug yang buruk. Bahkan pada bahasa seperti C++ atau Rust yang menyediakan mekanisme untuk membantu hal tersebut, mekanisme tersebut bisa memiliki efek yang signifikan pada rancangan perangkat lunak, terkadang menambah biaya pemrograman tersendiri. Kami merasakan perlu untuk menghilangkan biaya pemrograman tersebut, dan dengan perkembangan teknologi garbage collection pada beberapa tahun belakangan ini memberi kita kepercayaan bahwa ia dapat diimplementasikan dengan cukup murah, dengan latensi yang cukup rendah, dan bisa menjadi pendekatan yang cukup bagus untuk pemrograman pada sistem jaringan.

Kesulitan yang umum dari pemrograman konkuren berakar dari permasalahan waktu-hidup dari objek: saat objek dikirim diantara thread maka akan menjadi rumit untuk menjamin bahwa objek tersebut akan dibebaskan secara aman. Garbage collection otomatis membuat kode yang konkuren lebih mudah ditulis. Tentu saja, mengimplementasikan garbage collection dalam lingkungan yang konkuren adalah sebuah tantangan, namun menerapkannya sekali saja (pada runtime) daripada di setiap program dapat membantu semua orang.

Terakhir, disamping konkurensi, garbage collection membuat interface lebih mudah karena mereka tidak membutuhkan bagaimana memory diatur diantara mereka.

Ini bukan berarti bahwa pekerjaan dan teknologi bahasa terbaru dalam bahasa seperti Rust yang membawa ide baru terhadap permasalah pengaturan sumber daya adalah salah; kami sangat mendorong pekerjaan tersebut dan ikut merasa bersemangat melihat bagaimana mereka berkembang. Namun Go mengambil pendekatan lebih tradisional dengan mengatasi waktu-hidup objek lewat garbage collection, dan cukup dengan garbage collection saja.

Implementasi garbage collection pada Go sekarang menggunakan metode kolektor mark-and-sweep. Pada mesin yang memiliki banyak processor, maka kolektor berjalan pada core CPU tersendiri, paralel dengan program utama. Pekerjaan utama pada pengembangan kolektor pada beberapa tahun terakhir telah mengurangi waktu pause bahkan sampai ke sub-milidetik, bahkan untuk heap yang besar. Pekerjaan masih terus dilanjutkan untuk memperbagus algoritma, mengurangi biaya dan latensi lebih rendah lagi, dan mengeksplorasi pendekatan baru. Intisari ISSM oleh Rick Hudson dari tim Go menjelaskan progres sejauh ini dan menyarankan beberapa pendekatan di masa depan.

Mengenai performansi, ingatlah bahwa Go memberikan pemrogram kontrol yang cukup terhadap pengaturan dan alokasi memory, lebih banyak dari bahasa dengan garbage collection pada umumnya. Pemrogram dapat mengurangi biaya garbage collection lebih banyak dengan menggunakan bahasa dengan baik; lihat artikel Profiling program Go untuk melihat contohnya, termasuk demonstrasi dari perkakas profiling pada Go.