Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~18 мин +50 XP

Структура программы: package, import, func main

Структура программы на Go собирается по одной схеме: package сверху, import ниже, func main как точка входа. Схема строгая до занудства, и в этом скрыта хорошая новость: прочитав этот урок, вы сможете разобрать скелет любой программы на языке — от учебного примера до исходников Kubernetes. Разберём каждый слой, правила компиляции и ошибки, которые новички ловят первыми.

Содержание урока · 8 разделов

Откройте любой Go-файл — из документации, из открытого проекта на GitHub, из этого курса — и первые строки будут один в один: объявление пакета, затем импорты. Это не привычка сообщества, а часть языка: компилятор не начнёт сборку, если файл не объявил пакет, откажется работать с неиспользованным импортом и не соберёт программу без функции main. К такой строгости привыкаешь быстрее, чем кажется. Сначала она мешает — рука тянется просто стереть ненужную строку и пойти дальше; зато потом, читая чужой код, вы всегда знаете, где смотреть: что этот файл подключает и с какой функции начинается выполнение.

package: объявление пакета#

Первая строка любого файла .go — package имя. Пакет — единица компиляции и пространство имён: все файлы одной папки обязаны объявлять один и тот же пакет, иначе компилятор соберёт их в одну кучу неправильно и пожалуется. Специальный случай — package main: так помечаются программы, а не библиотеки. Команда go build собирает main-пакет в исполняемый файл; любой другой пакет становится библиотекой, из которой чужие программы импортируют функции. Отсюда правило, которое ловит всех новичков: package main живёт только там, где вы хотите получить запускаемую программу. Названия пакетов короткие, в нижнем регистре, без подчёркиваний: main, fmt, strings, http. Имя пакета совпадает с именем папки (main — исключение), и по этому имени вы обращаетесь к содержимому: fmt.Println — это пакет fmt, точка, функция Println из него.

Минимальная программа целиком

GO
package main

import "fmt"

func main() {
        fmt.Println("Скелет программы")
}

Три слоя: объявление пакета, импорт стандартной библиотеки, точка входа. В файле больше ничего нет — и это уже полноценная программа, которую собирает любой компилятор Go.

import: подключение пакетов#

Секция import перечисляет пакеты, чьи функции используются в файле. Один пакет — одна строка в кавычках; если пакетов несколько, их группируют в скобках — так принято в стандартной библиотеке и во всех крупных проектах. Содержимое пакета вызывается через точку: strings.ToUpper, fmt.Println. Путь в кавычках для стандартной библиотеки — просто имя пакета, а для чужого кода будет длиннее, с доменом проекта, — к модулям вернёмся в продвинутом курсе. Теперь неприятная часть, о которой лучше узнать заранее: компилятор требует, чтобы каждый импорт был использован. Ни предупреждений, ни «потом уберу» — неиспользованный импорт останавливает сборку. Точно так же он поступает с неиспользованными переменными. Это раздражает ровно до первого раза, когда вам достаётся чужой рефакторинг на две тысячи строк, — и в нём нет ни одного мёртвого импорта.

Групповой импорт

GO
package main

import (
        "fmt"
        "strings"
)

func main() {
        fmt.Println(strings.ToUpper("imports работают"))
}

Два пакета в одной секции import. Из fmt берём печать, из strings — функцию ToUpper, которая поднимает регистр строки. Пакет используется через имя: strings.ToUpper.

func main: точка входа#

Функция main — место, где программа начинается. Три правила. Первое: у main нет параметров и она ничего не возвращает; аргументы командной строки достаются иначе, через пакет os, о котором поговорим в финальном проекте. Второе: main обязана лежать в package main — точка входа в библиотеке бессмысленна, библиотеку запускает чужая программа. Третье, самое важное для будущего: программа живёт ровно до конца main. Как только main завершается, закрывается всё приложение, включая запущенные фоновые задачи. Запомните это — когда через двадцать уроков дойдём до горутин, именно эта строка объяснит половину их багов. И ещё одна приятность: компилятору всё равно, в каком порядке функции объявлены внутри пакета. Вызвать функцию, написанную ниже по файлу, можно спокойно.

Порядок объявления функций не важен

Запустите код. Потом перепишите функции в другом порядке — вывод не изменится. Затем удалите вызов finish() и запустите снова: функция стала мёртвым кодом, но ошибки не будет. Мёртвая функция разрешена, мёртвый импорт — нет: компилятор разумен в строгости.

Вывод

Запустите код, чтобы увидеть вывод.

Что выведет программа?

Прочитайте код и выберите, что он выведет:

package main

import "fmt"

func main() {
        finish()
        start()
}

func start() { fmt.Println("старт") }
func finish() { fmt.Println("финиш") }

Экспортируемое и внутреннее: заглавная буква решает#

В других языках есть ключевые слова public, private и protected. В Go роль модификатора доступа играет первая буква имени. Println начинается с заглавной — значит, функция экспортируемая, видна из других пакетов: поэтому fmt.Println доступен вам сразу после import "fmt". Если бы она называлась println со строчной, за пределами пакета fmt её бы никто не увидел, и вы ловили бы ошибку «undefined». То же правило работает для типов, структур, констант и переменных уровня пакета: заглавная — наружу, строчная — только внутри пакета. Механизм на одну букву, а покрывает весь вопрос видимости. Мне он кажется честным решением: видно прямо в месте вызова, экспортируется функция или это чья-то внутренняя кухня. Спорить, конечно, можно — в больших структурах не хватает промежуточного уровня «видно внутри модуля», — но для чтения кода это гениально просто.

Что произойдёт при попытке собрать программу без func main?

Какой импорт сломает компиляцию?

Соберите скелет программы

Вставьте три ключевых слова так, чтобы получилась минимальная запускаемая программа.

main "fmt" main() { fmt.Println("Скелет собран") }

Точки с запятой, комментарии и gofmt#

Вы могли заметить: в коде нет точек с запятой. Они в Go есть, но их вставляет лексер автоматически в конце строк — руками ставить не нужно, а лишние gofmt при форматировании убирает. Из этой автоматизации следует неожиданное правило: открывающая фигурная скобка обязана стоять на той же строке, что и объявление. Перенесёте её вниз — лексер поставит точку с запятой не туда, и компилятор выдаст синтаксическую ошибку. Привыкайте сразу, это второй по популярности сюрприз у пришедших из JavaScript. Комментарии — двойной слэш // до конца строки и блочные / … / как в C. У комментария над объявлением функции есть особая роль: это официальная документация, по которой работает go doc и сайт pkg.go.dev. Поэтому к таким комментариям пишут полное предложение, начиная с имени функции. Форматирование наконец — gofmt, и это не рекомендация: весь код в экосистеме отформатирован им, споры о пробелах в Go закончились.

Скелет вы теперь читаете свободно: package объявляет принадлежность, import — список зависимостей, func main — где начинается выполнение. Следующий шаг — наполнить main жизнью: в уроке Переменные и константы начинается разговор о данных, без которых Println так и будет печатать одни приветствия. Когда позже захочется разрезать проект на несколько своих пакетов и подключать чужие библиотеки, вернитесь к продвинутому уроку Пакеты и модули — но сперва лучше пройти базу целиком.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Первая строка файла — package: main для программ, имя папки — для библиотек.
  • import перечисляет используемые пакеты; неиспользуемый импорт — ошибка компиляции, а не предупреждение.
  • func main — точка входа: обязана быть в package main, без параметров и без возврата.
  • Программа живёт до конца main — фоновые задачи умирают вместе с ним.
  • Порядок объявления функций в пакете не важен — важен порядок вызова.
  • Заглавная буква имени экспортирует его из пакета, строчная оставляет внутренним.
  • Точки с запятой вставляет лексер, поэтому открывающая скобка стоит на строке объявления; gofmt решает вопросы стиля за всех.

Домашнее задание

Возьмите hello world из урока и проведите три эксперимента: удалите строку import и запустите; переименуйте main в main2 и запустите; добавьте свою функцию и вызовите её из main до и после Println. Каждый раз внимательно прочитайте сообщение компилятора — эти три формулировки ошибок вы теперь будете узнавать в лицо весь курс.

Личные заметкиПусто
Хранятся только в вашем браузере (Go Base · Структура программы: package, import, func main)

Вопросы и ответы по уроку

Можно ли объявить две функции main в одном проекте?

Внутри одного пакета main функция main объявляется один раз — иначе ошибка компиляции. Несколько отдельных main-пакетов в разных папках допустимы: так устроены проекты с утилитами в каталогах cmd.

Почему в Go не ставят точку с запятой?

Она есть, но лексер вставляет её автоматически в конце строк. Из-за этого открывающая фигурная скобка обязана стоять на той же строке, что и объявление, — иначе расстановка собьётся и код не соберётся.

Чем package main отличается от остальных пакетов?

package main — инструкция собрать исполняемый файл с точкой входа func main. Любое другое имя пакета даёт библиотеку: её функции импортируют другие программы, а запустить её саму нельзя.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Go Pro25 уроков~14 чпрактика25 уроков уже доступны

Продвинутые возможности Go: от замыканий до рефлексии

Продвинутый курс Go: замыкания, defer, горутины, каналы, контекст, интерфейсы, дженерики, рефлексия, тесты и JSON. 25 уроков с практикой в браузере.

  • Писать и читать конкурентный код: go-функции, каналы, select
  • Управлять жизненным циклом операций через context
  • Проектировать API на интерфейсах
  • Использовать дженерики без злоупотребления