В этом уроке мы подробнее разберем работу с пакетами в Go: научимся создавать, экспортировать и импортировать их.
Создание пакета
Любой код в Go существует внутри пакета — это папка с go-файлами. Заголовки всех файлов в пакете начинаются одинаково — package имя-пакета. Даже если мы создаем программу из одного файла, она все равно должна быть внутри пакета.
Начнем работу с первым Go-пакетом. Создайте папку ./greeting/, а внутри нее — файл constants.go. Добавьте в файл такой код:
// Файл ./greeting/constants.go
package greeting // Как всегда, объявляем пакет в начале файла
var greeting string = "Hello, Hexlet!" // Дальше пишем сам код
В сообществе Go-разработчиков принято давать имена пакетам только в нижнем регистре. Не должно быть camelCase, snake_case или kebab-case. Если очень нужно использовать больше одного слова, то стоит использовать аббревиатуру. Хороший пример — пакет bufio (buffer io) из стандартной библиотеки.
Проведем небольшой эксперимент — добавим в папку еще один файл getters.go со следующим содержимым:
// Файл ./greeting/getters.go
package greeting
func Get() string {
return greeting
}
Обратите внимание, что переменная greeting
объявлена в одном файле, а используется уже в другом. В отличие от других языков, в Go компилятор и линтер не будут ругаться на такой код. Неважно, на сколько файлов разбит пакет, потому что внутри пакета общая область видимости.
Пакет main
Чтобы Go-программу можно было скомпилировать и запустить из командной строки, используют пакет main
:
// Файл ./main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, Hexlet!")
}
Его единственное отличие от любого другого пакета — в нем содержится функция main
, которая не принимает аргументы и не возвращает значения. Команды go build
и go run
ищут функцию main
внутри пакета main и собирают исполняемый файл, который вызывает функцию main
. Эта функция — точка входа в программу.
Импорт и экспорт
В Go используется оригинальная система импортов. Воспользуемся созданными выше файлами и изучим импорт на их примере:
// Файл ./main.go
package main
import (
"./greeting" // Относительный импорт
"fmt"
)
func main() {
fmt.Println(greeting.Get())
}
Структура проекта выглядит так:
├── greeting
│ ├── constants.go
│ └── getters.go
└── main.go
Чтобы импортировать пакет, достаточно указать относительный или абсолютный путь до него в блоке импортов. При этом в Go нет возможности импортировать только одну переменную или функцию из пакета.
Теперь запустим программу из командной строки:
GO111MODULE=off go run . # GO111MODULE — это переменная окружения, которая обозначает, включены модули или нет
Hello, Hexlet!
Импортируются ли все переменные из пакета? Обновим функцию main
так, чтобы она использовала переменную greeting
:
func main() {
fmt.Println(greeting.greeting)
}
А теперь запустим:
GO111MODULE=off go run . # Ошибка! ./main.go:9:14: cannot refer to unexported name greeting.greeting
Компилятор выдает ошибку: программа не может ссылаться на не экспортируемое значение. Рассмотрим подробнее, что это значит.
Go определяет, экспортируется идентификатор или нет, по его имени:
- Если идентификатор начинается с заглавной буквы, значение экспортируется
- Если идентификатор начинается со строчной буквы или нижнего подчеркивания, значение не экспортируется (доступно только в своем пакете)
Алиасы
Go-разработчик может оказаться в ситуации, когда нужно импортировать два пакета с одинаковыми именами. Например, он решил выпустить вторую версию пакета greeting
и создал папку v2 внутри папки greeting. Внутри этой папки всего один файл greeting.go:
// Файл ./greeting/v2/greeting.go
package greeting // Обратите внимание, что имя пакета не обязательно совпадает с именем директории, в которой находится
func Get() string {
return "Hello from version 2!"
}
Обновим пакет main:
package main
import (
greetingv1 "./greeting"
greetingv2 "./greeting/v2"
"fmt"
)
func main() {
fmt.Println("Первое приветствие: ", greetingv1.Get(), "\n", "Второе приветствие: ", greetingv2.Get())
}
Чтобы импортировать два разных пакета с одинаковым именем, мы добавляем алиасы к импортам пакетов. Теперь можно обращаться к пакету первой версии по имени greetingv1, а ко второй — greetingv2. Если убрать алиасы, то компилятор вернет ошибку greeting redeclared as imported package name
.
В Go есть способ импортировать все экспортируемые переменные из пакета, при этом не используя имя пакета. Если дать пакету алиас .
, то из пакета импортируются все переменные и функции, будто бы они были объявлены внутри текущего пакета.
Выводы
Кратко перечислим основные выводы этого урока:
- Код в проекте делится на пакеты, даже если проект состоит всего из одного файла
- Пакет main.go содержит в себе функцию
main
, которая служит точкой входа в программу - Чтобы экспортировать функцию или переменную из пакета, достаточно дать ей название с заглавной буквы
Самостоятельная работа
-
В проекте hexlet-go создайте директорию greeting
-
В директории greeting создайте файл hello.go и добавьте туда код:
package greeting var greeting string = "Golang for Brave!" func Hello() string { return greeting }
-
Для локального импорта проект должен быть правильно настроен как Go модуль. Для этого инициализируйте Go модуль в вашем проекте. Вам нужно будет выполнить команду
go mod init hexlet-go
в корневой директории проекта. -
В корневой директории переименуйте файл hello.go в main.go, импортируйте в него пакет
greeting
и выведите на экран результат работы функцииHello()
:package main import ( "fmt" // Здесь должен быть импорт пакета greeting ) func main() { fmt.Println(greeting.Hello()) }
-
Запустите программу и убедитесь, что на экран вывелась строка Golang for Brave!
-
Залейте все изменения на гитхаб
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.