├── README.md ├── collections └── main.go ├── errors └── main.go ├── flow └── main.go ├── functions └── main.go ├── generics └── main.go ├── go.mod ├── interfaces └── main.go ├── packages ├── main.go ├── ping │ └── messenger.go └── pong │ └── messenger.go ├── structs └── main.go └── variables └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # Aprendendo go 2 | 3 | Esse tutorial é destinado para pessoas que desejam conhecer a linguagem go de forma rápida, baseado em iteração e experimentação. 4 | 5 | Ordem dos assuntos: 6 | 7 | 1. [Variables](https://github.com/mauricioabreu/tutorial-de-go/tree/main/variables) 8 | 2. [Flow](https://github.com/mauricioabreu/tutorial-de-go/tree/main/flow) 9 | 3. [Collections](https://github.com/mauricioabreu/tutorial-de-go/tree/main/collections) 10 | 4. [Functions](https://github.com/mauricioabreu/tutorial-de-go/tree/main/functions) 11 | 5. [Structs](https://github.com/mauricioabreu/tutorial-de-go/tree/main/structs) 12 | 6. [Errors](https://github.com/mauricioabreu/tutorial-de-go/tree/main/errors) 13 | 7. [Packages](https://github.com/mauricioabreu/tutorial-de-go/tree/main/packages) 14 | 8. [Interfaces](https://github.com/mauricioabreu/tutorial-de-go/tree/main/interfaces) 15 | 9. [Generics](https://github.com/mauricioabreu/tutorial-de-go/tree/main/generics) 16 | -------------------------------------------------------------------------------- /collections/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Coleções são maneiras de agrupar dados 6 | // Em go temos dois tipos para lidar com coleções: arrays e mapas 7 | // E os slices? Aguarde e verás... 8 | 9 | func main() { 10 | // Array é um tipo que é inicializado com um total de valores que ele terá 11 | var names [3]string 12 | names[0] = "Mauricio" 13 | names[1] = "Lais" 14 | names[2] = "Bentinho" 15 | fmt.Printf("My name: %s\n", names[0]) 16 | fmt.Println("All names:", names) 17 | // Arrays também podem ser inicializados "inline" 18 | otherNames := [3]string{"Mickey", "Minie", "Pluto"} 19 | fmt.Println(otherNames) 20 | // Podemos omitir o tamanho do array 21 | plusOtherNames := [...]string{"João", "Maria"} 22 | fmt.Println(plusOtherNames) 23 | 24 | // Slices são, em essência, arrays. Porém, são arrays que podem crescer 25 | // Um slice aponta pra arrays - é um "wrap" sobre arrays 26 | // Arrays não podem ter um total de elementos maior do que o definido em sua 27 | // inicialização 28 | // Esse conceito também existe em outras linguagens, como Rust com arrays e vetores 29 | // Para definir um slice, podemos omitir seu tipo 30 | colors := []string{"Blue", "Brown", "Green", "Red"} 31 | fmt.Println("Cores:", colors) 32 | 33 | // É possível iterar em arrays e slices usando for range 34 | for idx, color := range colors { 35 | fmt.Printf("Color at %d index: %s\n", idx, color) 36 | } 37 | 38 | // Podemos fatiar um slice, usando a seguinte sintaxe 39 | firstTwoColors := colors[0:2] 40 | fmt.Println("First two colors:", firstTwoColors) 41 | lastTwoColors := colors[2:4] 42 | fmt.Println("Last two colors:", lastTwoColors) 43 | 44 | // É possível criar um slice também usando make 45 | // make é uma função do go usada para criar alguns tipos como slices, maps e channels 46 | primeNumbers := make([]int, 0) 47 | primeNumbers = append(primeNumbers, 2, 3, 5, 7, 11, 13, 17, 19) 48 | fmt.Println("Prime numbers:", primeNumbers) 49 | 50 | // Mapas são como dicionários do Python ou Hash do Ruby 51 | // Esse map é um dicionário com chaves string e valores inteiros 52 | // Em go, os dicionários não podem ter tipos misturados 53 | // Importante: mapas em go não são ordenados 54 | users := map[string]int{ 55 | "Mauricio": 32, 56 | "Lais": 29, 57 | "Bentinho": 2, 58 | } 59 | fmt.Printf("My name is %s and my age is %d\n", "Mauricio", users["Mauricio"]) 60 | // Podemos iterar no par chave/valor como em outras linguagens 61 | for name, age := range users { 62 | fmt.Printf("Name %s and age %d\n", name, age) 63 | } 64 | // Podemos incluir mais uma usuário no mapa usando uma sintaxe similar a outras linguagens 65 | users["Catarina"] = 4 66 | // E imprimir de uma forma que ajuda a depuração do código 67 | fmt.Printf("Users: %#v\n", users) 68 | // Podemos deletar usando a função delete 69 | // Essa função deleta usando a referência 70 | delete(users, "Catarina") 71 | // Testando se ainda existe o elemento 72 | // A idade retorna 0 porque é o valor default do inteiro 73 | // Diferente de Rust, Go não tem recursos para mostrar a ausência de um valor 74 | catarinaAge, ok := users["Catarina"] 75 | fmt.Printf("Usuário existe? %t Se sim, qual é a idade? %d\n", ok, catarinaAge) 76 | } 77 | -------------------------------------------------------------------------------- /errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // Um erro é uma maneira de comunicar que algo na função deu errado 9 | // Em go, existe um pacote errors destinado a ajudar a cuidar de erros 10 | 11 | // Erros podem ser comparáveis também, para tomar a decisão correta quando 12 | // um erro acontecer. Por exemplo, é importante que sua lib tenha os erros exportados 13 | // para que clientes da lib possam saber o que fazer com cada tipo de erro 14 | var ( 15 | ErrIDontLikeNumber2 = errors.New("number 2 not allowed") 16 | ErrIDontLikeNumber3 = errors.New("number 3 not allowed") 17 | ) 18 | 19 | func doSomething(status bool) error { 20 | if status { 21 | return nil 22 | } 23 | return errors.New("bad status") 24 | } 25 | 26 | func withDifferentErrors(number int) error { 27 | if number == 2 { 28 | return ErrIDontLikeNumber2 29 | } 30 | if number == 3 { 31 | return ErrIDontLikeNumber3 32 | } 33 | return nil 34 | } 35 | 36 | func main() { 37 | // Esse é um jeito de verificar se a função tem erro 38 | // Mas poderia ser também 39 | // err := doSomething(true) 40 | // if err != nil { 41 | // fmt.Println("Sem erro") 42 | // } 43 | if err := doSomething(true); err != nil { 44 | fmt.Println("Tem erro?", err) 45 | } 46 | if err := doSomething(false); err != nil { 47 | fmt.Println("Tem erro?", err) 48 | } 49 | err := withDifferentErrors(2) 50 | if err == ErrIDontLikeNumber2 { 51 | fmt.Println("Number not allowed:", err) 52 | } 53 | if err == ErrIDontLikeNumber3 { 54 | fmt.Println("Number not allowed:", err) 55 | } 56 | 57 | // É possível também verificar se um erro faz parte de outro 58 | internalError := errors.New("bad input for function") 59 | anotherError := errors.New("any error") 60 | wrappedError := fmt.Errorf("something went wrong: %w", internalError) 61 | if errors.Is(wrappedError, internalError) { 62 | fmt.Println("Yes, error is wrapped with internalError") 63 | } 64 | if errors.Is(wrappedError, anotherError) { 65 | fmt.Println("Yes, error is wrapped with anotherError") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /flow/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Em go, temos duas maneiras de fazer controle de fluxo: if e switch 6 | func main() { 7 | // if/else tem a seguinte sintaxe 8 | number := 10 9 | if number > 10 { 10 | fmt.Printf("Number %d is greater than 10", number) 11 | } else { 12 | fmt.Printf("Number %d not greater than 10", number) 13 | } 14 | 15 | // Switch pode ser avaliado em torno de uma variável 16 | switch number { 17 | case 9: 18 | fmt.Println("9") 19 | case 10: 20 | fmt.Println("10") 21 | } 22 | 23 | // Ou em torno de comparações mais livres 24 | // O switch do go pára na primeira avaliação em que o resultado é uma verdade 25 | active := true 26 | switch { 27 | case number > 5: 28 | fmt.Println("Number is greater than 5") 29 | case active: 30 | fmt.Println("true is true") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /functions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Funções são definidas com nome e parâmetros 6 | // Elas também poder ter retornos 7 | // Exemplo: 8 | // 9 | // func helloWorld(name string) string { 10 | // return "Hello, " + name 11 | // } 12 | func helloWorld(name string) { 13 | fmt.Printf("Hello, %s\n", name) 14 | } 15 | 16 | // Funções podem aceitar funções como parâmetro 17 | // HOF - higher order functions 18 | // As funções precisam ter exatamente a mesma assinatura da função 19 | // que você deseja passar ao ser chamada 20 | // É possível colocar uma função até como campo de uma struct 21 | func welcome(name string, helloWorldF func(string)) { 22 | helloWorldF(name) 23 | } 24 | 25 | // Funções podem receber uma sequência de argumentos do mesmo tipo 26 | // Por exemplo, funções 27 | func sumNumbers(numbers ...int) int { 28 | total := 0 29 | for _, number := range numbers { 30 | total += number 31 | } 32 | return total 33 | } 34 | 35 | func main() { 36 | helloWorld("Mauricio") 37 | // Também é possível definir funções anônimas 38 | // \x -> x + 1 (Haskell) 39 | // lambda x : x + 1 (Python) 40 | helloWorldFunc := func(name string) { 41 | fmt.Printf("Hello, %s\n", name) 42 | } 43 | helloWorldFunc("Mauricio") 44 | welcome("Mauricio", helloWorldFunc) 45 | fmt.Println("Sum numbers:", sumNumbers(1, 2)) 46 | fmt.Println("Sum numbers:", sumNumbers(10, 20, 30)) 47 | } 48 | -------------------------------------------------------------------------------- /generics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Podemos criar uma interface pra ter um tipo bem definido como uma constraint 6 | type Number interface { 7 | int64 | float64 | float32 8 | } 9 | 10 | // Generics é uma forma de você ter uma mesma API para tipos de dados diferentes, especialmente 11 | // tipos concretos 12 | // Por exemplo, se quisermos somar uma lista de valores, anteriormente em go você tinha que ter 13 | // uma função para cada tipo. Somar uma lista de floats e somar uma lista de inteiros dependia 14 | // de ter funções iguais com assinaturas diferentes 15 | func nonGenericSumInt(numbers []int) int { 16 | total := 0 17 | for _, number := range numbers { 18 | total += number 19 | } 20 | 21 | return total 22 | } 23 | 24 | func nonGenericSumFloat(numbers []float64) float64 { 25 | total := 0.0 // Aqui precisa ser 0.0 porque o go assume 1 como inteiro nesse contexto 26 | for _, number := range numbers { 27 | total += number 28 | } 29 | 30 | return total 31 | } 32 | 33 | // Essa é uma função que lida com tipo parametrizável. As maiores diferenças entre código com 34 | // generics e interfaces são essas três: 35 | // 1) Generics são mais performáticas e seguras pois não precisam de type assertion 36 | // 2) Todo código é otimizado em tempo de compilação 37 | // 3) Interface é mais sobre contrato e implementação do que código genérico 38 | func genericSum[T int | float64 | float32](numbers []T) T { 39 | var total T 40 | for _, value := range numbers { 41 | total += value 42 | } 43 | return total 44 | } 45 | 46 | func main() { 47 | fmt.Println("Non generic sum:", nonGenericSumInt([]int{1, 2, 3})) 48 | fmt.Println("Non generic sum:", nonGenericSumFloat([]float64{1, 2, 3})) 49 | fmt.Println("Generic sum of integers:", genericSum([]int{1, 2, 3})) 50 | fmt.Println("Generic sum of floats:", genericSum([]float64{1, 2, 3})) 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mauricioabreu/tutorial-de-go 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /interfaces/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Interface é uma maneira de declararmos um conjunto de comportamentos 6 | // que uma struct deve obedecer. Em go, elas são extremamente úteis por dois motivos: 7 | // Histórico, porque go até ontem não tinha generics 8 | // Porque interfaces são maneiras boas de definirmos comportamentos que um determinado tipo 9 | // deve implementar, o que facilita passar diferentes implementações para uma mesma função 10 | 11 | // A definição da interface precisa ter o nome das funções 12 | type animal interface { 13 | makeNoise() string 14 | // Descomentar a função abaixo gera um erro pois as structs não implementam 15 | // a função totalPaws() int 16 | // totalPaws() int 17 | } 18 | 19 | // Agora criamos structs que implementam essa interface 20 | type dog struct{} 21 | 22 | func (d *dog) makeNoise() string { 23 | return "Woof!" 24 | } 25 | 26 | type cat struct{} 27 | 28 | func (c *cat) makeNoise() string { 29 | return "Meow!" 30 | } 31 | 32 | func goAnimalMakeANoise(pet animal) { 33 | fmt.Printf("What is your noise? %s\n", pet.makeNoise()) 34 | } 35 | 36 | func main() { 37 | // Inicializamos as structs e chamamos a função goAnimalMakeANoise 38 | // com structs diferentes. 39 | d := &dog{} 40 | goAnimalMakeANoise(d) 41 | 42 | c := &cat{} 43 | goAnimalMakeANoise(c) 44 | } 45 | -------------------------------------------------------------------------------- /packages/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mauricioabreu/tutorial-de-go/packages/ping" 5 | "github.com/mauricioabreu/tutorial-de-go/packages/pong" 6 | ) 7 | 8 | // Em go, para usar funções de um pacote, elas precisam estar exportadas. 9 | // Para exportar um recurso, seja variável, struct, função, etc, precisam estar com a primeira letra 10 | // em maiúsculo. 11 | // Essa é uma maneira de manter baixo acomplamento entre pacotes e trazer uma 12 | // maior fluidez na comunicação. 13 | func main() { 14 | ping.Hey() 15 | // A função abaixo existe, mas está com a primeira letra minúscula 16 | // pong.hello() 17 | pong.Hello() 18 | } 19 | -------------------------------------------------------------------------------- /packages/ping/messenger.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import "fmt" 4 | 5 | func Hey() { 6 | fmt.Println("Ping") 7 | } 8 | -------------------------------------------------------------------------------- /packages/pong/messenger.go: -------------------------------------------------------------------------------- 1 | package pong 2 | 3 | import "fmt" 4 | 5 | // Função que não pode ser chamada fora do pacote 6 | func hello() { 7 | fmt.Println("Pong") 8 | } 9 | 10 | func Hello() { 11 | fmt.Println("Pong") 12 | } 13 | -------------------------------------------------------------------------------- /structs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Struct é um tipo usado para estruturar dados complexos 9 | // Além de permitir atributos, ela também pode ter funções acopladas 10 | // A regra dos valores default vale para structs também, por isso existe 11 | // uma reclamação grande que em go você precisa defender de "zero-valued" 12 | // Exemplo: caso você tenha uma API que recebe um JSON e um campo booleano não é enviado 13 | // ele chegará na sua struct como false. 14 | type band struct { 15 | name string 16 | firstAlbum string 17 | members []string 18 | } 19 | 20 | // Funções podem ser acopladas a um valor de um tipo (como instância de objetos em Ruby) 21 | func (b *band) Repr() { 22 | fmt.Printf("Band: %s\nFirst Album: %s\nMembers: %s\n", b.name, b.firstAlbum, b.Members()) 23 | } 24 | 25 | func (b *band) Members() string { 26 | return strings.Join(b.members, ", ") 27 | } 28 | 29 | func main() { 30 | blink182 := band{ 31 | name: "Blink-182", 32 | firstAlbum: "Cheshire Cat", 33 | members: []string{"Mark Hoppus", "Travis Barker", "Tom DeLonge"}, 34 | } 35 | blink182.Repr() 36 | } 37 | -------------------------------------------------------------------------------- /variables/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // Variáveis em go podem ser definidas de dois jeitos 7 | // Usando var 8 | var me string 9 | me = "Mauricio" 10 | age := 32 11 | 12 | // Imprimir valores com interpolação com uso de funções que terminam em 'f' 13 | // Exemplos: Sprintf, Printf, Msgf, etc 14 | fmt.Printf("Name: %s and I'm %d years old\n", me, age) 15 | 16 | // Variáveis em go tem tipos 17 | pi := 3.14159265359 18 | fmt.Printf("Pi number: %f\n", pi) 19 | active := true 20 | fmt.Printf("Active? %t\n", active) 21 | 22 | // Coleções tem tipos 23 | var people []string 24 | people = append(people, me) 25 | girlfriend := "Lais" 26 | people = append(people, girlfriend) 27 | fmt.Printf("People: %v\n", people) 28 | } 29 | --------------------------------------------------------------------------------