Декларативная часть импорта пакетов в Go достаточно скучная и обыденная. Всего лишь нужно указать директиву import
и перечислить импортируемые пакеты. Современные IDE делают эту работу за Вас — сами подставляют пакеты в этот раздел, что очень удобно. Кроме того, они сворачивают этот блок, чтобы он не мешал обозревать код. Я же советую развернуть этот блок, и изучить его внимательно — возможно Вы найдете там нечто необычное:
package main
import (
"github.com/vigo5190/goimports-example/a"
foo "github.com/vigo5190/goimports-example/a"
. "github.com/vigo5190/goimports-example/b"
_ "github.com/vigo5190/goimports-example/c"
)
Если стандартный импорт, импорт с синонимом и _
я встречал, то импорт с .
я до этого не видел.
Для начало стоит вспомнить как же запускаются программы на Go.
Первое и самое важное — в корне проекта ( для библиотек и пакетов иначе) лежит файл main.go
, который при разработке запускают командой
go run main.go
Отличительная особенность этого файла в том, что декларируемый в нём пакет должен быть main
.
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello habr.com!")
}
По сути, точкой входа в программу является func main()
в пакете main
. Но это поведение можно немного хакнуть. Для этого придумана функция func init()
. Эта функция выполнится перед выполнением func main()
. Эту функцию так же можно писать в Ваших пакетах. Она всегда будет выполняться при импорте пакета (если быть точным — она выполнится один раз при первом импорте пакета в вашей программе). Так же стоит понимать, что init()
выполнится и при запуске тестов этого пакета.
Примеры пакетов
Пакет a
лишь экспортирует переменную, но не инициализирует её.
package a
var Foo string
Пакет b
экспортирует переменную и инициализирует её в init()
.
package b
var Foo string
func init() {
Foo = "bar"
}
Пакет c
экспортирует переменную, инициализирует её в init()
и выводит значение в stdout.
package c
import "fmt"
var Foo string
func init() {
Foo = "bar"
fmt.Printf("%#vn", Foo)
}
Импорт "простой"
В этом примере мы импортируем 2 пакета и выводим в stdout значения экспортированных переменных.
package main
import (
"fmt"
"github.com/vigo5190/goimports-example/a"
"github.com/vigo5190/goimports-example/b"
)
func main() {
fmt.Printf("%#vn", a.Foo)
fmt.Printf("%#vn", b.Foo)
}
Получим
go run main.go
""
"bar"
Что собственно происходит в этом коде. В разделе import
импортируется 2 пакета a
и b
. В пакете a
объвлена переменная со значением по умолчанию (для строк — пустая строка). В пакете b
значение переменной было проинициализировано в init()
значением "bar"
. Для обращения к переменным каждого пакета используется запись вида <имя_пакета>.<имя_поля>
.
Импорт c синонимом
package main
import (
"fmt"
"github.com/vigo5190/goimports-example/a"
foo "github.com/vigo5190/goimports-example/b"
bar "github.com/vigo5190/goimports-example/a"
)
func main() {
fmt.Printf("%#vn", a.Foo)
fmt.Printf("%#vn", foo.Foo)
fmt.Printf("%#vn", bar.Foo)
}
Получим
go run main.go
""
"bar"
""
Как видно из примера пакету b
присвоен синоним foo
. При этом пакет a
импортировался несколько раз — второй раз под псевдонимом bar
.
Пакеты импортируют, задавая синонимы, в нескольких случаях:
- Имя импортируемого пакета неудобное/некрасивое/… и хочется использовать другое;
- Имя импортируемого пересекается с именем другого пакета;
- Хочется бесшовно подменить пакет — интерфейсы пакетов должны совпадать.
Например, при импорте github.com/sirupsen/logrus
:
package db
import(
log "github.com/sirupsen/logrus"
)
Импорт c подчеркиванием
package main
import (
"fmt"
"github.com/vigo5190/goimports-example/a"
_ "github.com/vigo5190/goimports-example/c"
)
func main() {
fmt.Printf("%#vn", a.Foo)
}
Получим
go run main.go
"bar"
""
Как видно по коду, мы импортируем два пакета: a
и c
. При этом перед пакетом c
стоит _
и в самом коде пакет никак не используется. Такой прием используется для того, чтобы выполнить init()
из пакета.
В нашем примере в выводе на первой строке появился "bar"
, по той причине, что этот вывод находится в функции инициализации пакета c
.
Например, при импорте github.com/lib/pq
:
package db
import(
_ "github.com/lib/pq"
)
в init()
lib/pq
такой код:
func init() {
sql.Register("postgres", &Driver{})
}
который зарегистрирует драйвер.
Импорт c точкой
package main
import (
"fmt"
"github.com/vigo5190/goimports-example/a"
. "github.com/vigo5190/goimports-example/b"
)
func main() {
fmt.Printf("%#vn", a.Foo)
fmt.Printf("%#vn", Foo)
}
Получим
go run main.go
""
"bar"
Импорт с точкой добавляет все экспортируемые поля пакета в текущий скоуп (точнее говоря область видимости файла). И теперь Вы можете работать с полями импортированного пакет так, как будто они у вас в пакете.
Такой опцией стоит пользоваться очень осторожно — пример ниже.
package main
import (
. "fmt"
)
func main() {
Println("Hello, habr.com!")
}
Получим:
Hello, habr.com!
package main
import (
. "fmt"
. "math"
)
func main() {
Printf("%vn", Sqrt(9))
}
Получим:
3
Импорт c точкой (и ошибкой)
package main
import (
"fmt"
. "github.com/vigo5190/goimports-example/a"
. "github.com/vigo5190/goimports-example/b"
)
func main() {
fmt.Printf("%#vn", Foo)
}
Получим
go run main.go
# command-line-arguments
./main.go:7:2: Foo redeclared during import "github.com/vigo5190/goimports-example/b"
previous declaration during import "github.com/vigo5190/goimports-example/a"
./main.go:7:2: imported and not used: "github.com/vigo5190/goimports-example/b"
Как видно из вывода, при импорте в текущую область видимости пакетов с пересекающимися полями мы получим ошибку компиляции.
Поэтому подумайте лишний раз перед тем как использовать такой импорт — можно получить ошибку совершенно неожиданно.
Итого
Несмотря на жесткие ограничения синтаксиса, в Go можно делать достаточно много нестандартных вещей. Рассмотренные выше особенности импорта демонстрируют, что всего парой операторов можно очень сильно изменить поведение программы. Главное, при использование всех этих возможностей не выстрелить себе в ногу. И помните, что лучше написать простой и понятный код, чем сложный и "крутой".
P.S.
Примеры кода, с которым можно поиграться лежат на гитхабе: https://github.com/vigo5190/goimports-example
Автор: ViGo5190