One place for hosting & domains

      Entendendo o init em Go


      Introdução

      Em Go, a função pré-definida init() faz com que uma parte do código execute antes de qualquer outra parte do seu pacote. Esse código vai executar assim que o pacote for importado e poderá ser usado quando você precisar inicializar seu aplicativo em um estado específico, como quando você tem uma configuração específica ou um conjunto de recursos com os quais seu aplicativo precisa iniciar. Ele também é usado na importação de um efeito colateral – técnica usada para definir o estado de um programa por meio da importação de um pacote específico. Isso é frequentemente usado para register [registrar] um pacote junto ao outro, para garantir que o programa está considerando o código correto para a tarefa.

      Embora o init() seja uma ferramenta útil, ela pode, por vezes, deixar o código difícil de ler, uma vez que uma instância difícil de encontrar de init() irá afetar imensamente a ordem na qual o código é executado. Por isso, é importante para os desenvolvedores que são novos em Go entender as nuances dessa função, para que eles possam garantir que usem o init() de uma maneira legível ao escrever seus códigos.

      Neste tutorial, você aprenderá como o init() é usado para a configuração e inicialização de variáveis de pacotes específicos, processos computacionais únicos e o registro de um pacote para uso com outro pacote.

      Pré-requisitos

      Para alguns dos exemplos neste artigo, você vai precisar do seguinte:

      .
      ├── bin
      │
      └── src
          └── github.com
              └── gopherguides
      

      Declarando o init()

      Sempre que você declarar uma função init(), o Go irá carregá-la e executá-la antes de qualquer outra coisa naquele pacote. Para demonstrar isso, esta seção traz um passo a passo de como definir uma função init() e mostrar os efeitos na maneira como o pacote executa.

      Vamos primeiro usar seguinte exemplo de código sem a função init():

      main.go

      package main
      
      import "fmt"
      
      var weekday string
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      Neste programa, declaramos uma variável global chamada weekday. Por padrão, o valor de weekday é uma string vazia.

      Vamos executar este código:

      Como o valor de weekday está em branco, quando executarmos o programa, vamos obter o seguinte resultado:

      Output

      Today is

      Podemos preencher a variável em branco, introduzindo uma função init() que inicializa o valor de weekday no dia atual. Adicione as linhas destacadas a seguir ao main.go:

      main.go

      package main
      
      import (
          "fmt"
          "time"
      )
      
      var weekday string
      
      func init() {
          weekday = time.Now().Weekday().String()
      }
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      Neste código, importamos e usamos o pacote time para obter o dia atual da semana (Now(). Weekday(). String() ) e, em seguida, usamos o init() para inicializar weekday com aquele valor.

      Agora, quando executarmos o programa, ele imprimirá o valor do dia da semana atual:

      Output

      Today is Monday

      Embora isso mostre como o init() funciona, um caso de uso muito mais típico para o init() é usá-lo na importação de um pacote. Isso pode ser útil quando você precisar fazer tarefas específicas de configuração em um pacote, antes do momento em que vai querer que o pacote seja usado. Para demonstrar isso, vamos criar um programa que precisará de uma inicialização específica para que o pacote funcione como pretendido.

      Inicializando pacotes na importação

      Primeiro, vamos escrever um código que seleciona uma criatura aleatória de uma fatia e o imprime. No entanto, não vamos usar o init() em nosso programa inicial. Isso mostrará melhor o problema que temos e como o init() resolverá nosso problema.

      Em seu diretório src/github.com/gopherguides/, crie uma pasta chamada creature com o seguinte comando:

      Dentro da pasta creature, crie um arquivo chamado creature.go:

      • nano creature/creature.go

      Neste arquivo, adicione o seguinte conteúdo:

      creature.go

      package creature
      
      import (
          "math/rand"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Esse arquivo define uma variável chamada creatures que tem um conjunto de criaturas marinhas inicializadas como valores. Ele também tem uma função exportada Random que retornará um valor aleatório da variável creatures.

      Salve e saia desse arquivo.

      Em seguida, vamos criar um pacote cmd que usaremos para escrever nossa função main() e chamar o pacote creature.

      No mesmo nível de arquivo no qual criamos a pasta creature, crie uma pasta cmd com o seguinte comando:

      Na pasta cmd, crie um arquivo chamado main.go:

      Adicione o conteúdo a seguir ao arquivo:

      cmd/main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/creature"
      )
      
      func main() {
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
      }
      

      Aqui, importamos o pacote creature e, em seguida, na função main() usamos a função creature.Random() para recuperar uma criatura aleatória e imprimi-la quatro vezes.

      Salve e saia do main.go.

      Agora, temos nosso programa escrito por completo. No entanto, antes de executarmos esse programa, também precisaremos criar alguns dos arquivos de configuração para que o nosso código funcione corretamente. A linguagem Go usa Módulos Go para configurar as dependências de pacotes para a importação de recursos. Esses módulos são arquivos de configuração colocados no seu diretório de pacotes. Eles dizem ao compilador de onde importar os pacotes. Embora o aprendizado sobre os módulos esteja fora do escopo deste artigo, podemos escrever apenas algumas linhas de configuração para fazer com que este exemplo funcione localmente.

      No diretório cmd, crie um arquivo chamado go.mod:

      Assim que o arquivo estiver aberto, coloque o seguinte conteúdo:

      cmd/go.mod

      module github.com/gopherguides/cmd
       replace github.com/gopherguides/creature => ../creature
      

      A primeira linha desse arquivo diz ao compilador que o pacote cmd que criamos é, na verdade, o github.com/gopherguides/cmd. A segunda linha diz ao compilador que o github.com/gopherguides/creature pode ser encontrado localmente em disco no diretório ../creature.

      Salve e feche o arquivo. Em seguida, crie um arquivo go.mod no diretório creature:

      Adicione a linha de código a seguir ao arquivo:

      creature/go.mod

       module github.com/gopherguides/creature
      

      Isso diz ao compilador que o pacote creature que criamos é, na verdade, o pacote github.com/gopherguides/creature. Sem isso, o pacote cmd não saberia de onde importar esse pacote.

      Salve e saia do arquivo.

      Agora, você deve ter a seguinte estrutura de diretório e layout de arquivo:

      ├── cmd
      │   ├── go.mod
      │   └── main.go
      └── creature
          ├── go.mod
          └── creature.go
      

      Agora que temos toda a configuração completa, podemos executar o programa main com o seguinte comando:

      Isso dará:

      Output

      jellyfish squid squid dolphin

      Quando executamos o programa, recebemos quatro valores e os imprimimos. Se executarmos o programa várias vezes, notaremo*s que *o resultado obtido será sempre o mesmo e não um resultado aleatório - como o esperado. Isso acontece porque o pacote rand cria números pseudoaleatórios que gerarão consistentemente o mesmo resultado para um único estado inicial. Para alcançar um número mais aleatório, podemos propagar o pacote, ou definir uma fonte de mudança para que o estado inicial seja diferente sempre que executarmos o programa. Na linguagem Go, é comum usar o tempo atual para propagar o pacote rand.

      Como queremos que o pacote creature lide com a funcionalidade aleatória, abra este arquivo:

      • nano creature/creature.go

      Adicione as seguintes linhas destacadas ao arquivo creature.go:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          rand.Seed(time.Now().UnixNano())
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Nesse código, importamos o pacote time e usamos Seed() para propagar o tempo atual. Salve e saia do arquivo.

      Agora, quando executarmos o programa, vamos obter um resultado aleatório:

      Output

      jellyfish octopus shark jellyfish

      Se você continuar executando o programa repetidas vezes, você continuará a obter resultados aleatórios. No entanto, essa ainda não é uma implementação ideal do nosso código, pois toda vez que creature.Random() é chamada, ela também propaga novamente o pacote rand, chamando rand.Seed(time.Now(). UnixNano()) novamente. Executar novamente a propagação aumentará as chances de se propagar o mesmo valor inicial, caso o relógio interno não tiver sido alterado. Isso resultará em possíveis repetições do padrão aleatório, ou aumentará o tempo de processamento da CPU, fazendo com que o seu programa aguarde pela alteração no relógio.

      Para corrigir isso, podemos usar uma função init(). Vamos atualizar o arquivo creature.go:

      • nano creature/creature.go

      Adicione as seguintes linhas de código:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func init() {
          rand.Seed(time.Now().UnixNano())
      }
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      A adição da função init() diz ao compilador que ao importar o pacote creature, ele deve executar a função init() uma vez, fornecendo uma única propagação para a geração aleatória de números. Isso garante que não executemos o código mais vezes do que o necessário. Agora, caso executarmos o programa, continuaremos a obter resultados aleatórios:

      Output

      dolphin squid dolphin octopus

      Nesta seção, vimos de que modo o uso do init() pode assegurar que os cálculos ou inicializações apropriados sejam realizados antes do pacote ser usado. Em seguida, vamos ver como usar várias instruções init() em um pacote.

      Múltiplas instâncias de init()

      Ao contrário do que ocorre com a função main() - que pode ser declarada apenas uma vez, a função init() pode ser declarada várias vezes ao longo de um pacote. No entanto, o uso de várias init()s pode dificultar saber qual delas tem prioridade sobre as outras. Nesta seção, mostraremos como manter o controle sobre várias instruções init().

      Na maioria dos casos, as funções init() serão executadas na ordem em que você as encontrar. Vamos usar o código a seguir como exemplo:

      main.go

      package main
      
      import "fmt"
      
      func init() {
          fmt.Println("First init")
      }
      
      func init() {
          fmt.Println("Second init")
      }
      
      func init() {
          fmt.Println("Third init")
      }
      
      func init() {
          fmt.Println("Fourth init")
      }
      
      func main() {}
      

      Se executarmos o programa com o seguinte comando:

      Receberemos o seguinte resultado:

      Output

      First init Second init Third init Fourth init

      Note que cada init() é executado na ordem em que o compilador o encontra. No entanto, nem sempre pode ser fácil determinar a ordem na qual a função init() será chamada.

      Vamos ver uma estrutura de pacotes mais complicada, na qual temos vários arquivos, cada qual com sua própria função init() declarada em si. Para ilustrar isso, criaremos um programa que compartilha uma variável chamada message e a imprime.

      Exclua os diretórios creature e cmd e seu conteúdo da seção anterior e os substitua pela seguinte estrutura de diretórios e arquivos:

      ├── cmd
      │   ├── a.go
      │   ├── b.go
      │   └── main.go
      └── message
          └── message.go
      

      Agora, vamos adicionar o conteúdo de cada arquivo. Em a.go, adicione as seguintes linhas:

      cmd/a.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          fmt.Println("a ->", message.Message)
      }
      

      Esse arquivo contém uma única função init() que imprime o valor de message.Message do pacote message.

      Em seguida, adicione o seguinte conteúdo ao b.go:

      cmd/b.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          message.Message = "Hello"
          fmt.Println("b ->", message.Message)
      }
      

      Em b.go, temos uma única função init() que define o valor de message.Message para Hello e o imprime.

      Em seguida, crie um arquivo main.go, de maneira a se parecer com o seguinte:

      cmd/main.go

      package main
      
      func main() {}
      

      Esse arquivo não faz nada, mas proporciona um ponto de entrada para o programa executar.

      Por fim, crie seu arquivo message.go como o seguinte:

      message/message.go

      package message
      
      var Message string
      

      Nosso pacote message declara a variável exportada Message.

      Para executar o programa, execute o seguinte comando a partir do diretório cmd:

      Como temos vários arquivos Go na pasta cmd que compõem o pacote main, precisamos dizer ao compilador que todos os arquivos .go na pasta cmd devem ser compilados. Ao usar *.go, dizemos ao compilador para que carregue todos os arquivos na pasta cmd que terminem em .go. Se emitíssemos o comando go run main.go, o programa apresentaria falha ao compilar, uma vez que não veria o código nos arquivos a.go e b.go.

      Isso dará o seguinte resultado:

      Output

      a -> b -> Hello

      De acordo com as especificações da linguagem Go para a Inicialização de pacotes, quando vários arquivos são encontrados em um pacote, eles são processados em ordem alfabética. Por esse motivo, a primeira vez que imprimimos message.Message a partir do a.go, o valor estava em branco. O valor não foi inicializado até que a função init() do b.go tivesse sido executada.

      Se alterássemos o nome do arquivo a.go para c.go, teríamos um resultado diferente:

      Output

      b -> Hello a -> Hello

      Agora, o compilador encontra primeiro o b.go. Dessa forma, o valor de message.Message já estará inicializado com Hello quando a função init() em c.go for encontrada.

      Esse comportamento poderia criar um possível problema em seu código. É comum no desenvolvimento de software alterar os nomes dos arquivos e, pela forma como o init() é processado, alterar nomes de arquivos pode alterar a ordem em que o init() é processado. Isso pode ter o efeito indesejável de alterar o resultado do seu programa. Para garantir que o comportamento de inicialização possa ser reproduzido, os sistemas de compilação são encorajados a apresentar para o compilador os vários arquivos que pertençam ao mesmo pacote, pela ordem alfabética dos nomes dos arquivos. Uma maneira de garantir que todas as funções init() sejam carregadas na ordem é declará-las todas em um único arquivo. Isso impedirá que a ordem seja alterada, mesmo se os arquivos forem alterados.

      Além de garantir que a ordem das suas funções init() não seja alterada, você também deve tentar evitar gerenciar o estado em seu pacote usando variáveis globais, ou seja, variáveis que podem ser acessadas de qualquer lugar dentro do pacote. No programa anterior, a variável message.Message estava disponível a todo o pacote e mantinha o estado do programa. Por conta disso, as instruções init() conseguiram alterar a variável e desestabilizar a previsibilidade do seu programa. Para evitar isso, tente trabalhar com variáveis em espaços controlados que tenham o mínimo de acesso possível, ao mesmo tempo em que ainda permitam que seu programa funcione.

      Vimos que é possível ter várias declarações init() em um único pacote. No entanto, fazer isso pode criar efeitos indesejáveis e tornar seu programa mais difícil de ler ou prever. Evitar várias instruções init() ou mantê-las todas em um único arquivo irá garantir que o comportamento do seu programa não seja alterado quando os arquivos forem movidos ou seus nomes forem alterados.

      Na sequência, vamos examinar como o init() é usado para importar efeitos colaterais.

      Usando o init() para obter Efeitos colaterais

      Na linguagem Go, às vezes é desejável importar um pacote não pelo seu conteúdo, mas pelos efeitos colaterais que ocorrem após a importação do pacote. Isso significa, frequentemente, que há uma instrução init() no código importado que executa antes de qualquer outro código, permitindo que o desenvolvedor manipule o estado no qual seu programa está iniciando. Essa técnica é chamada de importação para obter um efeito colateral.

      Um caso de uso comum da importação para obtenção de efeitos colaterais é registrar a funcionalidade em seu código, o que permite que um pacote saiba qual parte do código o seu programa precisa usar. No pacote image, por exemplo, a função image.Decode precisa saber qual formato de imagem ela está tentando decodificar (jpg, png, gif etc) antes de poder ser executada. Você pode alcançar isso primeiro importando um programa específico que tenha um efeito colateral da instrução init().

      Vamos supor que esteja tentando usar o image.Decode em um arquivo .png com o seguinte trecho de código:

      Sample Decoding Snippet

      . . .
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      . . .
      

      Um programa com esse código ainda será compilado, mas sempre que tentarmos decodificar uma imagem png, vamos obter um erro.

      Para corrigir isso, precisamos primeiro registrar um formato de imagem para o image.Decode. Felizmente, o pacote image/png contém a seguinte instrução init():

      image/png/reader.go

      func init() {
          image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
      }
      

      Portanto, caso importemos image/png para o nosso trecho de decodificação, então a função image.RegisterFormat() em image/png será executada antes de qualquer parte do nosso código:

      Sample Decoding Snippet

      . . .
      import _ "image/png"
      . . .
      
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      

      Isso definirá o estado e registrará que precisamos da versão png do image.Decode(). Esse registro acontecerá como um efeito colateral da importação de image/png.

      Você pode ter notado o identificador em branco (_) antes de "image/png". Isso é necessário porque a linguagem Go não permite que você importe pacotes que não sejam usados ao longo do programa. Ao incluir o identificador em branco, o valor da importação em si é descartado, para que apenas o efeito colateral da importação sobreviva. Isso significa que, embora nunca chamemos o pacote image/png em nosso código, ainda poderemos importá-lo para obter o seu respectivo efeito colateral.

      É importante saber quando você precisa importar um pacote para obter seu efeito colateral. Sem o registro adequado, é provável que seu programa seja compilado, mas que não funcione corretamente quando for executado. Os pacotes na biblioteca padrão declararão a necessidade desse tipo de importação em sua documentação. Se escrever um pacote que exija a importação para obtenção de efeito colateral, você também deve garantir que a instrução init() que está usando seja documentada, para que os usuários que importem seu pacote possam usá-lo corretamente.

      Conclusão

      Neste tutorial, aprendemos que a função init() é carregada antes que o resto do código em seu pacote seja carregado e que ela pode realizar tarefas específicas em um pacote, como inicializar um estado desejado. Também aprendemos que a ordem na qual o compilador executa várias instruções init() depende da ordem na qual o compilador carregue os arquivos fonte. Se quiser aprender mais sobre o init(), verifique a documentação oficial da Golang ou leia a discussão na comunidade Go sobre a função.

      Você pode ler mais sobre funções em nosso artigo Como definir e chamar funções em Go, ou explorar toda a série de artigos de Como programar em Go.



      Source link

      Información sobre init en Go


      Introducción

      En Go, la función init() predeterminada establece una porción de código que debe ejecutarse antes que cualquier otra parte de su paquete. Este código se ejecutará tan pronto como se importe el paquete y puede usarse cuando necesite que su aplicación se inicie en un estado específico; por ejemplo, cuando requiera que la aplicación se inicie con una configuración o un conjunto de recursos específicos. También se utiliza al importar un efecto secundario, una técnica que se utiliza para establecer el estado de un programa al importar un paquete específico. Esto se suele utilizar para registrar un paquete con otro a fin de garantizar que el programa considere el código correcto para la tarea.

      Si bien init() es una herramienta útil, a veces puede dificultar la lectura del código, dado que una instancia init() difícil de encontrar afectará en gran medida el orden en el que se ejecuta el código. Debido a esto, es importante que los desarrolladores que comienzan a usar Go comprendan las facetas de esta función, para poder asegurarse de utilizar init() de forma legible al escribir código.

      A través de este tutorial, aprenderá a usar init() para configurar e inicializar variables de paquetes específicas, cálculos por única vez y registros de un paquete para su uso con otro.

      Requisitos previos

      Para algunos de los ejemplos que se incluyen en este artículo, necesitará lo siguiente:

      .
      ├── bin
      │
      └── src
          └── github.com
              └── gopherguides
      

      Declarar init()

      Siempre que declare una función init(), Go la cargará antes que a cualquier otra cosa de ese paquete. Para demostrarlo, en esta sección se explicará la manera de definir una función init() y se mostrarán los efectos sobre la forma en que se ejecuta el paquete.

      Primero, tomemos lo siguiente como ejemplo de código sin la función init():

      main.go

      package main
      
      import "fmt"
      
      var weekday string
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      En este programa, declaramos una variable global llamada weekday. De forma predeterminada, el valor de weekday es una cadena vacía.

      Ejecutaremos este código:

      Debido a que el valor de weekday está vacío, al ejecutar el programa, obtendremos el siguiente resultado:

      Output

      Today is

      Podemos completar la variable en blanco introduciendo una función init() que inicialice el valor de weekday en el día actual. Añada las siguientes líneas resaltadas a main.go:

      main.go

      package main
      
      import (
          "fmt"
          "time"
      )
      
      var weekday string
      
      func init() {
          weekday = time.Now().Weekday().String()
      }
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      En este código, importamos y usamos el paquete time para obtener el día actual de la semana (Now(). Weekday(). String()) y, luego, utilizamos init() para inicializar weekday con ese valor.

      Ahora, cuando ejecutemos el programa, imprimirá el día actual de la semana:

      Output

      Today is Monday

      Aunque esto ilustra la forma en que init() funciona, es mucho más común usar init() al importar un paquete. Esto puede ser útil cuando necesita realizar tareas de configuración específicas en un paquete antes de que se utilice. Para demostrarlo, crearemos un programa que requerirá una inicialización específica a fin de que el paquete funcione como se indica.

      Inicializar paquetes en la importación

      Primero, escribiremos código para que se seleccione e imprima un animal al azar de un segmento, pero no usaremos init() en nuestro programa inicial. Esto indicará mejor el problema que tenemos y la manera en que init() lo resolverá.

      Desde su directorio src/github.com/gopherguides/, cree una carpeta llamada creatrue con el siguiente comando:

      Dentro de la carpeta creature, cree un archivo llamado creature:

      • nano creature/creature.go

      En este archivo, añada el siguiente contenido:

      creature.go

      package creature
      
      import (
          "math/rand"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Este archivo define una variable llamada creatures que tiene un conjunto de animales marinos que se inicializan como valores. También tiene una función Random exportada que mostrará un valor al azar de la variable creatures.

      Guarde y cierre este archivo.

      A continuación, crearemos un paquete cmd que usaremos para escribir nuestra función main() e invocar el paquete creature.

      En el mismo nivel de archivo desde el que creamos la carpeta creature, cree una carpeta cmd con el siguiente comando:

      Dentro de la carpeta cmd, cree un archivo llamado main.go:

      Añada el siguiente contenido al archivo:

      cmd/main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/creature"
      )
      
      func main() {
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
      }
      

      Aquí, importamos el paquete creature y luego, en la función main(), usamos la función creature.Random() para obtener un animal al azar e imprimirlo cuatro veces.

      Guarde y cierre main.go.

      Ahora, tenemos todo nuestro programa escrito. Sin embargo, para poder ejecutar este programa, también debemos crear algunos archivos de configuración a fin de que nuestro código funcione correctamente. Go utiliza Go Modules para configurar las dependencias de paquetes e importar recursos. Estos módulos son archivos de configuración que se disponen en su directorio de paquetes e indican al compilador el punto desde el cual se deben importar los paquetes. Si bien en este artículo no obtendrá información sobre los módulos, podemos escribir algunas líneas de configuración para que este ejemplo funcione a nivel local.

      En el directorio cmd, cree un archivo llamado go.mod:

      Una vez que el archivo esté abierto, disponga el siguiente contenido:

      cmd/go.mod

      module github.com/gopherguides/cmd
       replace github.com/gopherguides/creature => ../creature
      

      La primera línea de este archivo indica al compilador que el paquete cmd que creamos es, de hecho, github.com/gopherguides/cmd. La segunda línea indica al compilador que github.com/gopherguides/creature se encuentra a nivel local en el disco, en el directorio ../creature.

      Guarde y cierre el archivo. A continuación, cree un archivo go.mod en el directorio creature:

      Añada la siguiente línea de código al archivo:

      creature/go.mod

       module github.com/gopherguides/creature
      

      Esto indica al compilador que el paquete creature que creamos, en realidad, es el paquete github.com/gopherguides/creature. Sin esto, el paquete cmd no tendría registro del punto desde el cual debería importar este paquete.

      Guarde y cierre el archivo.

      Ahora, debería contar con esta estructura de directorios y distribución de archivos:

      ├── cmd
      │   ├── go.mod
      │   └── main.go
      └── creature
          ├── go.mod
          └── creature.go
      

      Ahora que completamos toda la configuración, podemos ejecutar el programa main con el siguiente comando:

      Esto proporcionará lo siguiente:

      Output

      jellyfish squid squid dolphin

      Cuando ejecutamos este programa, recibimos cuatro valores y los imprimimos. Si ejecutamos el programa varias veces, observaremos que siempre obtenemos el mismo resultado, en vez de un resultado al azar como se espera. Esto se debe a que el paquete rand crea números pseudoaleatorios que generarán de forma sistemática el mismo resultado para un único estado inicial. Para lograr un número más aleatorio, podemos propagar el paquete o establecer un origen cambiante para que el estado inicial sea diferente cada vez que ejecutemos el programa. En Go, es habitual usar la hora actual para propagar el paquete rand.

      Dado que queremos que el paquete creature maneje la funcionalidad aleatoria, abra este archivo:

      • nano creature/creature.go

      Añada las siguientes líneas al archivo creature.go:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          rand.Seed(time.Now().UnixNano())
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      En este código, importamos el paquete time y usamos Seed() para propagar la hora actual. Guarde el archivo y ciérrelo.

      Ahora, cuando ejecutemos el programa, obtendremos un resultado aleatorio:

      Output

      jellyfish octopus shark jellyfish

      Si continúa ejecutando el programa una y otra vez, seguirá obteniendo resultados aleatorios. Sin embargo, esta todavía no es una implementación ideal de nuestro código, porque cada vez qye se invoca creature.Random() también se vuelve a propagar el paquete rand invocando rand.Seed(time.Now(). UnixNano()) de nuevo. La repetición de la propagación aumentará la probabilidad de realizar la propagación con el mismo valor inicial si el reloj interno no se ha modificado, lo cual posiblemente generará repeticiones del patrón aleatorio o aumentará el tiempo de procesamiento de la CPU al hacer que el programa espere el cambio del reloj.

      Para solucionar esto, podemos usar una función init(). Actualizaremos el archivo creature.go:

      • nano creature/creature.go

      Añada las siguientes líneas de código:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func init() {
          rand.Seed(time.Now().UnixNano())
      }
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      La adición de la función init() indica al compilador que, al importar el paquete creature, debe ejecutar la función init() una vez con una sola propagación para la generación de un número al azar. Esto garantiza que no ejecutemos código más de lo necesario. Ahora, si ejecutamos el programa, continuaremos obteniendo resultados aleatorios:

      Output

      dolphin squid dolphin octopus

      En esta sección, vimos que el uso de init() puede garantizar que se realicen inicializaciones o cálculos adecuados antes de usar un paquete. A continuación, veremos la forma de usar varias instrucciones init() en un paquete.

      Varias instancias de init()

      A diferencia de la función main(), que solo se puede declarar una vez, la función init() puede declararse varias veces en un paquete. Sin embargo, varias funciones init() pueden hacer que resulte difícil determinar la que tiene prioridad sobre las demás. En esta sección, se mostrará la manera de mantener el control sobre varias instrucciones init().

      En la mayoría de los casos, las funciones init() se ejecutarán en el orden en el que las encuentre. Tomemos el siguiente código como ejemplo:

      main.go

      package main
      
      import "fmt"
      
      func init() {
          fmt.Println("First init")
      }
      
      func init() {
          fmt.Println("Second init")
      }
      
      func init() {
          fmt.Println("Third init")
      }
      
      func init() {
          fmt.Println("Fourth init")
      }
      
      func main() {}
      

      Si ejecutamos el programa con el siguiente comando:

      Obtendremos el siguiente resultado:

      Output

      First init Second init Third init Fourth init

      Observe que cada función init() se ejecuta en el orden en el que el compilador la encuentra. Sin embargo, es posible que no siempre sea tan fácil determinar el orden de invocación de las funciones init().

      Veamos una estructura de paquetes más complicada en la que tenemos varios archivos, cada uno con su propia función init() declarada en su interior. Para ilustrar esto, crearemos un programa que comparta una variable llamada message y la imprima.

      Elimine los directorios creature y cmd y su contenido de la sección anterior, y sustitúyalos por los directorios y la estructura de archivos que se indican a continuación:

      ├── cmd
      │   ├── a.go
      │   ├── b.go
      │   └── main.go
      └── message
          └── message.go
      

      Ahora, agregaremos el contenido de cada archivo. En a.go, añada las siguientes líneas:

      cmd/a.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          fmt.Println("a ->", message.Message)
      }
      

      Este archivo contiene una función init() única que imprime el valor de message.Message del paquete message.

      A continuación, añada el siguiente contenido a b.go:

      cmd/b.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          message.Message = "Hello"
          fmt.Println("b ->", message.Message)
      }
      

      En b.go, hay una función init() única que fija el valor de message.Message en Hello y lo imprime.

      A continuación, cree main.go para que tenga el siguiente aspecto:

      cmd/main.go

      package main
      
      func main() {}
      

      Este archivo no hace más que simplemente ofrecer un punto de entrada para que se ejecute el programa.

      Por último, cree su archivo message.go de la siguiente manera:

      message/message.go

      package message
      
      var Message string
      

      Nuestro paquete messages declara la variable Message exportada.

      Para iniciar el programa, ejecute el siguiente comando desde el directorio cmd:

      Debido a que hay varios archivos de Go en la carpeta cmd que conforman el paquete main, debemos indicar al compilador que todos los archivos .go de la carpeta cmd deben compilarse. Usar *.go indica al compilador que cargue todos los archivos que terminan en .go de la carpeta cmd. Si emitiéramos el comando go main.go, el programa no se compilaría porque no detectaría el código en los archivos a.go y b.go.

      Esto generará el siguiente resultado:

      Output

      a -> b -> Hello

      De acuerdo con la especificación del lenguaje de Go para Inicialización de paquetes, cuando se encuentran varios archivos en un paquete se procesan en orden alfabético. Es por esto que la primera vez que imprimimos message.Message desde a.go, el valor estaba vacío. El valor no se inicializó hasta que se ejecutó la función init() desde b.go.

      Si cambiáramos el nombre del archivo de a.go a c.go, obtendríamos un resultado diferente:

      Output

      b -> Hello a -> Hello

      Ahora, el compilador encuentra b.go primero y, por lo tanto, el valor de message.Message ya está inicializado con Hello cuando se encuentra la función init() en c.go.

      Este comportamiento podría generar un problema en su código. En el ámbito del desarrollo de software, es habitual cambiar los nombres de los archivos y, por la forma en que se procesa init(), hacer este cambio puede modificar el orden en el que se procesa init(). Esto podría tener el efecto no deseado de cambiar el resultado de su programa. Para garantizar un comportamiento de inicialización reproducible, se recomienda que los sistemas de compilación presenten a un compilador varios archivos pertenecientes al mismo paquete en orden de nombre de archivo léxico. Una forma de garantizar que se carguen todas las funciones init() en orden es declararlas en su totalidad en un único archivo. Esto impedirá que el orden cambie, incluso si se cambian los nombres de los archivos.

      Además de garantizar que el orden de sus funciones init() no cambie, también debe intentar evitar la administración del estado en su paquete usando variables globales; es decir, variables accesibles desde cualquier punto del paquete. En el programa anterior, la variable message.Message estaba disponible para todo el paquete y mantuvo el estado del programa. Debido a este acceso, las instrucciones init() pudieron cambiar la variable y desestabilizar la previsibilidad de su programa. Para evitar esto, intente trabajar con variables en espacios controlados que tengan el menor nivel de acceso posible y, al mismo tiempo, permitan que el programa funcione.

      Vimos que puede tener varias declaraciones init() en un único paquete. Sin embargo, esto puede crear efectos no deseados y hacer que su programa sea difícil de leer o predecir. Evitar tener varias instrucciones init() o mantenerlas en un único archivo garantizará que el comportamiento de su programa no cambie al mover los archivos o modificar su nombre.

      A continuación, veremos cómo se utiliza init() para la importación con efectos secundarios.

      Usar init() para efectos secundarios

      En Go, a veces es conveniente importar un paquete no por su contenido, sino por los efectos secundarios que se producen al importarlo. Esto suele significar que hay una instrucción init() en el código importado que se ejecuta antes de cualquier otro código, lo cual permite que el desarrollador manipule el estado en el que se inicia el programa. La técnica se denomina importación para efectos secundarios.

      Un caso de uso común para realizar una importación para obtener efectos secundarios tiene que ver con registrar la funcionalidad en su código, lo cual permite que un paquete registre la parte del código que necesita usar su programa. En el paquete image, por ejemplo, la función image.Decode debe registrar el formato de la imagen que intenta decodificar (jpg, png y gif, entre otros) para poder ejecutarse. Puede realizar esto importando, primero, un programa específico que tenga un efecto secundario de instrucción init().

      Supongamos que intenta usar image.Decode en un archivo .png con el siguiente fragmento de código:

      Sample Decoding Snippet

      . . .
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      . . .
      

      De todos modos, se compilará un programa con este código, pero cada vez que intentemos decodificar una imagen png, obtendremos un error.

      Para solucionar esto, primero, debemos registrar un formato de imagen para image.Decode. Afortunadamente, el paquete image/png contiene la siguiente instrucción init():

      image/png/reader.go

      func init() {
          image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
      }
      

      Por lo tanto, si importamos image/png en nuestro fragmento de decodificación, la función image.RegisterFormat() en image/png se ejecutará antes que cualquier parte de nuestro código:

      Sample Decoding Snippet

      . . .
      import _ "image/png"
      . . .
      
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      

      Con esto, se establecerá el estado y se registrará que necesitamos la versión png de image.Decode(). El registro se realizará como efecto secundario de la importación de image/png.

      Posiblemente, haya observado el identificador en blanco (_) antes de "image/png". Esto es necesario porque Go no le permite importar paquetes que no se utilicen en todo el programa. Cuando se incluye el identificador en blanco, el valor de la importación se descarta para que solo se produzca el efecto secundario de la importación. Esto significa que, aunque nunca invoquemos el paquete image/png en nuestro código, podemos importarlo para obtener el efecto secundario.

      Es importante conocer el momento en que se debe importar un paquete debido a su efecto secundario. Sin el registro adecuado, es probable que su programa se compile y no funcione correctamente cuando se ejecute. Los paquetes de la biblioteca estándar declararán la necesidad de este tipo de importación en su documentación. Si escribe un paquete que requiere una importación para obtener efectos secundarios, también debe asegurarse de que la instrucción init() que esté usando se documente para que los usuarios que importen su paquete puedan utilizarlo correctamente.

      Conclusión

      A través de este tutorial, aprendió que la función init() se carga antes que el resto del código de su paquete y que puede realizar tareas específicas para un paquete, como inicializar un estado deseado. También aprendió que el orden en el que el compilador ejecuta varias instrucciones init() depende del orden en el que carga los archivos de origen. Si desea obtener más información sobre init(), consulte la documentación oficial de Golang o lea los comentarios acerca de la función en la comunidad de Go.

      Puede leer más sobre funciones en nuestro artículo Cómo definir e invocar funciones en Go o consultar toda la serie Cómo programar en Go.



      Source link

      Функция init в Go


      Введение

      В Go заданнная функция init() выделяет элемент кода, который запускатся до любой другой части вашего пакета. Этот код запускается сразу же после импорта пакета, и его можно использовать при необходимости инициализации приложения в определенном состоянии, например, если для запуска приложения требуется определенная конфигурация или набор ресурсов. Также используется при импорте побочных эффектов, то есть при применении методики установки состояния программы посредством импорта определенного пакета. Часто используется для регистрации одного пакета в другом, чтобы программа рассматривала правильный код для этой задачи.

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

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

      Предварительные требования

      Для некоторых из приведенных в этой статье примеров вам потребуется следующее:

      .
      ├── bin
      │
      └── src
          └── github.com
              └── gopherguides
      

      Декларирование init()

      Каждый раз, когда вы декларируете функцию init(), Go загружает и запускает ее прежде всех остальных элементов этого пакета. Чтобы продемонстрировать это, в данном разделе мы подробно покажем определение функции init() и ее влияние на выполнение пакета.

      Вначале рассмотрим следующий пример кода без функции init():

      main.go

      package main
      
      import "fmt"
      
      var weekday string
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      В этой программе мы декларировали глобальную переменную с именем weekday. По умолчанию значение weekday представляет собой пустую строку.

      Запустим этот код:

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

      Output

      Today is

      Мы можем заполнить пустую переменную, используя функцию init() для инициализации значения weekday как текущего дня. Добавьте следующие выделенные строки в файл main.go:

      main.go

      package main
      
      import (
          "fmt"
          "time"
      )
      
      var weekday string
      
      func init() {
          weekday = time.Now().Weekday().String()
      }
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      В этом коде мы импортировали и использовали пакет time для получения текущего дня недели (Now(). Weekday(). String()), а затем использовали init() для инициализации weekday с этим значением.

      Теперь при запуске программы она выводит текущий день недели:

      Output

      Today is Monday

      Хотя это показывает принцип работы функции init(), гораздо чаще init() используется при импорте пакета. Это может быть полезно, если вам требуется выполнить в пакете определенные задачи по настройке, прежде чем использовать этот пакет. Чтобы продемонстрировать это, создадим программу, которая потребует определенной инициализации для обеспечения требуемой работы пакета.

      Инициализация пакетов при импорте

      Вначале мы напишем код, который выбирает случайное существо из среза и выводит его. Однако мы не будем использовать init()в начальной программе. Это лучше покажет стоящую перед нами проблему и возможность ее решения с помощью функции init().

      Создайте в каталоге src/github.com/gopherguides/ папку с именем creature с помощью следующей команды:

      Создайте в папке creature файл с именем creature.go:

      • nano creature/creature.go

      Добавьте в этот файл следующее содержание:

      creature.go

      package creature
      
      import (
          "math/rand"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Этот файл определяет переменную с именем creatures, для которой в качестве значений инициализирован набор морских существ. Также она имеет экспортируемую функцию Random, которая выводит случайное значение переменной creatures.

      Сохраните и закройте этот файл.

      Теперь создайте пакет cmd, который мы используем для записи функции main() и вызова пакета creature.

      На том же уровне файла, где мы создали папку creature, создайте папку cmd с помощью следующей команды:

      Создайте в папке cmd файл с именем main.go:

      Добавьте в файл следующие строчки:

      cmd/main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/creature"
      )
      
      func main() {
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
      }
      

      Здесь мы импортировали пакет creature и использовали в функции main() функцию creature.Random(), чтобы получить случайное существо и вывести его четыре раза.

      Сохранение и выход из main.go.

      Теперь у нас написана вся программа. Однако перед запуском этой программы нам также потребуется создать несколько файлов конфигурации, чтобы обеспечить правильную работу нашего кода. Go использует Go Modules для настройки зависимостей пакетов при импорте ресурсов. Эти модули представляют собой файлы конфигурации в каталоге вашего пакета, которые указывают компилятору, откуда импортировать пакеты. Хотя изучение модулей не входит в состав настоящей статьи, мы можем написать несколько строк конфигурации, чтобы этот пример работал локально.

      Создайте в каталоге cmd файл с именем go.mod:

      После открытия файла добавьте в него следующий код:

      cmd/go.mod

      module github.com/gopherguides/cmd
       replace github.com/gopherguides/creature => ../creature
      

      Первая строка файла указывает компилятору, что созданный нами пакет cmd на самом деле представляет собой пакет github.com/gopherguides/cmd. Вторая строка указывает компилятору, что каталог github.com/gopherguides/creature можно найти на локальном диске в каталоге ../creature.

      Сохраните и закройте файл. Затем создайте файл go.mod в каталоге creature:

      Добавьте в файл следующую строчку кода:

      creature/go.mod

       module github.com/gopherguides/creature
      

      Это говорит компилятору, что созданный нами пакет creature на самом деле является пакетом github.com/gopherguides/creature. Без этого пакет cmd не будет знать, откуда импортировать этот пакет.

      Сохраните и закройте файл.

      Теперь у вас должны быть следующая структура каталогов и расположение файлов:

      ├── cmd
      │   ├── go.mod
      │   └── main.go
      └── creature
          ├── go.mod
          └── creature.go
      

      Мы завершили настройку и теперь можем запустить программу main с помощью следующей команды:

      Это даст нам следующее:

      Output

      jellyfish squid squid dolphin

      При запуске этой программы мы получили четыре значения и вывели их. Если мы запустим программу несколько раз, результаты всегда будут одинаковыми, хотя ожидается случайный результат. Это связано с тем, что пакет rand создает псевдослучайные числа, постоянно дающие один и тот же результат для одного и того же начального состояния. Чтобы получить действительно случайное число, мы можем задать начальное случайное число для пакета или задать изменяющийся источник, чтобы при каждом запуске программы состояние было разным. В Go обычно используется текущее время в качестве начального случайного числа для пакета rand.

      Поскольку нам нужно, чтобы пакет creature работал с функцией случайных чисел, откроем этот файл:

      • nano creature/creature.go

      Добавьте в файл creature.go следующие выделенные строки:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          rand.Seed(time.Now().UnixNano())
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      В этом коде мы импортировали пакет time и использовали Seed() для использования текущего времени в качестве начального случайного числа. Сохраните и закройте файл.

      Теперь при запуске программы мы будем получать действительно случайный результат:

      Output

      jellyfish octopus shark jellyfish

      При каждом запуске программы результаты будут оставаться случайными. Однако эта реализация кода также не идеальна, поскольку при каждом вызове creature.Random() повторно задается начальное случайное число для пакета rand посредством вызова функции rand.Seed(time.Now(). UnixNano()) еще раз. Повторное начальное случайное число может совпадать с предыдущим, если время на внутренних часах не изменилось. Это может вызвать повторы шаблонов случайных чисел или увеличение времени обработки в связи с ожиданием смены времени на часах.

      Для решения этой проблемы мы можем использовать функцию init(). Обновим файл creature.go:

      • nano creature/creature.go

      Добавьте следующие строчки кода:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func init() {
          rand.Seed(time.Now().UnixNano())
      }
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Добавление функции init() указывает компилятору, что при импорте пакета creature необходимо запустить функцию init() один раз и задать начальное случайное число для генерирования случайных чисел. Так нам не придется лишний раз выполнять код. Если мы запустим программу, мы по-прежнему будем получать случайные результаты:

      Output

      dolphin squid dolphin octopus

      В этом разделе мы показали, как использование функции init() может обеспечить проведение правильных расчетов или операций инициализации перед использованием пакета. Далее мы рассморим использование нескольких выражений init() в пакете.

      Использование нескольких экземпляров init()

      В отличие от функции main(), которую можно декларировать только один раз, функцию init() можно декларировать в пакете много раз. Однако при использовании нескольких экземпляров функции init() может быть сложно понять, какой из них имеет приоритет перед другими. В этом разделе мы покажем, как контролировать несколько выражений init().

      В большинстве случаев функции init() выполняются в том порядке, в каком они содержатся в программе. Рассмотрим в качестве примера следующий код:

      main.go

      package main
      
      import "fmt"
      
      func init() {
          fmt.Println("First init")
      }
      
      func init() {
          fmt.Println("Second init")
      }
      
      func init() {
          fmt.Println("Third init")
      }
      
      func init() {
          fmt.Println("Fourth init")
      }
      
      func main() {}
      

      Если мы запустим программу с помощью следующей команды:

      Мы получим следующий результат:

      Output

      First init Second init Third init Fourth init

      Обратите внимание, что каждое выражение init() выполняется в том порядке, в каком оно поступает в компилятор. Однако порядок вызова функций init() может быть не так легко определить.

      Рассмотрим более сложную структуру пакета, где у нас имеется несколько файлов, для каждого из которых декларирована собственная функция init(). Для иллюстрации мы создадим программу, передающую переменную с именем message и выводящую ее.

      Удалите каталоги creature и cmd и их содержимое из предыдущего раздела и замените их следующими каталогами и структурой файлов:

      ├── cmd
      │   ├── a.go
      │   ├── b.go
      │   └── main.go
      └── message
          └── message.go
      

      Теперь добавим содержимое каждого файла. В файле a.go добавьте следующие строчки:

      cmd/a.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          fmt.Println("a ->", message.Message)
      }
      

      Этот файл содержит одну функцию init(), которая выводит значение message.Message из пакета message.

      Добавьте следующие строки в файл b.go:

      cmd/b.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          message.Message = "Hello"
          fmt.Println("b ->", message.Message)
      }
      

      В файле b.go имеется одна функция init(), которая задает для message.Message значение Hello и выводит его.

      Создадим файл main.go, который будет выглядеть следующим образом:

      cmd/main.go

      package main
      
      func main() {}
      

      Этот файл ничего не делает, но предоставляет начальную точку для запуска программы.

      В заключение создайте файл message.go как показано здесь:

      message/message.go

      package message
      
      var Message string
      

      Наш пакет message декларирует экспортированную переменную Message.

      Для запуска программы выполните следующую команду в каталоге cmd:

      Поскольку в папке cmd имеется несколько файлов Go, составляющих пакет main, нам нужно указать компилятору, что все файлы .go в папке cmd должны быть скомпилированы. Использование *.go указывает компилятору на необходимость загрузить все файлы из папки cmd, которые заканчиваются на .go. Если мы отправим команду go run main.go, программа не будет компилироваться, поскольку она не увидит код в файлах a.go и b.go.

      Результат будет выглядеть следующим образом:

      Output

      a -> b -> Hello

      Согласно спецификации инициализации пакетов в языке Go, при наличии в пакете нескольких файлов они обрабатываются в алфавитном порядке. В связи с этим, когда мы первый раз распечатали message.Message из a.go, значение было пустым. Значение не было инициализировано до запуска функции init() из b.go.

      Если бы мы изменили имя файла с a.go на c.go, результат был бы другим:

      Output

      b -> Hello a -> Hello

      Теперь компилятор вначале получает b.go и значение message.Message уже инициализировано как Hello при появлении функции init() в c.go.

      Такое поведение может вызвать проблемы при выполнении кода. При разработке программного обеспечения имена файлов часто меняются, и, в связи с особенностями функции init(), изменение имен файлов может изменить последовательность обработки функций init(). Это может привести к нежелательному изменению выводимых программой результатов. Чтобы обеспечить стабильное поведение при инициализации, рекомендуется при сборке указывать компилятору несколько файлов из одного пакета в алфавитном порядке. Чтобы обеспечить загрузку всех функций init() по порядку, можно декларировать все эти функции в одном файле. Это предотвратит изменение порядка даже в случае изменения имен файлов.

      Помимо обеспечения порядка выполнения функций init(), вам также следует избегать управления состояниями в пакете с помощью глобальных переменных, т. е. переменных, которые доступны во всем пакете. В предыдущей программе переменная message.Message была доступна всему пакету и поддерживала состояние программы. В связи с таким доступом, выражения init() могли изменять переменную и снижать прогнозируемость работы программы. Чтобы избежать этого, попробуйте работать с переменными в контролируемых пространствах с минимальным доступом, обеспечивающим возможность работы программы.

      Итак, в одном пакете может быть несколько деклараций init(). Однако это может создать нежелательные эффекты и сделать программу более сложной для чтения или осложнить прогнозирование ее работы. Если не использовать несколько выражений init() или объединять их в одном файле, поведение программы не изменится в случае перемещения файлов или смены имен файлов.

      Теперь посмотрим, как функция init() используется для импорта с побочными эффектами.

      Использование init() для побочных эффектов

      Иногда в Go требуется импортировать пакет не ради его содержимого, но ради побочных эффектов, возникающих при импорте пакета. Часто это означает, что в импортируемом коде содержится выражение init(), выполняемое перед любым другим кодом, что позволяет разработчику изменять состояние программы при запуске. Такая методика называется импортированием для побочного эффекта.

      Импортирование для побочного эффекта обычно используется для функции регистрации в коде, чтобы пакет знал, какую часть кода нужно использовать вашей программе. Например, в пакете imageфункция image.Decode должна знать, какой формат она пытается декодировать (jpg, png, gif и т. д.), прежде чем ее можно будет выполнить. Для этого можно предварительно импортировать определенную программу с побочным эффектом выражения init().

      Допустим, вы пытаетесь использовать image.Decode в файле .png со следующим фрагментом кода:

      Sample Decoding Snippet

      . . .
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      . . .
      

      Программа с этим кодом будет скомпилирована, однако при попытке декодирования изображения png мы получим сообщение об ошибке.

      Для устранения этой проблемы нужно предварительно зарегистрировать формат изображения для image.Decode. К счастью, пакет image/png содержит следующее выражение init():

      image/png/reader.go

      func init() {
          image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
      }
      

      Поэтому, если мы импортируем image/png в сниппет для декодировки, функция image.RegisterFormat() пакета image/png будет запущена до любого нашего кода:

      Sample Decoding Snippet

      . . .
      import _ "image/png"
      . . .
      
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      

      Эта функция задаст состояние и зарегистрирует необходимость использования версии png функции image.Decode(). Эта регистрация происходит в качестве побочного эффекта импорта image/png.

      Возможно вы заметили пустой идентификатор (_) перед "image/png". Он необходим, потому что Go не позволяет импортировать пакеты, которые не используются в программе. При указании пустого идентификатора значение импорта отбрасывается так, что действует только побочный эффект импорта. Это означает, что хотя мы не вызываем пакет image/png в нашем коде, мы можем импортировать его ради побочного эффекта.

      Важно понимать, когда нужно импортировать пакет ради его побочного эффекта. Без надлежащей регистрации существует вероятность, что программа скомпилируется, но не будет правильно работать при запуске. В документации к пакетам стандартной библиотеки декларируется потребность в этом типе импорта. Если вы напишете пакет, который требуется импортировать ради побочного эффекта, вам также следует убедиться, что используемое выражение init() задокументировано, и импортирующие ваш пакет пользователи смогут правильно его использовать.

      Заключение

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

      Дополнительную информацию о функциях можно найти в статье Определение и вызов функций и в других статьях из серии статей по программированию на Go.



      Source link