Хабрахабр

4 способа импортировать пакет в Go

Всего лишь нужно указать директиву import и перечислить импортируемые пакеты. Декларативная часть импорта пакетов в Go достаточно скучная и обыденная. Кроме того, они сворачивают этот блок, чтобы он не мешал обозревать код. Современные 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 main(). Для этого придумана функция func init(). Она всегда будет выполняться при импорте пакета (если быть точным — она выполнится один раз при первом импорте пакета в вашей программе). Эту функцию так же можно писать в Ваших пакетах. Так же стоит понимать, что init() выполнится и при запуске тестов этого пакета.

Пакет a лишь экспортирует переменную, но не инициализирует её.

github.com/vigo5190/goimports-example/a

package a var Foo string

Пакет b экспортирует переменную и инициализирует её в init().

github.com/vigo5190/goimports-example/b

package b var Foo string func init() { Foo = "bar"
}

Пакет c экспортирует переменную, инициализирует её в init() и выводит значение в stdout.

github.com/vigo5190/goimports-example/c

package c import "fmt" var Foo string func init() { Foo = "bar" fmt.Printf("%#v\n", Foo)
}

В этом примере мы импортируем 2 пакета и выводим в stdout значения экспортированных переменных.

package main import ( "fmt" "github.com/vigo5190/goimports-example/a" "github.com/vigo5190/goimports-example/b"
) func main() { fmt.Printf("%#v\n", a.Foo) fmt.Printf("%#v\n", b.Foo)
}

Получим

go run main.go "" "bar"

В разделе import импортируется 2 пакета a и b. Что собственно происходит в этом коде. В пакете b значение переменной было проинициализировано в init() значением "bar". В пакете a объвлена переменная со значением по умолчанию (для строк — пустая строка). Для обращения к переменным каждого пакета используется запись вида <имя_пакета>.<имя_поля> .

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("%#v\n", a.Foo) fmt.Printf("%#v\n", foo.Foo) fmt.Printf("%#v\n", bar.Foo)
}

Получим

go run main.go "" "bar" ""

При этом пакет a импортировался несколько раз — второй раз под псевдонимом bar. Как видно из примера пакету b присвоен синоним foo.

Пакеты импортируют, задавая синонимы, в нескольких случаях:

  • Имя импортируемого пакета неудобное/некрасивое/… и хочется использовать другое;
  • Имя импортируемого пересекается с именем другого пакета;
  • Хочется бесшовно подменить пакет — интерфейсы пакетов должны совпадать.

Пример оправданного использования синонима

Например, при импорте github.com/sirupsen/logrus:

package db import( log "github.com/sirupsen/logrus"
)

package main import ( "fmt" "github.com/vigo5190/goimports-example/a" _ "github.com/vigo5190/goimports-example/c"
) func main() { fmt.Printf("%#v\n", a.Foo)
}

Получим

go run main.go "bar" ""

При этом перед пакетом c стоит _ и в самом коде пакет никак не используется. Как видно по коду, мы импортируем два пакета: a и c. Такой прием используется для того, чтобы выполнить init() из пакета.

В нашем примере в выводе на первой строке появился "bar", по той причине, что этот вывод находится в функции инициализации пакета c.

Пример оправданного использования _

Например, при импорте github.com/lib/pq:

package db import( _ "github.com/lib/pq"
)

в init() lib/pq такой код:

func init() )
}

который зарегистрирует драйвер.

package main import ( "fmt" "github.com/vigo5190/goimports-example/a" . "github.com/vigo5190/goimports-example/b"
) func main() { fmt.Printf("%#v\n", a.Foo) fmt.Printf("%#v\n", Foo)
}

Получим

go run main.go "" "bar"

И теперь Вы можете работать с полями импортированного пакет так, как будто они у вас в пакете. Импорт с точкой добавляет все экспортируемые поля пакета в текущий скоуп (точнее говоря область видимости файла).

Такой опцией стоит пользоваться очень осторожно — пример ниже.

Пример 1

package main import ( . "fmt"
) func main() { Println("Hello, habr.com!")
}

Получим:

Hello, habr.com!

Пример 2

package main import ( . "fmt" . "math"
) func main() { Printf("%v\n", Sqrt(9))
}

Получим:

3

package main import ( "fmt" . "github.com/vigo5190/goimports-example/a" . "github.com/vigo5190/goimports-example/b"
) func main() { fmt.Printf("%#v\n", 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.

Примеры кода, с которым можно поиграться лежат на гитхабе.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть