Trabalhando com generics

Se você está no mundo Go a algum tempo, com certeza já ouviu falar desse carinha ai. Para aqueles que estão chegando agora, generics é uma das features mais pedidas/aguardadas desde que a linguagem foi liberada para o mundo.

Como já falamos em outros posts, Go é uma linguagem de tipagem forte, ou seja, sempre precisamos declarar o tipo da variável, parâmetros de uma função e seus retornos. Isso acaba fazendo com que, em algumas ocasiões, seja necessário duplicar nosso código só para que ele atenda dois tipos diferentes de dados.

Um exemplo básico desse problema seria uma função que soma uma lista de valores, onde sem generics, caso fossemos somar int64 e float64, seria necessário criar duas funções basicamente iguais, só mudando o tipo de dado do parâmetro e do retorno.

Leia mais »

Formatando strings para logs e mensagens

Se você já trabalha com Go a algum tempo, muito provavelmente você já conhece e utiliza o %s, %d e o \n. Pois bem, nesse post vamos abordar os principais os verbos disponíveis para te ajudar na hora de formatar uma string em Go.

Se você é novo em Go, saiba que os verbos que vamos abordar nesse post podem ser utilizados com as funções Printf, Sprintf e Errorf do package fmt, assim como as funções Fatalf, Panicf e Printf do package log.

Para começar, vamos a um exemplo bem simples utilizando somente os dois verbos que já comentamos. Digamos que nosso programa, sempre que alguém pede um novo café, exibe o nome da pessoa e a quantidade de café que ela consumiu no dia.

func main() {
    name := "Tiago"
    coffee := 5

    fmt.Printf("Olá %s, você já bebeu %d cafés hoje", name, coffee)
}

Ao ser executado, o %s será substituido pelo conteúdo da váriavel name, e o %d pela vafiável coffee.

Leia mais »

Aguardando execução de múltiplas goroutines

Pense em um cenário onde você abra centenas ou talvez milhares de goroutines, porém que essa quantidade não seja fixa.

Utilizar um channel para controlar a quantidade de goroutines que já finalizaram a execução pode ser muito trabalhoso e em alguns casos até impossível já que, para um channel simples, teriamos que esperar a execução de cada goroutine antes de iniciar a próxima.

Se resolvemos usar um channel com buffer, teremos que especificar sua capacidade durante sua criação, o que também pode nos levar a criar um buffer muito pequeno, onde não conseguimos iniciar todas as goroutines que queremos de uma só vez.

Se você quiser saber mais sobre goroutines e channels antes de continuar, vou deixar aqui a lista com os 3 últimos posts onde abordamos esses assuntos:

Bom, mas se gerir a execução das goroutines com channels pode ser trabalhoso ou em alguns casos até mesmo inviável, como podemos fazer?

Leia mais »

Trabalhando com switch/case

Dando continuidade nos nossos posts sobre comandos core da linguagem, nesse post vamos falar sobre o switch/case.

Esse comando é bem conhecido e existe em quase toda linguagem de programação. Se você nunca ouviu falar dele, tente imaginá-lo como uma forma mais curta de encadear um monte de if - else.

Uma diferença que vale ressaltar entre Go e as outras linguagens é que ao contrário das outras linguagens, o Go só executa o caso selecionado eliminando assim a necessidade de colocar um break.

Em sua forma mais básica, ele recebe um valor no inicio e depois checa se esse valor é igual algum dos casos definidos, e caso não seja, executa a caso padrão.

Para iniciar, vamos a um exemplo básico.

switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("MAC OS")
case "linux":
    fmt.Println("Linux")
default:
    fmt.Printf("%s \n", os)
}
Leia mais »

O que são e como utilizar channels

Algumas semanas atrás fizemos um post falando sobre o que são e como funcionam as goroutines (link do post). Nesse post, mencionamos sobre os channels quando fizemos um “fix” para que o programa esperasse a execução da goroutine antes de terminar.

No post de hoje vamos aprofundar um pouco sobre o que são os channels e como podemos usá-los.

Podemos pensar em channels como sendo uma espécie de tunel de comunidação entre goroutines, onde uma goroutine consegue enviar informações para outra antes mesmo de terminar sua execução. Nesse mesmo cenário, a goroutine que recebe a informação, ficaria pausada até as informações chegarem.

Quando vamos declarar um channel ou inicializa-lo, precisamos associar um tipo de dado a ele. Esse tipo de dado é o tipo que ele estará apto à transportar entre goroutines.

Leia mais »

Convertendo tipos de dados

Em linguagens fortemente tipadas, muitas vezes precisamos fazer conversões dos tipos das nossas variáveis para poder executar algum tipo de função interna. Uma das conversões mais comum que acontecem é a de int ou float para string e vice versa.

Também pode acontecer conversões dentro do mesmo “grupo”, por exemplo de int32 para int16. Porém, embora pertençam ao mesmo “grupo”, temos que ficar atentos ao tamanho máximo que cada um desses tipos comportam, pois caso o valor extrapole a capacidade do tipo destino, a conversão irá dar errado.

var i32 int32 = 10
i16 := int16(i32)
fmt.Printf("%T - %d", i16, i16)

No exemplo acima, tudo funcionará bem, pois 10 é um valor que os dois tipos de dados comportam. Agora se o valor de i32 fosse 32769, a conversão para int16 retornaria -32767, e se fosse algo maior ainda, como por exemplo 132769, a conversão retornaria 1697.

Leia mais »

O que são e como funcionam as Goroutines

Na semana passada explicamos a diferença entre concorrência e paralelismo (link do post), o que serviu de base para o post de hoje, onde vamos explicar as famosas goroutines.

Goroutines são funções ou métodos executados em concorrência. Podemos pensar nelas como uma especie de lightweight thread que são gerenciadas pelo runtime do Go.

Chamamos de lightweight thread pois o custo para sua criação é muito menor quando comparada com um thread de verdade. Outro ponto positivo é que o runtime consegue aumentar ou diminuir a quantidade de goroutines de acordo com a necessidade da aplicação, enquanto o número de thread normalmente é fixo.

Leia mais »

Entendendo os tipos de dados e suas capacidades

Embora seja um assunto não muito abordado, pelo menos no meu ponto de vista, entender todos os tipos de dados que uma linguagem oferece, ajuda muito no momento de criar um programa, principalmente do ponto de vista de performance e eficiência no consumo de recursos.

Por isso, no post de hoje vamos ver todos os tipos de dados básicos que temos na linguagem e suas capacidades.

Bool

O tipo booleano, é um dos tipos mais básicos e comum no mundo da tecnologia.

Embora em algumas linguagens ele possa ser definido com 0 e 1, em Go os únicos valores aceitos são true ou false.

String

Conjunto de todos os caracteres de 8-bit, ou seja, qualquer tipo de texto.

Uma string pode ser vazia, mas não pode ser nula (nil).

Leia mais »

Entenda a diferença entre concorrência e paralelismo

Na próxima semana vou fazer um post falando sobre go routines, por isso hoje vamos entender o conceito que há por trás dessas crianças.

Embora algumas pessoas usem os dois termos para descrever a mesma coisa, eles com certeza não são. Particularmente, eu sempre dizia que Go executava suas go routines em paralelo, o que faz alusão a serem executadas com paralelismo, o que é uma grande mentira já que que Go é uma linguagem que trabalha com concorrência e não paralelismo.

Mas afinal o que isso quer dizer e qual a diferença?

Concorrência é basicamente a capacidade de lidar com várias coisas de uma só vez, enquanto paralelismo é a capacidade de lidar com várias coisas ao mesmo tempo.

Se você achou que parece ser a mesma coisa, calma… vamos dar um exemplo para tentar deixar mais claro.

Leia mais »

Qual a diferença entre valor e referência (ponteiro)

Hoje vamos abordar um tema muito interessante e que é comum a quase todas as linguagens, valor vs referência (também conhecida como ponteiro).

Primeiramente, não podemos nos esquecer que, cada variável que criamos, independente do seu tipo, assim como arrays, slices e maps, são espaços alocados em memória.

Outro ponto importante que temos que ter em mente é que, os parâmetros de uma função também são variáveis.

Para ajudar na explicação, vamos criar uma função com 2 parâmetros, onde o primeiro espera um valor e o segundo um ponteiro.

func ValPoint(valor string, ponteiro *string) {
    fmt.Println(valor)
    fmt.Println(ponteiro)
}
Leia mais »