Go

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск
Go
Golang.png
Класс языка:

многопоточный, императивный, структурированный

Тип исполнения:

компилируемый

Появился в:

2009

Автор:

Роберт Гризмер, Роб Пайк и Кен Томпсон

Расширение файлов:

.go

Выпуск:

1.7 (15 августа 2016)[1]

Система типов:

строгая, статическая, с выводом типов

Основные реализации:

gc (8g, 6g, 5g), gccgo

Испытал влияние:

Си, Python, Оберон-2, Активный Оберон, Limbo

Лицензия:

лицензия BSD

Сайт:

golang.org[2]

ОС:

кроссплатформенное программное обеспечение

Go (часто также Golang) — компилируемый многопоточный язык программирования, разработанный компанией Google[3]. Первоначальная разработка Go началась в сентябре 2007 года, а его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон[4], занимавшиеся до этого проектом разработки операционной системы Inferno. Официально язык был представлен в ноябре 2009 года. На данный момент его поддержка осуществляется для операционных систем FreeBSD, OpenBSD, Linux, Mac OS X, Windows[5], начиная с версии 1.3 в язык Go включена экспериментальная поддержка DragonFly BSD, Plan 9 и Solaris, начиная с версии 1.4 — поддержка платформы Android.

Название[править | править вики-текст]

Название языка, выбранное компанией Google, практически совпадает с названием языка программирования Go!, созданного Ф. Джи. МакКейбом и К. Л. Кларком в 2003 году.[6] Обсуждение названия ведётся на странице, посвящённой Go[6].

Назначение и основные возможности языка[править | править вики-текст]

Язык Go разрабатывался как язык системного программирования для создания высокоэффективных программ, работающих на современных распределённых системах и многоядерных процессорах. Он может рассматриваться как попытка создать замену языку Си. При разработке уделялось особое внимание обеспечению высокоэффективной компиляции. Программы на Go компилируются в объектный код (хотя доступен и интерпретатор) и не требуют для исполнения виртуальной машины.

Основные возможности языка Go[4]:

  • Go — язык со строгой статической типизацией. Доступен автоматический вывод типов, для пользовательских типов — «утиная типизация».
  • Полноценная поддержка указателей, но без возможности применять к ним арифметические операции, в отличие от C/C++/D.
  • Строковый тип со встроенной поддержкой юникода.
  • Использование динамических массивов, хэш-таблиц, срезов (слайсов), вариант цикла для обхода коллекции.
  • Средства функционального программирования: неименованные функции, замыкания, передача функций в параметрах и возврат функциональных значений.
  • Автоматическое управление памятью со сборщиком мусора.
  • Средства объектно-ориентированного программирования, но без поддержки наследования реализации (наследуются только интерфейсы). По большому счёту, Go является процедурным языком с поддержкой интерфейсов.
  • Средства параллельного программирования: встроенные в язык потоки (go routines), взаимодействие потоков через каналы и другие средства организации многопоточных программ.
  • Достаточно лаконичный и простой синтаксис, основанный на Си, но существенно доработанный, с большим количеством синтаксического сахара.

При этом из языка сознательно исключены:

Язык продолжает развиваться, и разработчики рассматривают возможность включения в язык средств обобщённого программирования. В «Часто задаваемых вопросах»[4] по языку приводятся аргументы против использования утверждений, а наследование без указания типа, наоборот, отстаивается.

Синтаксис[править | править вики-текст]

Синтаксис языка Go схож с синтаксисом языка Си, с отдельными элементами, заимствованными из Оберона и скриптовых языков.

Go — регистро-зависимый язык с полной поддержкой Юникода в строках и идентификаторах
Идентификатор традиционно может быть любой непустой последовательностью, включающей буквы, цифры и знак подчёркивания, начинающийся с буквы и не совпадающий ни с одним из ключевых слов языка Go. При этом под «буквами» понимаются все символы Юникода, относящиеся к категориям «Lu» (буквы верхнего регистра), «Ll» (буквы нижнего регистра), «Lt» (заглавные буквы), «Lm» (буквы-модификаторы) или «Lo» (прочие буквы), под «цифрами» — все символы из категории «Nd» (числа, десятичные цифры). Таким образом, ничто не мешает использовать в идентификаторах, например, русские буквы.
Идентификаторы, различающиеся только регистром букв, являются различными. В языке существует ряд соглашений об использовании заглавных и строчных букв. В частности, в именах пакетов используются только строчные буквы. Все ключевые слова Go пишутся в нижнем регистре.
Естественно, в строковых литералах могут использоваться все символы Юникода без ограничений.
Комментарии и точки с запятой
Go использует оба типа комментариев в стиле Си: строчные (начинающиеся с // …) и блочные (/* … */). Строчный комментарий рассматривается компилятором как перевод строки. Блочный, располагающийся на одной строке — как пробел, на нескольких строках — как перевод строки.
Точка с запятой в Go используется в качестве обязательного разделителя в некоторых операциях (if, for, switch).
Формально также она должна завершать каждую команду, но практически ставить такую точку с запятой в конце строки нет необходимости, так как компилятор в процессе обработки кода сам добавляет точки с запятой в конец каждой строки, которая, без учёта пустых символов, завершается идентификатором, числом, символьным литералом, строкой, ключевыми словами break, continue, fallthrough, return, командой инкремента или декремента (++ или —) или закрывающей круглой, квадратной или фигурной скобкой (важное исключение — запятая в приведённый список не входит). Из этого следует две особенности:
  • Практически точка с запятой нужна только в некоторых форматах операторов if, for, switch и для разделения команд, расположенных на одной строке. Поэтому в коде на Go точек с запятой очень мало.
  • Побочным эффектом автоматической расстановки точек с запятой компилятором стало то, что не в любом месте программы, где допустим пробел, можно использовать перенос строки. В частности, в описаниях, командах инициализации и конструкциях if, for, switch нельзя переносить открывающуюся фигурную скобку на следующую строку:
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 { // ВЕРНО
}
При переносе запятой на следующую строку текущая строка заканчивается идентификатором и в её конце автоматически ставится точка с запятой, что нарушает синтаксис списка (запятая, как уже говорилось выше — исключение из правила, после неё точка с запятой компилятором не добавляется).
Таким образом, язык диктует определённый стиль записи кода. В комплект компилятора Go входит утилита gofmt, обеспечивающая правильное и единообразное форматирование исходных текстов. Все тексты стандартной библиотеки Go отформатированы этой утилитой.
Объявление типа
Синтаксис объявления типа, в основном, решён в духе Паскаля.
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);
При объявлении переменные инициализируются на нулевое значение для данного типа (0 для int, пустая строка для string, nil для указателей).
Объявления можно группировать:
var (
	i int
	m float
)
Автоматический вывод типов
Язык Go поддерживает также автоматический вывод типов. Переменная может быть инициализирована при объявлении, её тип при этом можно не указывать — типом переменной становится тип присваиваемого ей выражения. Для литералов (чисел, символов, строк) стандарт языка определяет конкретные встроенные типы, к которым относится каждое такое значение. Чтобы инициализировать переменную другого типа, к литералу необходимо применить явное преобразование типа.
var v = *p
Присваивания
Внутри функции короткий синтаксис присваивания переменным значения с автоматическим выводом типов напоминает обычное присваивание в Паскале:
v1 := v2 // аналог var v1 = v2
Go допускает множественные присваивания, выполняемые параллельно:
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) // НЕВЕРНО - нет переменной, которой присваивается второй результат
Обязательное использование локальных переменных и псевдопеременная «_»
Любая локальная переменная обязательно должна быть использована, то есть её значение должно участвовать в какой-либо операции в пределах функции, где она объявлена. В отличие от Паскаля и Си, где объявление локальной переменной и последующее её неиспользование или потеря значения, присвоенного локальной переменной (когда переменной присваивается значение, которое затем нигде не читается), может лишь вызывать предупреждение (warning) компилятора, в Go такая ситуация считается языковой ошибкой и приводит к невозможности компиляции программы. Это означает, в частности, что программист не может проигнорировать значение (или одно из значений), возвращаемое вызываемой функцией, просто присвоив его какой-нибудь переменной и отказавшись от его дальнейшего использования. Если возникает необходимость игнорировать одно из значений, возвращаемых вызовом функции, используется предопределённая псевдопеременная с именем «_» (один знак подчёркивания). Она может быть указана в любом месте, где должна быть переменная, принимающая значение. Соответствующее значение не будет присвоено никакой переменной и просто потеряется. Смысл такого архитектурного решения — выявление на стадии компиляции возможной потери результатов вычислений: случайный пропуск обработки значения будет обнаружен компилятором, а использование псевдопеременной «_» укажет на то, что программист сознательно проигнорировал результаты.
Так, в примере выше, если из двух возвращаемых функцией incTwo значений нужно только одно, вместо второй переменной нужно указать «_»:
first := incTwo(1, 2) // НЕВЕРНО
first, _ := incTwo(1, 2) // ВЕРНО, второй результат не используется
Переменная «_» может указываться в списке присваивания любое число раз. Все результаты функции, которым соответствует «_», будут проигнорированы.
Механизм отложенного вызова defer
Отложенный вызов заменяет сразу несколько синтаксических средств, в частности, обработчики исключений и блоки с гарантированным завершением. Вызов функции, которому предшествует ключевое слово defer, параметризуется в той точке программы, где размещён, а выполняется непосредственно перед выходом программы из области видимости, где он был объявлен, независимо от того, как и по какой причине происходит этот выход. Ниже пример использования defer в качестве блока гарантированного завершения[7]
// Функция, копирующая файл
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()
}
Прочие синтаксические различия
Отсутствуют круглые скобки для условных конструкций for и if:
func print(arr []int) {
    n := len(arr)
    for i := 0; i < n; i++ {
        println(arr[i])
    }
}
Любые циклы в Go описываются с помощью конструкции for, синтаксических аналогов while, do-while и других вариантов циклической конструкции в языке нет. Но конструкция for позволяет организовать цикл любого желаемого вида.
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, выглядит следующим образом:

  • В одном из параметров (обычно последнем) функция возвращает объект-ошибку. В качестве типа ошибки обычно используется библиотечный интерфейс error. Типичной практикой является возврат пустого указателя nil, если функция выполнилась без ошибок.
  • После вызова полученный из функции объект проверяется и ошибка, если она возникла, обрабатывается.
func ReadFile(srcName string)(result string, err error) {
    file, err := os.Open("file.txt")
    if err != nil {
        return nil, err 
    } 
    ... // Дальнейшее исполнение функции, если ошибки не было
    return result, nil  // Возврат результата и пустой ошибки, если выполнение успешно
}
  • Проигнорировать ошибку, возвращаемую из функции (в примере выше — не проверить значение переменной err) невозможно, так как инициализация переменной без последующего использования в языке Go приводит к ошибке компиляции. Конечно, этот эффект можно обойти подстановкой вместо err псевдопеременной «_», но это считается плохой практикой и, во всяком случае, явно бросается в глаза при просмотре кода.

Начинающие программисты на Go нередко критикуют его идеологию обработки ошибок, заявляя, что многочисленные проверки ошибок засоряют код и затрудняют его восприятие, тогда как механизм исключений позволяет сосредоточить всю обработку ошибок в блоках catch. В действительности идеология Go вполне позволяет обрабатывать ошибки элегантно и экономно, в литературе по языку описан ряд паттернов для этого (см., например, статью Роба Пайка в официальном блоге Go (русский перевод).

При возникновении фатальных ошибок, делающих невозможным дальнейшее исполнение программы, возникает состояние «паники» (panic). Типичный пример возникновения паники — деление на нуль в процессе вычислений либо обращение за границы массива. В отсутствие обработки паника приводит к аварийному завершению программы с выдачей сообщения об ошибке и трассировки стека вызовов. Для обеспечения отказоустойчивости программы паника тоже может быть перехвачена и обработана. Для это используется механизм отложенного исполнения defer. Инструкция defer, как говорилось выше, получает в качестве параметра вызов функции (то есть фактически создаёт замыкание), который производится тогда, когда исполнение программы покидает текущую область видимости. Это происходит даже в случае паники. Для её перехвата в функции, вызываемой в defer, необходимо вызвать стандартную функцию recover() — она прекращает системную обработку паники и возвращает её причину в виде объекта error. Далее программист может обработать полученную ошибку любым желаемым образом, в том числе и возобновить панику, вызвав стандартную функцию panic(err error).

Многопоточность[править | править вики-текст]

Модель многопоточности Go была создана на основе CSP (англ.) Тони Хоара по типу предыдущих распараллеливаемых языков программирования Occam и Limbo,[4], но также присутствуют такие особенности Пи-исчисления, как канальная передача.

Go дает возможность создать новый поток выполнения программы (go-процедуру) с помощью ключевого слова go, которое запускает анонимную или именованную функцию в заново созданной go-процедуре (аналог сопрограмм). Все go-процедуры в рамках одного процесса используют общее адресное пространство, выполняясь над ОС-потоками, но без жёсткой привязки к последним, что позволяет выполняющейся 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), через которые можно передавать любые значения. Для передачи значения в канал используется <- в качестве бинарного оператора, для получения сообщения из канала — <- в качестве унарного оператора.

Объектно-ориентированное программирование[править | править вики-текст]

Специальное ключевое слово для объявления класса в Go отсутствует, но для любого именованного типа, включая структуры и базовые типы вроде int, можно определить методы, так что в смысле ООП все такие типы являются классами.

type newInt int

Синтаксис определения метода заимствован из языка Оберон-2 и отличается от обычного определения функции тем, что получатель англ. receiver, то есть объект, для которого вызывается метод, явно указывается в описании после ключевого слова func. Если в C++ или Java получатель всегда имеет стандартное имя (this), то в 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++). В Go каждый тип, предоставляющий методы, обозначенные в интерфейсе, может трактоваться как реализация интерфейса, явного объявления не требуется.

type myInterface interface {
	get() int
	set(i int)
}

Объявленный выше тип myType реализует интерфейс myInterface, хотя это нигде не указано явно.

Такой подход к наследованию соответствует некоторым практическим тенденциям современного программирования. Так в знаменитой книге «банды четырёх» (Эрих Гамма и др.) о паттернах проектирования, в частности, написано:

« Зависимость от реализации может повлечь за собой проблемы при попытке повторного использования подкласса. Если хотя бы один аспект унаследованной реализации непригоден для новой предметной области, то приходится переписывать родительский класс или заменять его чем-то более подходящим. Такая зависимость ограничивает гибкость и возможности повторного использования. С проблемой можно справиться, если наследовать только абстрактным классам, поскольку в них обычно совсем нет реализации или она минимальна. »

В Go нет понятия виртуальной функции. Полиморфизм обеспечивается за счёт интерфейсов. Если для вызова метода используется переменная обычного типа, то такой вызов связывается статически, то есть всегда вызывается метод, определённый для данного конкретного типа. Если же метод вызывается для переменной типа «интерфейс», то такой вызов связывается динамически, и в момент исполнения для запуска выбирается тот вариант метода, который определён для типа объекта, фактически присвоенного в момент вызова этой переменной.

Динамическая поддержка объектно-ориентированного программирования для Go осуществлена с помощью проекта GOOP.

История версий[править | править вики-текст]

На текущий момент существует только одна основная версия самого языка Go — версия 1. Версии среды разработки (компилятора, инструментария и стандартных библиотек) Go нумеруются по двухзначной («<версия языка>.<основной релиз>») либо трёхзначной («<версия языка>.<основной релиз>.<дополнительный релиз>») системе. Выпуск новой «двузначной» версии автоматически означает прекращение поддержки предыдущей «двузначной» версии. «Трёхзначные» версии выпускаются для исправления обнаруженных ошибок и проблем с безопасностью; исправления безопасности в таких версиях могут затрагивать две последние «двузначные» версии[8]. На апрель 2016 года последней является версия 1.6.2, вышедшая 20 апреля 2016 года.

С марта 2012 года, когда была представлена версия Go 1, вышли следующие основные версии:

  • go 1 — 28 марта 2012 года — Первая официальная версия; зафиксированы библиотеки, внесены изменения в синтаксис.
  • go 1.1 — 13 мая 2013 года — целочисленное деление на нуль стало синтаксической ошибкой, введены method values — замыкания метода с заданным значением-источником, в некоторых случаях стало необязательным использование return; в реализации разрешено выбирать между 32- и 64-разрядным представлением стандартного целочисленного типа, изменения в поддержке Unicode.
  • go 1.2 — 1 декабря 2013 года — любая попытка обратиться по указателю nil гарантированно вызывает панику, введены трёхиндексные срезы. Доработки Unicode.
  • go 1.3 — 18 июня 2014 года — изменена модель распределения памяти; удалена поддержка платформы Windows 2000, добавлены DragonFly BSD, FreeBSD, NetBSD, OpenBSD, Plan 9, Solaris.
  • go 1.4 — 10 декабря 2014 года — разрешена конструкция цикла «for range x { … }» (цикл по коллекции без использования переменных), запрещено двойное автоматическое разыменование при вызове метода (если x **T — двойной указатель на тип T, то вызов метода для x в виде x.m() — запрещён); в реализацию добавлена поддержка платформ Android, NaCl on AMR, Plan9 on AMD64.
  • go 1.5 — 19 августа 2015 года — в записи map-литералов указание типа каждого элемента сделано факультативным, в реализации среда исполнения и компилятор полностью переписаны на Go и ассемблере, более не используется язык Си.
  • go 1.6 — 17 февраля 2016 года — изменений в языке нет, среда портирована на платформы Linux on 64-bit MIPS, Android on 32-bit x86 (android/386), изменения в инструментарии.
  • go 1.7 - 16 августа 2016 — уменьшены время компиляции и размер бинарных файлов, увеличена скорость работы и в стандартную библиотеку добавлен пакет context.

Реализации[править | править вики-текст]

На данный момент существуют два основных компилятора Go:

  • 6g (и сопутствующие ему инструменты, вместе известные под названием gc) написан на Си с применением yacc/Bison для парсера
  • Gccgo — ещё один компилятор Go с клиентской частью, написанной на C++, и рекурсивным парсером, совмещённым со стандартным бэк-эндом GCC[9]. Поддержка Go доступна в GCC, начиная с версии 4.6[10].

А так же перспективные разработки:

  • llgo — прослойка для компиляции в llvm, написанная на самом go (находится в разработке)[11].
  • SSA interpreter — интерпретатор, позволяющий запускать программы на go.

Средства разработки[править | править вики-текст]

Среда разработки Go содержит всего три инструмента командной строки: компилятор go, обеспечивающий построение объектных файлов, и вспомогательные утилиты godoc и gofmt, предназначенные, соответственно, для документирования программ и для форматирования исходного кода по стандартным правилам. Для отладки программ может использоваться отладчик gdb.

Единственная на текущий момент IDE, изначально ориентированная на язык Go, это LiteIDE[1] (ранее проект назывался GoLangIDE). Это небольшая по объёму оболочка, написанная с использованием 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)
}

Примечания[править | править вики-текст]

Ссылки[править | править вики-текст]