Go | |
![]() | |
Класс языка | многопоточный, императивный, структурированный |
---|---|
Тип исполнения | компилируемый |
Появился в | 2009 |
Автор | Роберт Гризмер, Роб Пайк и Кен Томпсон |
Расширение файлов |
.go |
Выпуск | |
Система типов | строгая, статическая, с выводом типов |
Основные реализации: | gc (8g, 6g, 5g), gccgo |
Испытал влияние | Си, Паскаль/Модула/Оберон[2], Limbo |
Лицензия | BSD[3] |
Сайт | golang.org |
Go (часто также Golang) — компилируемый многопоточный язык программирования, разработанный внутри компании Google[4]. Первоначальная разработка Go началась в сентябре 2007 года, а его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон[5], занимавшиеся до этого проектом разработки операционной системы Inferno. Официально язык был представлен в ноябре 2009 года. На данный момент его поддержка осуществляется для операционных систем FreeBSD, OpenBSD, Linux, macOS, Windows[6], начиная с версии 1.3 в язык Go включена экспериментальная поддержка DragonFly BSD, Plan 9 и Solaris, начиная с версии 1.4 — поддержка платформы Android.
Название языка, выбранное компанией Google, практически совпадает с названием языка программирования Go!, созданного Ф. Джи. МакКейбом и К. Л. Кларком в 2003 году[7]. Обсуждение названия ведётся на странице, посвящённой Go[7].
Язык Go разрабатывался как язык программирования для создания высокоэффективных программ, работающих на современных распределённых системах и многоядерных процессорах. Он может рассматриваться как попытка создать замену языкам Си и C++[8]. По словам Роба Пайка[8], «Go был разработан для решения реальных проблем, возникающих при разработке программного обеспечения в Google». В качестве основных таких проблем он называет:
В результате получился язык, «который не стал прорывом, но тем не менее явился отличным инструментом для разработки крупных программных проектов»[8].
Go — компилируемый язык. Предполагается, что программы на Go будут транслироваться компилятором в объектный код целевой аппаратной платформы и в дальнейшем исполняться непосредственно, не требуя виртуальной машины. Архитектура языка изначально проектировалась так, чтобы обеспечить быструю компиляцию в эффективный объектный код. Хотя для Go доступен и интерпретатор, практически в нём нет большой потребности, так как скорость компиляции достаточно высока для обеспечения интерактивной разработки.
Основные возможности языка Go[5]:
При этом в язык сознательно не был включён ряд популярных средств.
Язык продолжает развиваться, и разработчики рассматривают возможность включения в язык средств обобщённого программирования. В «Часто задаваемых вопросах»[5] по языку приводятся аргументы против использования утверждений, а наследование без указания типа, наоборот, отстаивается.
Синтаксис языка Go схож с синтаксисом языка Си, с отдельными элементами, заимствованными из Оберона и скриптовых языков.
func g() // !
{ // НЕВЕРНО
}
if x {
} // !
else { // НЕВЕРНО
}
func g(){ // ВЕРНО
}
if x {
} else { // ВЕРНО
}
func f(i // !
, k int // !
, s // !
, t string) string { // НЕВЕРНО
}
func f(i,
k int,
s,
t string) string { // ВЕРНО
}
type PostString string // Тип "строка", аналогичен встроенному
type StringArray []string // Тип-массив с элементами строкового типа
type Person struct { // Тип-структура
name string // поле стандартного типа string
post PostString // поле ранее объявленного пользовательского строкового типа
bdate time.Time // поле типа Time, импортированного из пакета time
edate time.Time
chief *Person // поле-указатель
infer [](*Person) // поле-массив
}
type InOutString chan string // тип-канал для передачи строк
type CoompareFunc ( a, b interface {} ) int // тип-функция.
type TitleString=string // "TitleString" - псевдоним для встроенного типа string
type Integer=int64 // "Integer" - псевдоним для встроенного 64-разрядного целого типа
Go | C++ |
---|---|
var v1 int
var v2 string
var v3 [10]int
var v4 []int
var v5 struct { f int }
var v6 *int
var v7 map[string]int
var v8 func(a int) int
|
int v1;
const std::string v2; (примерно)
int v3[10];
int* v4; (примерно)
struct { int f; } v5;
int* v6; (но нет арифметики для указателей)
std::unordered_map* v7; (примерно)
int (*v8)(int a);
|
var (
i int
m float
)
var v = *p
v1 := v2 // аналог var v1 = v2
i, j = j, i // Поменять местами значения i и j.
func f(i, j, k int, s, t string) string { }
func f(a, b int) (int, string) {
return a+b, "сложение"
}
func incTwo(a, b int) (c, d int) {
c = a+1
d = b+1
return
}
first, second := incTwo(1, 2) // first = 2, second = 3
first := incTwo(1, 2) // НЕВЕРНО - нет переменной, которой присваивается второй результат
first := incTwo(1, 2) // НЕВЕРНО
first, _ := incTwo(1, 2) // ВЕРНО, второй результат не используется
// Функция, копирующая файл
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName) // Открытие файла-источника
if err != nil { // Проверка
return // Если неудача, возврат с ошибкой
}
// Если пришли сюда, то файл-источник был успешно открыт
defer src.Close() // Отложенный вызов: src.Close() будет вызван по завершении CopyFile
dst, err := os.Create(dstName) // Открытие файла-приёмника
if err != nil { // Проверка и возврат при ошибке
return
}
defer dst.Close() // Отложенный вызов: dst.Close() будет вызван по завершении CopyFile
return io.Copy(dst, src) // Копирование данных и возврат из функции
// После всех операций будут вызваны: сначала dst.Close(), затем src.Close()
}
func print(arr []int) {
n := len(arr)
for i := 0; i < n; i++ {
println(arr[i])
}
}
for { // бесконечный цикл
// Выход из цикла должен быть организован вручную,
// обычно это делается с помощью конструкций return или break
}
for i < 10 { // цикл выполняется, пока условие истинно (аналог while в Си)
}
for i := 0; i < 10; i++ { // точно то же самое, что цикл for в Си
}
var arr []int
for i, v := range arr { // цикл по элементам массива или среза arr
// i - индекс текущего элемента
// v - копия текущего элемента (аналог arr[i])
}
for i := range arr {
// используется только индекс
}
for _, v := range arr {
// используется только элемент массива
}
for range arr { //цикл по коллекции без переменных - поддерживается с версии 1.4
// Может использоваться, когда коллекция используется только в качестве счётчика итераций,
// а само текущее значение не требуется.
}
Язык Go не поддерживает типичный для большинства современных языков синтаксис структурной обработки исключений (блоки try-catch); авторы сочли, что его применение провоцирует программиста на игнорирование ошибок.
Для обработки ошибок создатели языка рекомендуют использовать возврат ошибки как одного из результатов вызова функции и проверку его на месте вызова. Типичный порядок, выдерживаемый в стандартных библиотеках Go, выглядит следующим образом:
func ReadFile(srcName string)(result string, err error) {
file, err := os.Open("file.txt")
if err != nil {
// Генерация новой ошибки с уточняющим текстом
return nil, fmt.Errorf("Ошибка при чтении файла %s: %g\n", srcName, err)
}
... // Дальнейшее исполнение функции, если ошибки не было
return result, nil // Возврат результата и пустой ошибки, если выполнение успешно
}
Обработка ошибок без использования исключений является одной из наиболее часто критикуемых особенностей Go. Критики заявляют, что многочисленные проверки ошибок засоряют код и затрудняют его восприятие, тогда как механизм исключений позволяет сосредоточить всю обработку ошибок в блоках catch
. В действительности идеология Go вполне позволяет обрабатывать ошибки элегантно и экономно, в литературе по языку описан ряд паттернов для этого (см., например, статью Роба Пайка в официальном блоге Go, русский перевод).
При возникновении фатальных ошибок, делающих невозможным дальнейшее исполнение программы, возникает состояние «паники» (panic). Типичный пример возникновения паники — деление на ноль в процессе вычислений либо обращение за границы массива. В отсутствие обработки паника приводит к аварийному завершению программы с выдачей сообщения об ошибке и трассировки стека вызовов. Для обеспечения отказоустойчивости программы паника тоже может быть перехвачена и обработана. Для этого используется механизм отложенного исполнения defer. Инструкция defer, как говорилось выше, получает в качестве параметра вызов функции (то есть фактически создаёт замыкание), который производится тогда, когда исполнение программы покидает текущую область видимости. Это происходит даже в случае паники. Для её перехвата в функции, вызываемой в defer, необходимо вызвать стандартную функцию recover() — она прекращает системную обработку паники и возвращает её причину в виде объекта error. Далее программист может обработать полученную ошибку любым желаемым образом, в том числе и возобновить панику, вызвав стандартную функцию panic(err error).
Модель многопоточности Go была создана на основе CSP Тони Хоара по типу предыдущих распараллеливаемых языков программирования Occam и Limbo[5], но также присутствуют такие особенности как Пи-исчисления и канальная передача.
Go дает возможность создать новый поток выполнения программы с помощью ключевого слова go, которое запускает анонимную или именованную функцию в заново созданной go-процедуре (термин, используемый в Go для обозначения сопрограмм). Все go-процедуры в рамках одного процесса используют общее адресное пространство, выполняясь над ОС-потоками, но без жёсткой привязки к последним, что позволяет выполняющейся go-процедуре покидать поток с заблокированной go-процедурой (ждущей, например, отправки или приема сообщения из канала) и продолжать работу далее. Библиотека времени исполнения включает мультиплексор, обеспечивающий разделение доступного количества системных ядер между go-процедурами. Имеется возможность ограничить максимальное число физических процессорных ядер, на которых будет исполняться программа. Самостоятельная поддержка go-процедур runtime-библиотекой Go позволяет без затруднений использовать в программах огромные количества go-процедур, намного превышающие предельное число поддерживаемых системой потоков.
func server(i int) {
for {
print(i)
time.Sleep(10)
}
}
go server(1)
go server(2)
В выражении go можно использовать замыкания.
var g int
go func(i int) {
s := 0
for j := 0; j < i; j++ { s += j }
g = s
}(1000)
Для связи между go-процедурами используются каналы (встроенный тип chan), через которые можно передавать любые значения. Для передачи значения в канал используется <-
в качестве бинарного оператора, для получения сообщения из канала — <-
в качестве унарного оператора.
Помимо CSP или совместно с механизмом канальной передачи Go позволяет использовать и обычную модель синхронизированного взаимодействия потоков через общую память, с использованием типовых средств синхронизации доступа, таких как мьютексы. Особенностью многопоточности в Go является то, что go-процедура никак не идентифицируется и не является языковым объектом, на который можно сослаться при вызове функций или который можно поместить в контейнер. Соответственно, отсутствуют средства, позволяющие непосредственно влиять на исполнение сопрограммы извне её, такие как приостановка и последующий запуск, изменение приоритета, ожидание завершения одной сопрограммы в другой, принудительное прерывание исполнения. Любые воздействия на go-процедуру (кроме завершения главной программы, которое автоматически завершает все go-процедуры) могут выполняться только через каналы или иные механизмы синхронизации. Ниже показана типовой код, запускающий несколько go-процедур и ожидающий их завершения с помощью синхронизирующего объекта WaitGroup из системного пакета sync. Этот объект содержит счётчик, первоначально с нулевым значением, который может увеличиваться и уменьшаться, и метод Wait(), который вызывает приостановку текущего потока и ожидание до тех пор, пока счётчик не обнулится.
func main() {
var wg sync.WaitGroup // Создание waitgroup. Исходное значение счётчика - 0
logger := log.New(os.Stdout, "", 0) // log.Logger - потоково-безопасный тип для вывода
for _, arg := range os.Args {
wg.Add(1) // Увеличение счётчика waitgroup на единицу
// Запуск go-процедуры для обработки параметра arg
go func(word string) {
// Отложенное уменьшение счётчика waitgroup на единицу.
// Произойдёт по завершении функции.
defer wg.Done()
logger.Println(prepareWord(word)) // Выполнение обработки и вывод результата
}(arg)
}
wg.Wait() // Ожидание, пока счётчик в waitgroup wg не станет равным нулю.
}
Здесь перед созданием каждой новой go-процедуры счётчик объекта wg увеличивается на единицу, а по завершении go-процедуры — уменьшается на единицу. В результате в цикле, запускающем обработку аргументов, к счётчику будет добавлено столько единиц, сколько запущено go-процедур. По завершении цикла вызов wg.Wait() вызовет приостановку главной программы. Когда каждая из go-процедур завершается, она уменьшает счётчик wg на единицу, поэтому ожидание главной программы закончится тогда, когда завершится столько go-процедур, сколько было запущено. Без последней строки главная программа, запустив все go-процедуры, немедленно завершилась бы, прервав исполнение тех из них, которые не успели выполниться.
Несмотря на наличие встроенной в язык многопоточности не все стандартные языковые объекты являются потоко-безопасными. Так, стандартный тип map (отображение) не потоко-безопасен. Создатели языка объяснили такое решение соображениями эффективности, так как обеспечение безопасности для всех подобных объектов привело бы к дополнительным накладным расходам, которые далеко не всегда являются обязательными (те же операции с отображениями могут быть частью более крупных операций, которые уже синхронизированы программистом, и тогда дополнительная синхронизация лишь усложнит и замедлит программу). Начиная с версии 1.9 в библиотечный пакет sync, содержащий средства поддержки параллельной обработки, добавлен потоко-безопасный тип sync.Map, который при необходимости можно использовать. Также можно обратить внимание на использованный в последнем примере способ вывода результатов. Обычно вывод на консоль в Go производится функциями пакета fmt (Printf, Println и так далее). Однако этот пакет не является потоко-безопасным, и для использования его функций в go-процедурах пришлось бы дополнительно синхронизировать их вызовы. В примере проблема обойдена с помощью использования потоко-безопасного типа log.Logger, который также содержит методы для вывода текстов и может быть использован для вывода на консоль.
Специальное ключевое слово для объявления класса в Go отсутствует, но для любого именованного типа, включая структуры и базовые типы вроде int, можно определить методы, так что в смысле ООП все такие типы являются классами.
type newInt int
Синтаксис определения метода заимствован из языка Оберон-2 и отличается от обычного определения функции тем, что после ключевого слова func в круглых скобках объявляется так называемый «получатель» (англ. receiver), то есть объект, для которого вызывается метод, и тип, к которому относится метод. Если в традиционных объектных языках получатель обычно имеет стандартное имя (в C++ или Java — «this», в ObjectPascal — «self» и т. п.), то в Go он указывается явно и его имя может быть любым правильным Go-идентификатором.
type myType struct { i int }
func (p *myType) get() int { return p.i }
func (p *myType) set(i int) { p.i = i }
Наследование классов (структур) в Go формально отсутствует, но имеется технически близкий к нему механизм встраивания (англ. embedding). В описании структуры можно использовать так называемое анонимное поле — поле, для которого не указывается имя, а только тип. В результате такого описания все элементы встраиваемой структуры станут одноимёнными элементами встраивающей. В отличие от классического наследования, встраивание не влечёт полиморфное поведение (объект встраивающего класса не может выступать в качестве объекта встраиваемого без преобразования типов).
Невозможно явно описать методы для безымянного типа (синтаксис просто не даёт возможности указать тип получателя в методе), но это ограничение можно легко обойти путём встраивания именованного типа с необходимыми методами.
Полиморфизм классов обеспечивается в Go механизмом интерфейсов (похожи на полностью абстрактные классы в C++). Интерфейс описывается с помощью ключевого слова interface, внутри (в отличие от описаний типов-классов) описания объявляются предоставляемые интерфейсом методы.
type myInterface interface {
get() int
set(i int)
}
В Go нет необходимости явно указывать, что некоторый тип реализует определённый интерфейс. Вместо этого действует правило: каждый тип, предоставляющий методы, обозначенные в интерфейсе, может быть использован как реализация этого интерфейса. Объявленный выше тип myType
реализует интерфейс myInterface
, хотя это нигде не указано явно, поскольку он содержит методы get()
и set()
, сигнатуры которых соответствуют описанным в myInterface
.
Аналогично классам, интерфейсы допускают встраивание:
type mySecondInterface interface {
myInterface // то же, что явно описать get() int; set(i int)
change(i int) int
}
Здесь интерфейс mySecondInterface наследует интерфейс myInterface (то есть объявляет, что предоставляет методы, входящие в myInterface) и дополнительно объявляет один собственный метод change()
.
Хотя в принципе возможно построить в программе на Go и иерархию интерфейсов, как это практикуется в других объектных языках, и даже имитировать наследование, это считается плохой практикой. Язык диктует не иерархический, а композиционный подход к системе классов и интерфейсов. Классы-структуры при таком подходе вообще могут оставаться формально независимыми, а интерфейсы не объединяются в единую иерархию, а создаются для конкретных применений, при необходимости встраивая уже имеющиеся. Неявная реализация интерфейсов в Go обеспечивает чрезвычайную гибкость этих механизмов и минимум технических затруднений при их использовании.
Такой подход к наследованию соответствует некоторым практическим тенденциям современного программирования. Так в знаменитой книге «банды четырёх» (Эрих Гамма и др.) о паттернах проектирования, в частности, написано:
![]() | Зависимость от реализации может повлечь за собой проблемы при попытке повторного использования подкласса. Если хотя бы один аспект унаследованной реализации непригоден для новой предметной области, то приходится переписывать родительский класс или заменять его чем-то более подходящим. Такая зависимость ограничивает гибкость и возможности повторного использования. С проблемой можно справиться, если наследовать только абстрактным классам, поскольку в них обычно совсем нет реализации или она минимальна. | ![]() |
В Go нет понятия виртуальной функции. Полиморфизм обеспечивается за счёт интерфейсов. Если для вызова метода используется переменная обычного типа, то такой вызов связывается статически, то есть всегда вызывается метод, определённый для данного конкретного типа. Если же метод вызывается для переменной типа «интерфейс», то такой вызов связывается динамически, и в момент исполнения для запуска выбирается тот вариант метода, который определён для типа объекта, фактически присвоенного в момент вызова этой переменной.
Динамическая поддержка объектно-ориентированного программирования для Go осуществлена с помощью проекта GOOP.
В силу молодости языка его критика сосредоточена, главным образом, в Интернет-статьях, обзорах и на форумах. Одной из площадок, где аккумулируются критические замечания в адрес самого языка и его существующих реализаций, является сам сайт проекта Go. В основном критика языка фокусируется на отсутствии в нём тех или иных популярных средств, предоставляемых другими языками. В первую очередь это средства обобщённого программирования (generics) и структурная обработка исключений. Также часто критикуется отсутствие «полноценного ООП» (фактически — наследования реализации), перегрузки функций, переопределения операторов. Типичным ответом на критику подобного рода является утверждение, что средства, за отсутствие которых критикуется язык, не являются действительно необходимыми либо имеют нежелательные побочные эффекты использования, а то, что делается с их помощью в других языках, может быть реализовано в Go его имеющимися средствами.
Популярность Go в последние годы росла: с 2014 года в рейтинге TIOBE он поднялся с 65-го места на 18-е, текущее значение рейтинга составляет около 1 %. По результатам опроса сайта dou.ua[10] язык Go в 2018 году стал девятым в списке самых используемых и шестым в списке языков, которым отдают личное предпочтение разработчики. При этом с 2012 года, когда вышел первый публичный релиз, использование языка неуклонно растёт. В опубликованном на сайте проекта Go списке компаний, использующих язык в промышленных разработках, насчитывается несколько десятков наименований.
Накоплен большой массив библиотек различного назначения. Поскольку исторически язык ориентировался, в основном, на написание бэк-энда (серверной части веб-проектов), системные библиотеки более развиты в части поддержки сетевых технологий, преобразования и обработки данных, чем в части средств реализации интерфейса пользователя. Практически единственным стандартным способом создать не-консольное клиентское приложение является написание локального сервера с веб-интерфейсом. Существуют созданные сторонними разработчиками библиотеки, обеспечивающие интерфейс с популярными UI-фреймворками, такими как GTK+ и Qt, но они довольно громоздки. Имеется также несколько разработок UI-фреймворков на самом Go, но ни один из этих проектов не достиг уровня промышленной применимости. В 2015 году на конференции GopherCon 2015 в Денвере один из создателей языка, Роберт Грисмер, отвечая на вопросы, согласился, что Go нуждается в пакете UI, но заметил, что такой пакет должен быть универсальным, мощным и мультиплатформенным, что делает его разработку длительным и непростым процессом. Во всяком случае, на конец 2018 года вопрос о реализации клиентского GUI на Go остаётся открытым.
Cуществует только одна основная версия самого языка Go — версия 1. Версии среды разработки (компилятора, инструментария и стандартных библиотек) Go нумеруются по двухзначной («<версия языка>.<основной релиз>») либо трёхзначной («<версия языка>.<основной релиз>.<дополнительный релиз>») системе. Выпуск новой «двузначной» версии автоматически означает прекращение поддержки предыдущей «двузначной» версии. «Трёхзначные» версии выпускаются для исправления обнаруженных ошибок и проблем с безопасностью; исправления безопасности в таких версиях могут затрагивать две последние «двузначные» версии[11].
Авторы декларировали[12] стремление к сохранению, насколько это возможно, обратной совместимости в пределах основной версии языка. Это означает, что до выхода релиза Go 2 почти любая программа, созданная в среде Go 1, будет корректно компилироваться в любой последующей версии Go 1.x и выполняться без ошибок. Исключения возможны, но они немногочисленны. Однако бинарной совместимости между релизами не гарантируется, так что программа при переходе на более поздний релиз Go должна быть полностью перекомпилирована.
С марта 2012 года, когда была представлена версия Go 1, вышли следующие основные версии:
С 2017 года ведётся активная подготовка к выпуску следующей базовой версии языка, имеющей условное обозначение «Go 2.0»[13]. Проводится сбор замечаний к текущей версии и предложений по преобразованиям, аккумулируемых на wiki-сайте проекта[14]. Точных сроков выхода новой версии не называется, было лишь сказано, что процесс подготовки займёт «около двух лет», причём часть новых элементов языка будет включена уже в очередные релизы версии Go 1 (разумеется, только те, которые не нарушают обратной совместимости).[13] В числе возможных принципиальных новшеств назывались явно объявляемые константные значения, новый механизм обработки ошибок и средства обобщённого программирования. В сети доступны проекты нововведений. 28 августа 2018 года в официальном блоге разработчиков был опубликован ролик, ранее представленный на конференции Gophercon 2018, в котором демонстрируются черновые варианты нового дизайна обработки ошибок и механизма обобщённых функций.
Обработка ошибок базируется на тех же принципах, что и в первой версии, но дополнена средствами, позволяющими избавиться от постоянных проверок:
func CopyFile(src, dst string) error {
handle err {
return fmt.Errorf("copy %s to %s error: %v", src, dst, err)
}
r := check os.Open(src)
defer r.Close()
w := check os.Create(dst)
defer w.Close()
check io.Copy(w, r)
check w.Close()
return nil
}
Как видно из примера (простейшая функция копирования файла), предлагается добавить две конструкции: check
и handle
. Первая используется в вызове и «перехватывает» последнее возвращаемое функцией значение, предполагая, что это — объект error
; если он равен nil, то остальные значения передаются по назначению, если же нет, то управление передаётся в блок handle
, размещённый выше по тексту в этой же функции. В данном примере при ошибке в любом из вызовов функция CopyFile
вернёт объект-ошибку с текстом, содержащим её параметры и текст ошибки, полученной в неудачной операции. Возможно наличие нескольких блоков handle
в одной функции, в этом случае они будут выполняться в обратном порядке, до первого оператора return
, который обязательно должен находиться в одном из них. В отсутствие блоков обработки конструкция check
приведёт к тому, что произойдёт немедленный возврат из текущей функции, причём в последний по счёту результат будет передана полученная при вызове ошибка, а остальные результаты, если они есть, сохранят свои текущие значения.
Механизм обобщённых функций основывается на двух элементах: «тип-параметр» и «контракт»[15].
contract Equal(t T) {
t == t
}
func Uniq(type T Equal)(in <-chan T) <-chan T {
out := make(chan T)
go func() {
v := <-in
out <- v
for next := range in {
if v != next {
v = next
out <- v
}
}
}()
}
...
src := make(chan string)
...
// Вызов обобщённой функции для конкретного типа string
for s := range Uniq(string)(src) {
fmt.Println(s)
}
Тип параметра обобщённой функции (здесь это T
) описывается в её заголовке с ключевым словом type
и указанием ранее описанного контракта (здесь — Equal
). Описание контракта содержит код, который обрабатывается компилятором как обычная функция, но без генерации кода. Если некоторый тип будет использован для конкретизации функции Uniq, то для этого типа компилятор обработает описание контракта Equal, и компиляция программы будет успешна только в случае, если эта обработка не приведёт к обнаружению ошибки. В данном случае проверка будет успешной, если к значениям типа может применяться операция проверки на равенство. Используя контракты, разработчики рассчитывают избежать ситуации, когда в конкретизации обобщённого кода используются типы, не обладающие необходимыми качествами, что может приводить к ошибкам, проявляющимся лишь на этапе исполнения.[15]
На данный момент существуют два основных компилятора Go:
А также перспективные разработки:
Среда разработки Go содержит несколько инструментов командной строки: утилиту go, обеспечивающий компиляцию, тестирование и управление пакетами, и вспомогательные утилиты godoc и gofmt, предназначенные, соответственно, для документирования программ и для форматирования исходного кода по стандартным правилам. Для вывода полного списка инструментов необходимо вызвать утилиту go без указания аргументов. Для отладки программ может использоваться отладчик gdb. Независимыми разработчиками представлено большое количество инструментов и библиотек, предназначенных для поддержки процесса разработки, главным образом, для облегчения анализа кода, тестирования и отладки.
На текущий момент доступны две IDE, изначально ориентированные на язык Go — это проприетарная GoLand [1] (разрабатывается в JetBrains на платформе IntelliJ) и свободная LiteIDE[2] (ранее проект назывался GoLangIDE). LiteIDE — небольшая по объёму оболочка, написанная на С++ с использованием qt4, с помощью которой можно выполнять весь базовый набор действий по разработке ПО на Go: создание кода (редактор поддерживает подсветку синтаксиса и автодополнение), компиляцию, отладку, форматирование кода, запуск инструментов.
Также Go поддерживается плагинами в универсальных IDE Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus и других. Автоподсветка, автодополнение кода на Go и запуск утилит компиляции и обработки кода реализованы в виде плагинов к более чем двум десяткам распространённых текстовых редакторов под различные платформы, в том числе Emacs, Vim, Notepad++, jEdit.
Ниже представлен пример программы «Hello, World!» на языке Go.
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Пример реализации команды Unix echo:
package main
import (
"os"
"flag" // парсер параметров командной строки
)
var omitNewLine = flag.Bool("n", false, "не печатать знак новой строки")
const (
Space = " "
NewLine = "\n"
)
func main() {
flag.Parse() // Сканирование списка аргументов и установка флагов
var s string
for i := 0; i < flag.NArg(); i++ {
if i > 0 {
s += Space
}
s += flag.Arg(i)
}
if !*omitNewLine {
s += NewLine
}
os.Stdout.WriteString(s)
}