A diferença entre ponteiro e valor nos métodos

Quando escrevemos métodos em Go, uma das decisões importantes é se devemos passar a struct por valor ou por ponteiro. A escolha pode impactar a performance, o comportamento do nosso código e a alocação de memória. Neste post, vamos explorar essa diferença com um exemplo prático e entender em quais situações cada abordagem é mais adequada.

Vamos começar com uma pequena struct e dois métodos: um onde a struct é passada por valor e outro por ponteiro.

Leia mais »

Tudo sobre a pasta internal

Embora não haja uma obrigatoriedade em como organizar projetos Go, um recurso pouco explorado – pelo menos da maneira correta -, é a utilização da pasta internal .

A pasta internal é um recurso poderoso que permite organizar e encapsular código que não deve ser utilizado por outros packages. Em outras palavras, ela é perfeita para armazenar funções auxiliares, estruturas de dados, domínio e outros detalhes de implementação.

Como funciona

De forma simples, quando o compilador Go encontra uma pasta internal, ele a trata como um package separado e não exporta nenhum dos seus símbolos para fora do package ao qual ela pertence. Isso significa que qualquer código na pasta internal só pode ser acessado por outros arquivos dentro do mesmo package “principal”.

Leia mais »

Dicas e práticas sobre error handling em Go

Hoje vamos falar sobre um tópico crucial em Go, como lidar com erros. Em Go, erros são valores importantes e devem ser tratados com cuidado.

Definindo um error

Primeiramente, em Go, um erro é qualquer valor que implementa a interface error. Esta interface tem um único método: Error() string.

type MyError struct {
    Message string
    Code    int
}

func (e *MyError) Error() string {
    return fmt.Sprintf("error: %s, code: %d", e.Message, e.Code)
}
Leia mais »

Como injetar valores em variáveis com ldflags

Uma feature pouco conhecida para quem está iniciando na linguagem Go, é a capacidade de injetarmos valores em variáveis durante o processo de build. Embora possa parecer um tanto quanto estranho fazer isso, essa técnica nos possibilita adicionar informações como versão, data e commit do build, sem a necessidade de commitar essas informações. Embora possamos criar essas variáveis no mesmo arquivo onde estará a função main da aplicação, eu prefiro fazer algo que possa ser reutilizado.

Definindo as variáveis de build

Por isso, pensando em um repositório onde haverão várias aplicações, ou até mesmo em uma estrutura de monorepo, vamos criar um package dentro da pasta pkg chamado build. Dentro do package, vamos criar um arquivo chamado version.go com o seguinte conteúdo.

Leia mais »
green tree

Qual a diferença entre reflect.TypeOf e reflect.ValueOf

Se você é novo no mundo Go, muito provavelmente ainda não tenha trabalhado diretamente com o package reflect. Na verdade, é completamente normal pessoas que já trabalhem com a linguagem a algum tempo e ainda não tenham utilizado tal package.

Isso por que esse package tem uma utilidade muito específica. Não que todos os packages não tenham, ou pelo menos deveriam ter. Mas no caso do reflect, sua especificidade é tão grande, que mesmo em sistemas que o utilizam, ele provavelmente fica em alguma parte obscura, complexa e pouco mantida.

Tendo dito tudo isso e, ao mesmo tempo falado muito pouco, nesse post explicarei tudo o que você precisa saber sobre o package reflect e suas funções TypeOf e ValueOf.

Reflect

Antes de entender o que as funções TypeOf e ValueOf tem a oferecer, falemos um pouco sobre a finalidade do package reflect.

Leia mais »
blue threads

Quais as diferenças entre goroutines e threads

Um pensamento muito comum para quem está chegando na linguagem Go, ou só conhece a linguagem pelo o que “ouviu na rua”, é achar que as goroutines são threads.

Acontece que na prática, goroutines e threads, embora parecidas, são coisas bem diferentes. Vejamos.

Uma thread, nada mais é do que um espaço que o sistema operacional aloca na memória. Normalmente, uma thread ocupa um espaço de 2mb.

Quando uma goroutine é criada, ela normalmente ocupa 2kb de espaço em memória, o que dá aproximadamente 1% do tamanho de uma thread. Ou seja, para uma goroutine simples, ter uma thread inteira só para ela acaba sendo um grande desperdício de memória.

Embora modificar o tamanho das threads do sistema operacional pudesse resolver o problema de desperdício de memória em goroutines simples, isso acabaria gerando um grande problema para goroutines mais complexas, onde podemos ver seu consumo chegar na casa de gigas.

Leia mais »
photo of gray faucet

Como resolver memory leaks em maps

Uma das formas mais comuns de se fazer cache em aplicações Go é utilizando um map. Se você já fez isso, deve ter notado um aumento gradual no consumo de memória, e que normalmente após um restart da máquina ou pod volta ao “normal”.

Isso acontece devido a forma como o map funciona. Por isso, antes de ver o que podemos fazer para resolver esse tipo de problema, vamos entender melhor o map.

Para exemplificar o problema, vamos considerar uma variável do tipo map[int][128]byte, que será “carregada” com 1 milhão de elementos e que na sequência serão removidas.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := 1_000_000
	m := make(map[int][128]byte)
	printAlloc()

	for i := 0; i < n; i++ {
		m[i] = [128]byte{}
	}
	printAlloc()

	for i := 0; i < n; i++ {
		delete(m, i)
	}

	runtime.GC()
	printAlloc()
	runtime.KeepAlive(m)
}

func printAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%d KB\\n", m.Alloc/1024)
}
Leia mais »
high angle view of people on bicycle

Como resolver race condition com sync/atomic

Algum tempo atrás, publicamos um post aqui no blog explicando como resolver race condition utilizando mutex e channels.

Embora o conteúdo daquele post continue sendo válido, para alguns casos mais simples de race condition, como por exemplo o do post, podemos utilizar o package sync/atomic para nos auxiliar.

Para dar o ponta pé inicial, vamos escrever um código que não irá funcionar corretamente por haver race condition.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var total int64

	var wg sync.WaitGroup

	for i := 0; i < 50; i++ {
		wg.Add(1)

		go func() {
			for c := 0; c < 1000; c++ {
				total += int64(c)
			}
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Println(total)
}
Leia mais »

Orientação a objetos em Go

Go não é uma linguagem de programação orientada a objetos. No entanto, algumas de suas features fazem com que seja possível trabalhar com algo muito parecido.

Nesse post vou falar sobre como podemos utilizar essas features para ter alguns comportamentos parecidos com orientação a objetos.

Classe

Go não implemente o conceito de classe como podemos encontrar em outras linguagens. Porém, para suprimir essa necessidade, podemos utilizar as structs ou estruturas.

type Foo struct {}

type bar struct {}

Esse tipo de dado composto nos permite criar campos, que podemos pensar como se fossem atributos. Também é possível adicionar métodos as structs.

Leia mais »

Múltiplos channels e a cláusula select

Dando continuidade ao nosso estudo de goroutines e channels, nesse post vamos falar sobre uma cláusula pouco utilizada.

Antes de começar, vou deixar os links para os outros posts caso você tenha perdido algum da série.

A cláusula select é utilizada para que uma função consiga trabalhar com múltiplos channels. Ela bloqueia a execução da função até que um dos channels esteja pronto para ser executado. Caso mais de um channel esteja pronto para ser executado, ela selecionará de forma aleatória qual executar.

Para tentar ficar um pouco mais claro, vamos escrever um pequeno programa para ilustrar o comportamento.

Leia mais »