DevTools

Cheatsheet Go

Linguagem de programação do Google

Volver a los lenguajes
Go
84 tarjetas encontradas
Categorías:
Versiones:

Sintaxe Básica


11 cards
Variables
package main

import "fmt"

func main() {
    var nombre string = "Ana"
    var edad int = 30
    var activo bool = true
    var x, y int = 1, 2

    fmt.Println(nombre, edad, activo, x, y)
}

Declara variables con var, indicando el tipo o dejando que Go lo infiera. Puedes declarar varias en la misma línea. El valor inicial, si se omite, es el zero value del tipo.

Operadores
// Aritméticos
+  -  *  /  %

// Comparación
==  !=  >  <  >=  <=

// Lógicos
&&  ||  !

// Bitwise
&  |  ^  <<  >>

// Dirección y desreferencia
&x   *p

Go tiene operadores aritméticos, de comparación, lógicos (&&, ||, !) y bitwise. % da el resto de la división. & obtiene la dirección de una variable y * desreferencia un puntero.

Conversiones de tipo
var f float64 = 3.99
var i int = int(f)        // 3 (trunca)

var n int = 65
var c string = string(n)  // "A" (rune)

// No hay conversión implícita
var x int = 10
// var y float64 = x        // ERROR
var y float64 = float64(x)  // OK

Go no hace conversiones automáticas entre tipos: hay que convertir explícitamente con int(), float64(), etc. Convertir float64 a int trunca la parte decimal.

Inferencia con :=
func main() {
    precio := 9.99     // float64
    nombre := "Ana"     // string
    contador := 0     // int
    ok := true        // bool

    // varias de una vez
    a, b := 1, 2
}

El operador := declara e infiere el tipo a partir del valor, pero solo funciona dentro de funciones. Es la forma más común de crear variables locales. A nivel de paquete usa var.

Zero values
var i int            // 0
var f float64        // 0.0
var s string         // ""
var b bool           // false
var p *int           // nil
var sl []int         // nil
var m map[string]int // nil

Toda variable en Go tiene un valor inicial automático, el zero value: 0 para numeros, cadena vacía para string, false para bool y nil para punteros, slices y maps. No hay variables sin inicializar.

strconv
import "strconv"

// int -> string
s := strconv.Itoa(42)          // "42"

// string -> int
n, err := strconv.Atoi("123")  // 123

// otros
strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
strconv.ParseFloat("3.14", 64)
strconv.FormatBool(true)       // "true"
strconv.ParseBool("true")

El paquete strconv convierte entre strings y tipos básicos. Itoa() transforma un int en string y Atoi() hace lo inverso, devolviendo también un error si el texto no es válido.

Tipos de datos
// Enteros
int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64

// Numeros con coma
float32, float64

// Otros
string  // texto (inmutable)
bool    // true / false
byte    // alias de uint8
rune    // alias de int32 (Unicode)
complex64, complex128

Go es fuertemente tipado. Los enteros tienen tamaños fijos (int8 a int64); int depende de la arquitectura. float64 es el estándar para decimales y rune representa un carácter Unicode.

Strings básicas
s := "Hola Mundo"

len(s)     // longitud en bytes
s[0]       // primer byte
s[0:3]     // substring "Hola"

// Concatenar
nombre := "Hola, " + "Ana"

// String bruta (multilínea)
texto := `linea 1
linea 2`

Las strings son secuencias inmutables de bytes. len() devuelve el tamaño en bytes y s[0:3] extrae una parte. Las comillas invertidas crean strings brutas que aceptan varias líneas sin escapes.

Salida con fmt
import "fmt"

fmt.Println("Hola")             // con newline
fmt.Print("sem ", "newline")
fmt.Printf("Nombre: %s\n", "Ana")

// Verbos comunes
fmt.Printf("%d\n", 42)         // entero
fmt.Printf("%.2f\n", 3.14159)  // 3.14
fmt.Printf("%t\n", true)       // bool
fmt.Printf("%v\n", objeto)     // valor por defecto
fmt.Printf("%+v\n", struct1)   // con campos
fmt.Printf("%T\n", 3.14)       // tipo
fmt.Sprintf("x=%d", 5)         // devuelve un string

El paquete fmt imprime y formatea. Println() añade salto de línea y Printf() usa verbos: %d entero, %s string, %f float, %v valor por defecto. Sprintf() devuelve la string formateada.

Constantes e iota
const Pi = 3.14159

const (
    StatusOK       = 200
    StatusNotFound = 404
)

// iota: contador automático
const (
    Domingo = iota // 0
    Segunda        // 1
    Martes          // 2
    Quarta         // 3
)

Las constantes se declaran con const y no pueden cambiar. Dentro de un bloque, iota genera numeros secuenciales automáticos (0, 1, 2...), ideal para enumeraciones.

Paquete strings
import "strings"

s := "Hola Mundo Go"

strings.Contains(s, "Mundo")          // true
strings.HasPrefix(s, "Hola")           // true
strings.HasSuffix(s, "Go")            // true
strings.Split(s, " ")                 // [Hola Mundo Go]
strings.Join([]string{"a", "b"}, "-") // "a-b"
strings.Replace(s, "o", "0", -1)
strings.ToUpper(s)
strings.ToLower(s)
strings.Trim("  x  ", " ")            // "x"

El paquete strings trae funciones para manipular texto: Contains() búsqueda, Split() divide y Join() une. Replace() con -1 sustituye todas las ocurrencias.

Controlo de Fluxo


9 cards
if / else
edad := 20

if edad >= 18 {
    fmt.Println("adulto")
} else if edad > 12 {
    fmt.Println("joven")
} else {
    fmt.Println("niño")
}

El if en Go no usa paréntesis en la condición, pero las llaves son obligatorias. Puedes encadenar else if y else.

for como while e infinito
// while
x := 10
for x > 0 {
    x--
}

// bucle infinito
for {
    if condición {
        break
    }
}

Go no tiene while: se usa for solo con la condición. Un for sin nada crea un bucle infinito, que termina con break o return.

defer
f, err := os.Open("fichero.txt")
if err != nil {
    return err
}
defer f.Close() // se ejecuta al final

// varios defer: orden LIFO
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// salida: 3, 2, 1

defer aplaza una llamada hasta el final de la función, ideal para limpieza (cerrar ficheros y conexiones). Varios defer se ejecutan en orden inverso (LIFO).

if con inicialización
if err := fazer(); err != nil {
    log.Fatal(err)
}
// err no existe aquí

if n, ok := m["clave"]; ok {
    fmt.Println(n)
}

Puedes declarar una variable antes de la condición, separada por ;. Queda limitada al bloque del if. Es el patrón idiomático para verificar errores y resultados.

break y continue
for i := 0; i < 10; i++ {
    if i == 3 {
        continue // salta el 3
    }
    if i == 7 {
        break // para en el 7
    }
    fmt.Println(i)
}

// break con label (bucles anidados)
Loop:
for _, a := range x {
    for _, b := range a {
        if b == alvo {
            break Loop
        }
    }
}

continue salta a la siguiente iteración y break termina el bucle. En bucles anidados, un label (como Loop:) permite a break salir del bucle exterior.

for clásico
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// contador decreciente
for i := 10; i > 0; i-- {
    fmt.Println(i)
}

El for es el único bucle en Go. En su forma clásica tiene tres partes: inicialización, condición y post-instrucción, separadas por ;. No usa paréntesis.

switch
dia := 2

switch dia {
case 1:
    fmt.Println("Segunda")
case 2, 3:
    fmt.Println("Martes ou Quarta")
case 4, 5:
    fmt.Println("Quinta ou Sexta")
default:
    fmt.Println("Fin de semana")
}

El switch compara un valor con varios case. En Go, cada caso termina automáticamente (sin break). Puedes agrupar varios valores en el mismo case, separados por comas.

for range
// slice
for i, v := range slice {
    fmt.Println(i, v)
}

// map
for k, v := range mapa {
    fmt.Println(k, v)
}

// string (runes)
for i, r := range "Hola" {
    fmt.Println(i, string(r))
}

// ignorar el índice
for _, v := range slice { }

range recorre slices, maps, strings y channels, devolviendo índice y valor. Usa _ para ignorar el índice. En strings, itera por rune (carácter Unicode).

switch sin condición
hora := 14

switch {
case hora < 12:
    fmt.Println("Bom dia")
case hora < 20:
    fmt.Println("Boa tarde")
default:
    fmt.Println("Boa noite")
}

// fallthrough (raro)
switch x {
case 1:
    fmt.Println("um")
    fallthrough
case 2:
    fmt.Println("dois")
}

Un switch sin expresión funciona como una cadena de if/else if, evaluando cada condición en orden. fallthrough fuerza la ejecución del caso siguiente (poco usado).

Funções


9 cards
Función simple
func sumar(a, b int) int {
    return a + b
}

// parámetros del mismo tipo
func media(a, b, c float64) float64 {
    return (a + b + c) / 3
}

resultado := sumar(3, 4) // 7

Una función se declara con func, seguida del nombre, parámetros y tipo de retorno. Los parámetros del mismo tipo pueden compartir el tipo (a, b int). El retorno viene al final.

Funciones como valor
var fn func(int) int

fn = func(x int) int {
    return x * 2
}

fmt.Println(fn(5)) // 10

// en maps
operaciones := map[string]func(int, int) int{
    "suma": func(a, b int) int { return a + b },
}

Las funciones son valores de primera clase en Go: puedes guardarlas en variables, maps o slices. El tipo func(int) int describe una función que recibe y devuelve un int.

Recursión
func factorial(n int) int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n-1)
}

func fibonacci(n int) int {
    if n < 2 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

fmt.Println(factorial(5)) // 120

Una función recursiva se llama a sí misma hasta alcanzar un caso base. factorial() multiplica hasta llegar a 1. La recursión es elegante, pero para casos simples un bucle puede ser más eficiente.

Múltiples retornos
func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("división por zero")
    }
    return a / b, nil
}

resultado, err := dividir(10, 2)
if err != nil {
    log.Fatal(err)
}

Go permite devolver varios valores. El patrón idiomático es retornar (resultado, error), donde error es nil cuando todo va bien. El llamador siempre verifica el error.

Funciones anónimas
// ejecutar inmediatamente
func() {
    fmt.Println("executada ya")
}()

// con un argumento
func(nombre string) {
    fmt.Println("Hola,", nombre)
}("Ana")

// asignar a una variable
doble := func(x int) int { return x * 2 }
fmt.Println(doble(4)) // 8

Una función anónima no tiene nombre y puede definirse y llamarse en el mismo lugar. Se usa mucho en go func() {...}() para goroutines y como argumento de otras funciones.

Retornos nombrados
func calcular(a, b int) (suma, producto int) {
    suma = a + b
    producto = a * b
    return // devuelve suma y producto
}

s, p := calcular(3, 4) // 7, 12

Los retornos pueden tener nombre, sirviendo como variables locales. Un return sin valores devuelve los valores nombrados actuales. Útil para documentar el significado de cada retorno.

Closures
func contador() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

inc := contador()
fmt.Println(inc()) // 1
fmt.Println(inc()) // 2
fmt.Println(inc()) // 3

Una closure es una función que captura variables del entorno donde fue creada. Aquí, la función anónima mantiene el estado de n entre llamadas sucesivas.

Funciones variádicas
func suma(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

suma(1, 2, 3)       // 6
suma(1, 2, 3, 4, 5) // 15

// expandir un slice
vals := []int{1, 2, 3}
suma(vals...)

Los parámetros con ... aceptan un numero variable de argumentos, recibidos como un slice. Puedes pasar un slice existente expandiéndolo con vals.... fmt.Println() es variádica.

Higher-order functions
// recibir una función como parámetro
func aplicar(nums []int, fn func(int) int) []int {
    res := make([]int, len(nums))
    for i, v := range nums {
        res[i] = fn(v)
    }
    return res
}

doble := aplicar([]int{1, 2, 3}, func(x int) int {
    return x * 2
}) // [2, 4, 6]

Las funciones de orden superior reciben o devuelven otras funciones. Pasar una función como parámetro permite generalizar comportamiento, como transformar cada elemento de un slice.

Structs e Interfaces


10 cards
Struct
type Persona struct {
    Nombre  string
    Edad int
    Activo bool
}

// instanciar
p := Persona{
    Nombre:  "Ana",
    Edad: 30,
    Activo: true,
}

fmt.Println(p.Nombre) // Ana

Una struct agrupa campos de tipos diferentes en un único tipo. Se crea con type y se instancia indicando los campos por nombre. Es la base para modelar datos en Go.

Constructores
type Usuario struct {
    Nombre  string
    Email string
}

// convención: New + nombre del tipo
func NewUsuario(nombre, email string) *Usuario {
    return &Usuario{
        Nombre:  nombre,
        Email: email,
    }
}

u := NewUsuario("Ana", "ana@mail.com")

Go no tiene constructores nativos; la convención es una función NewX() que devuelve un puntero a la struct ya inicializada. Permite validar datos y definir valores por defecto.

Type assertion
var i any = "hola"

// afirmar el tipo
s := i.(string)
fmt.Println(s)

// forma segura (comma-ok)
s, ok := i.(string)
if ok {
    fmt.Println("es string:", s)
}

n, ok := i.(int) // ok == false

Una type assertion (i.(string)) extrae el valor concreto de una interface. La forma segura con ok evita un panic si el tipo no corresponde.

Formas de instanciar
type Ponto struct{ X, Y int }

// con nombres de campo (recomendado)
p1 := Ponto{X: 1, Y: 2}

// por posición (orden de los campos)
p2 := Ponto{1, 2}

// zero value
var p3 Ponto // {0, 0}

// con new (devuelve un puntero)
p4 := new(Ponto)
p4.X = 5

Puedes instanciar una struct con los nombres de los campos (más claro) o por posición. var p Ponto crea el zero value y new() devuelve un puntero a una struct puesta a cero.

Interfaces
type Forma interface {
    Area() float64
    Perímetro() float64
}

type Cuadrado struct{ Lado float64 }

func (q Cuadrado) Area() float64 {
    return q.Lado * q.Lado
}
func (q Cuadrado) Perímetro() float64 {
    return 4 * q.Lado
}
// Cuadrado implementa Forma implícitamente

Una interface define un conjunto de métodos. En Go, la implementación es implícita: cualquier type con esos métodos satisface la interface, sin declaración explícita. Esto favorece el desacoplamiento.

Type switch
func describir(i any) {
    switch v := i.(type) {
    case int:
        fmt.Println("entero:", v)
    case string:
        fmt.Println("texto:", v)
    case bool:
        fmt.Println("bool:", v)
    case []int:
        fmt.Println("slice de int")
    default:
        fmt.Printf("outro: %T\n", v)
    }
}

Un type switch (i.(type)) prueba el tipo concreto de una interface en varios case. La variable v asume el tipo correcto en cada rama. %T muestra el tipo.

Métodos (receiver por valor)
type Círculo struct {
    Radio float64
}

func (c Círculo) Area() float64 {
    return math.Pi * c.Radio * c.Radio
}

c := Círculo{Radio: 5}
fmt.Println(c.Area())

Un método es una función con un receiver, declarado entre func y el nombre. Con receiver por valor (c Círculo), el método trabaja sobre una copia y no altera el original.

Interface vacía (any)
// any es alias de interface{}
func imprimir(v any) {
    fmt.Println(v)
}

imprimir(42)
imprimir("texto")
imprimir([]int{1, 2, 3})

var x any = "hola"

La interface vacía any (alias de interface{}) acepta cualquier tipo, pues no exige métodos. Es útil para funciones genéricas, pero pierde seguridad de tipos — úsala con moderación.

Métodos (receiver por puntero)
type Contador struct {
    Valor int
}

func (c *Contador) Incrementar() {
    c.Valor++ // altera el original
}

ct := Contador{}
ct.Incrementar()
ct.Incrementar()
fmt.Println(ct.Valor) // 2

Con receiver por puntero (c *Contador), el método puede modificar el original y evita copiar structs grandes. Usa puntero siempre que necesites alterar el estado.

Embedding (composición)
type Animal struct {
    Nombre string
}

func (a Animal) Describir() string {
    return "Soy " + a.Nombre
}

type Perro struct {
    Animal // campo embebido
    Raca   string
}

c := Perro{Animal: Animal{Nombre: "Rex"}, Raca: "Labrador"}
fmt.Println(c.Nombre)        // promovido
fmt.Println(c.Describir()) // método promovido

Go usa composición en vez de herencia. Embeber un tipo (sin nombre de campo) promueve sus campos y métodos al tipo exterior. Perro gana Nombre y Describir() de Animal.

Coleções


10 cards
Arrays
var arr [5]int          // [0,0,0,0,0]
arr[0] = 10

nums := [3]int{1, 2, 3}

// tamaño inferido
vals := [...]int{1, 2, 3, 4}

len(nums) // 3

// el tamaño forma parte del tipo
var a [3]int
var b [4]int
// a = b // ERROR: tipos diferentes

Un array tiene un tamaño fijo que forma parte del tipo ([3]int es distinto de [4]int). Se pasa por copia. En la práctica, casi siempre se usa un slice, que es dinámico.

make y copy
// make: crea un slice con len y cap
s := make([]int, 5)     // len=5, cap=5
s = make([]int, 0, 10)  // len=0, cap=10

// copy: copia elementos
src := []int{1, 2, 3}
dst := make([]int, len(src))
n := copy(dst, src) // n=3

// map predimensionado
m := make(map[string]int, 100)

make() crea slices, maps y channels con un tamaño inicial, evitando realocaciones. copy(dst, src) copia elementos entre slices de forma segura, sin compartir el array.

Ordenación (sort)
import "sort"

nums := []int{3, 1, 4, 1, 5}
sort.Ints(nums) // [1,1,3,4,5]

sort.Strings([]string{"b", "a", "c"})

// ordenación personalizada
personas := []Persona{{"Ana", 30}, {"Ray", 25}}
sort.Slice(personas, func(i, j int) bool {
    return personas[i].Edad < personas[j].Edad
})

sort.SearchInts(nums, 3) // búsqueda binaria

El paquete sort ordena slices: Ints() y Strings() para tipos básicos, sort.Slice() con una función de comparación para tipos personalizados. SearchInts() hace una búsqueda binaria.

Slices
s := []int{1, 2, 3}

// slice vacío
var vacio []int    // nil
outro := []int{}   // vacío, no nil

len(s) // 3 (longitud)
cap(s) // capacidad

s[0] // 1
s[2] // 3

Un slice es una vista dinámica sobre un array, con longitud y capacidad. Es la colección más usada en Go. Se declara con []tipo sin tamaño fijo.

Maps
m := map[string]int{
    "manzana":   3,
    "banana": 5,
}

// map vacío
vacio := make(map[string]int)

m["naranja"] = 2 // añadir
m["manzana"] = 10   // actualizar

fmt.Println(m["banana"]) // 5
len(m)

Un map guarda pares clave-valor. Se crea con map[clave]valor{} o make(). El acceso y la asignación usan la sintaxis m[clave]. Las claves deben ser comparables.

Paquete slices (Go 1.21+)
import "slices"

s := []int{3, 1, 2}

slices.Sort(s)        // ordena
slices.Contains(s, 2) // true
slices.Index(s, 2)    // posición
slices.Reverse(s)     // invierte

slices.Min(s) // mínimo
slices.Max(s) // máximo

slices.Equal(a, b) // compara
slices.Clone(s)    // copia independiente

El paquete slices (Go 1.21+) trae funciones genéricas modernas: Contains(), Sort(), Min(), Max() y Clone(). Clone() crea una copia que no comparte el array.

append
s := []int{1, 2, 3}

s = append(s, 4)            // [1,2,3,4]
s = append(s, 5, 6)         // varios
s = append(s, []int{7, 8}...) // otro slice

// importante: reasignar!
s = append(s, 9)

append() añade elementos al final de un slice y devuelve el nuevo slice — hay que reasignar (s = append(s, ...)). Si la capacidad llega al límite, Go asigna un array mayor automáticamente.

Maps: delete y comma-ok
m := map[string]int{"a": 1, "b": 2}

// comma-ok: ¿la clave existe?
v, ok := m["a"]
if ok {
    fmt.Println("existe:", v)
}

// zero value si no existe
v = m["z"] // 0 (sin error!)

delete(m, "a") // quitar

Acceder a una clave inexistente devuelve el zero value sin error. Por eso se usa el patrón comma-ok (v, ok := m[clave]) para saber si la clave existe. delete() elimina una entrada.

Fatiar (slicing)
s := []int{0, 1, 2, 3, 4, 5}

s[1:4]   // [1,2,3]
s[:3]    // [0,1,2]
s[3:]    // [3,4,5]
s[:]     // vista completa
s[1:4:4] // low:high:max (limita cap)

// comparte el array subyacente!
sub := s[1:3]
sub[0] = 99 // altera s también

La sintaxis s[low:high] crea un sub-slice. Atención: el sub-slice comparte el array original, por lo que los cambios afectan a ambos. Usa copy() o la forma de tres índices para aislar.

Recorrer colecciones
// slice
for i, v := range []int{10, 20, 30} {
    fmt.Println(i, v)
}

// map (¡orden aleatorio!)
for k, v := range m {
    fmt.Println(k, v)
}

// solo claves
for k := range m {
    fmt.Println(k)
}

range recorre slices (índice, valor) y maps (clave, valor). El orden de iteración de un map es intencionalmente aleatorio. Usa _ para ignorar el índice o la clave.

Concorrência


10 cards
Goroutines
func tarea(n int) {
    fmt.Println("tarea", n)
}

func main() {
    go tarea(1) // goroutine

    go func() {
        fmt.Println("asíncrono")
    }()

    time.Sleep(time.Second) // espera simple
}

Una goroutine es una función ejecutada concurrentemente, lanzada con go. Son extremadamente ligeras (pocos KB). El programa termina cuando main() acaba, por eso hay que sincronizar.

Cerrar y recorrer channels
ch := make(chan int)

go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // cerrar al terminar
}()

// range termina cuando el channel cierra
for v := range ch {
    fmt.Println(v)
}

// comma-ok: ¿channel cerrado?
v, ok := <-ch

close(ch) indica que no habrá más envíos. Un for range sobre el channel termina automáticamente cuando cierra. El patrón v, ok := <-ch devuelve ok == false si está cerrado y vacío.

atomic y RWMutex
import "sync/atomic"

var total int64
atomic.AddInt64(&total, 1) // incremento atómico
v := atomic.LoadInt64(&total)

// RWMutex: varias lecturas, 1 escritura
var rw sync.RWMutex
rw.RLock() // lectura
// ... leer datos ...
rw.RUnlock()
rw.Lock() // escritura
// ... escribir ...
rw.Unlock()

sync/atomic hace operaciones atómicas sin locks, ideal para contadores simples. sync.RWMutex permite varias lecturas simultáneas (RLock()) pero solo una escritura (Lock()).

Channels
ch := make(chan string)

go func() {
    ch <- "hola" // envía (bloquea)
}()

msg := <-ch // recibe (bloquea)
fmt.Println(msg)

Un channel comunica entre goroutines de forma segura. ch <- valor envía y <-ch recibe. Un channel sin buffer bloquea hasta que haya un receptor y un emisor listos.

select
select {
case msg := <-ch1:
    fmt.Println("ch1:", msg)
case msg := <-ch2:
    fmt.Println("ch2:", msg)
case <-time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nenhum pronto")
}

select espera por varias operaciones de channel, ejecutando el primer caso listo. Si varios están listos, elige uno al azar. default evita el bloqueo y time.After() crea timeouts.

Context
ctx, cancel := context.WithTimeout(
    context.Background(),
    5*time.Second,
)
defer cancel()

select {
case res := <-fazer(ctx):
    fmt.Println(res)
case <-ctx.Done():
    fmt.Println("cancelado:", ctx.Err())
}

El paquete context controla la cancelación y los timeouts entre goroutines. WithTimeout() crea un contexto que expira; ctx.Done() es un channel que cierra al cancelar. Pasa ctx como primer parámetro.

Channel con buffer
ch := make(chan int, 3) // buffer de 3

ch <- 1 // no bloquea
ch <- 2
ch <- 3
// ch <- 4 // BLOQUEA (lleno)

fmt.Println(<-ch)         // 1 (FIFO)
fmt.Println(len(ch), cap(ch)) // 2 3

Un channel con buffer (make(chan int, 3)) acepta envíos sin bloquear mientras haya espacio. Solo bloquea cuando está lleno (envío) o vacío (recepción).

WaitGroup
var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println(n)
    }(i)
}

wg.Wait() // bloquea hasta que todas terminen
fmt.Println("hecho")

sync.WaitGroup espera a que un conjunto de goroutines termine. Add(1) registra cada una, Done() (en defer) marca la finalización y Wait() bloquea hasta que todas acaben.

Channels direccionales
// solo envía
func produtor(ch chan<- int) {
    ch <- 42
}

// solo recibe
func consumidor(ch <-chan int) {
    fmt.Println(<-ch)
}

ch := make(chan int)
go produtor(ch)
consumidor(ch)

Puedes restringir la dirección de un channel en el tipo: chan<- int solo envía, <-chan int solo recibe. Esto hace las APIs más claras y seguras, indicando quién produce y quién consume.

Mutex
var (
    mu       sync.Mutex
    contador int
)

func incrementar() {
    mu.Lock()
    defer mu.Unlock()
    contador++
}

// 1000 goroutines seguras
for i := 0; i < 1000; i++ {
    go incrementar()
}

Un sync.Mutex protege datos compartidos: Lock() bloquea el acceso y Unlock() lo libera. Usa defer mu.Unlock() para garantizar la liberación incluso en caso de error.

Tratamento de Erros


8 cards
Patrón error
f, err := os.Open("datos.txt")
if err != nil {
    return fmt.Errorf("abrir fichero: %w", err)
}
defer f.Close()

// usar f

En Go, los errores son valores. Las funciones que pueden fallar devuelven un error como último valor de retorno. El patrón idiomático es comprobar if err != nil inmediatamente y propagar el error.

errors.Is
import (
    "errors"
    "os"
)

_, err := os.Open("inexistente.txt")

if errors.Is(err, os.ErrNotExist) {
    fmt.Println("fichero no existe")
}

// compara a lo largo de la cadena de wrapping

errors.Is(err, objetivo) comprueba si un error (o cualquier error envuelto en la cadena) corresponde a un valor específico, como os.ErrNotExist. Sustituye la comparación directa, que no funciona con wrapping.

Crear errores
import (
    "errors"
    "fmt"
)

// error simple
err := errors.New("algo falhou")

// error formateado
err = fmt.Errorf("valor %d es inválido", n)

return err

errors.New() crea un error con un mensaje fijo. fmt.Errorf() crea errores formateados, como Sprintf. Ambos devuelven un valor que implementa la interface error.

errors.As
var nf *NotFoundError

if errors.As(err, &nf) {
    // nf ahora tiene el tipo concreto
    fmt.Println("faltou:", nf.Nombre)
}

// extrae el primer error del tipo indicado

errors.As(err, &objetivo) búsqueda en la cadena el primer error de un tipo concreto y lo copia en objetivo. Permite acceder a campos específicos de errores personalizados incluso después de wrapping.

Wrapping con %w
func lerConfig() error {
    _, err := os.Open("config.yaml")
    if err != nil {
        return fmt.Errorf("leer config: %w", err)
    }
    return nil
}

// %w envuelve el error original,
// preservando la cadena de causas

El verbo %w en fmt.Errorf() envuelve el error original, creando una cadena. Esto permite inspeccionar la causa más tarde con errors.Is() y errors.As(), sin perder contexto.

errors.Join (Go 1.20+)
import "errors"

err1 := validarNome(nombre)
err2 := validarEmail(email)

// combinar varios errores
err := errors.Join(err1, err2)
if err != nil {
    return err // muestra ambos
}

errors.Is(err, err1) // true

errors.Join() (Go 1.20+) combina varios errores en uno solo, útil para validar múltiples campos. Los errores originales siguen siendo detectables con errors.Is() y errors.As().

Error personalizado
type NotFoundError struct {
    Nombre string
}

func (e *NotFoundError) Error() string {
    return e.Nombre + " no encontrado"
}

// usar
return &NotFoundError{Nombre: "usuario"}

Cualquier tipo con un método Error() string implementa la interface error. Los errores personalizados pueden transportar datos adicionales (como Nombre), útiles para el llamador.

panic y recover
func seguro() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recuperado:", r)
        }
    }()

    panic("error fatal")
}

// panic interrumpe; recover solo en defer

panic() interrumpe el flujo y se propaga por la pila; recover(), llamado dentro de un defer, lo captura y recupera el control. Es último recurso — en Go, prefiere devolver un error.

Pacotes e Módulos


8 cards
go.mod
module github.com/usuario/proyecto

go 1.22

require (
    github.com/gorilla/mux v1.8.1
)

El archivo go.mod define el módulo: la ruta (nombre) y la versión de Go. Se crea con go mod init. Las dependencias van en require y las versiones exactas en go.sum.

go get
# añadir dependencia
go get github.com/gorilla/mux

# versión específica
go get github.com/gorilla/mux@v1.8.1

# actualizar
go get -u ./...

# quitar no usadas
go mod tidy

go get descarga y añade dependencias al go.mod. Puedes fijar una versión con @v1.8.1 y actualizar todo con -u. go mod tidy limpia las que ya no se usan.

import
import (
    "fmt"
    "os"
    "net/http"

    alias "outro/paquete" // renombrar
    _ "driver/mysql"     // solo side-effects
    . "math"             // dot import (evitar)
)

fmt.Println("hola")

import trae paquetes al archivo. Puedes dar un alias para evitar conflictos, usar _ para ejecutar solo la inicialización (drivers) o . para importar sin prefijo (desaconsejado).

go mod
# crear módulo
go mod init github.com/user/proj

# limpiar dependencias
go mod tidy

# descargar todas
go mod download

# verificar integridad
go mod verify

# ver el grafo de dependencias
go mod graph

Los comandos go mod gestionan el módulo: init crea el go.mod, tidy sincroniza las dependencias con el código, download las guarda en la caché y verify confirma las sumas.

Exportación (mayúscula)
package calculo

// Mayúscula = exportado (público)
func Sumar(a, b int) int {
    return a + b
}

type Servidor struct {
    Host  string // campo público
    puerto int    // campo privado
}

// minúscula = privado al paquete
func interno() { }

En Go, la visibilidad es por capitalización: los nombres con mayúscula (Sumar, Servidor) se exportan y son accesibles fuera del paquete. Los nombres con minúscula son privados al paquete.

Organización del proyecto
meuprojeto/
├── go.mod
├── go.sum
├── main.go          // package main
├── cmd/
│   └── server/
│       └── main.go  // otro binario
├── internal/        // privado al módulo
│   └── handler/
└── pkg/             // reutilizable
    └── util/

Un proyecto Go se organiza por paquetes en carpetas. cmd/ guarda los binarios, internal/ código privado al módulo (el compilador impide el import externo) y pkg/ código reutilizable.

init()
package config

var baseURL string

func init() {
    // se ejecuta antes de main()
    baseURL = os.Getenv("API_URL")
    if baseURL == "" {
        baseURL = "http://localhost"
    }
}

La función init() se ejecuta automáticamente antes de main(), una vez por paquete. Sirve para inicializar estado, registrar drivers o validar configuración. Puede haber varias por archivo.

package main
package main

import "fmt"

// punto de entrada obligatorio
func main() {
    fmt.Println("Hola, mundo!")
}

Un programa ejecutable necesita package main y una función main() sin parámetros ni retorno. Es el punto de entrada. Los paquetes de biblioteca usan otros nombres.

Dicas e CLI


9 cards
CLI: comandos esenciales
go run main.go    # compila y ejecuta
go build          # genera binario
go install        # compila e instala
go test ./...     # corre tests
go fmt ./...      # formatea código
go mod tidy       # limpia dependencias
go clean          # limpia cachés

Go tiene una única herramienta, go. run compila y ejecuta, build genera el binario, test corre los tests y fmt formatea el código con el estilo estándar. ./... abarca todos los paquetes.

Interfaces útiles
// fmt.Stringer
type Ponto struct{ X, Y int }
func (p Ponto) String() string {
    return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}

// io.Reader / io.Writer
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}

La biblioteca estándar define interfaces pequeñas y ubicuas. fmt.Stringer (String()) personaliza la impresión. io.Reader e io.Writer abstraen cualquier fuente o destino de datos.

Buenas prácticas
// 1. Tratar siempre los errores
if err != nil {
    return err
}

// 2. Nombres cortos y claros
i, v := range s // no "index, value"

// 3. Composición en vez de herencia

// 4. Receptor corto
func (s *Servidor) Start() { }

// 5. Context como 1.er parámetro
func Get(ctx context.Context, id string) { }

Go valora la simplicidad: trata siempre los errores, usa nombres cortos, prefiere la composición a la herencia y pasa context.Context como primer parámetro. Formatea siempre con go fmt.

Tests (testing)
// suma_test.go
package calculo

import "testing"

func TestSomar(t *testing.T) {
    got := Sumar(2, 3)
    want := 5
    if got != want {
        t.Errorf("Sumar(2,3) = %d; quero %d", got, want)
    }
}

Los tests viven en archivos _test.go con funciones TestX(t *testing.T). Se ejecutan con go test. t.Errorf() registra un fallo sin parar; t.Fatalf() detiene el test.

io.Reader en la práctica
import (
    "io"
    "os"
    "strings"
)

r := strings.NewReader("hola mundo")

// copiar de un Reader a un Writer
io.Copy(os.Stdout, r)

// leer todo
data, _ := io.ReadAll(r)

// un archivo es un Reader
f, _ := os.Open("f.txt")
io.Copy(os.Stdout, f)

io.Reader es la abstracción central de entrada. io.Copy() transfiere datos de un Reader a un Writer de forma eficiente. Los archivos, strings y respuestas HTTP son todos Reader.

Table-driven tests
func TestSomar(t *testing.T) {
    pruebas := []struct {
        a, b, want int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, tt := range pruebas {
        if got := Sumar(tt.a, tt.b); got != tt.want {
            t.Errorf("Sumar(%d,%d) = %d; quero %d",
                tt.a, tt.b, got, tt.want)
        }
    }
}

Los table-driven tests definen varios casos en un slice de structs e iteran sobre ellos con range. Es el patrón idiomático en Go: compacto, fácil de extender y con mensajes de error claros.

JSON
type Persona struct {
    Nombre  string `json:"nombre"`
    Edad int    `json:"edad"`
}

// struct -> JSON
data, _ := json.Marshal(Persona{"Ana", 30})

// JSON -> struct
var p Persona
json.Unmarshal(data, &p)

// con formateo
json.MarshalIndent(p, "", "  ")

El paquete encoding/json serializa structs. Marshal() convierte a JSON y Unmarshal() hace lo inverso. Las tags json:"nombre" definen los nombres de los campos en el JSON.

go vet y formateo
# análisis estático
go vet ./...

# formatear
go fmt ./...

# formatear + organizar imports
goimports -w .

# linter completo (externo)
golangci-lint run

go vet detecta errores sospechosos que compilan pero están mal. go fmt formatea el código con el estilo oficial. goimports también organiza los imports automáticamente.

Generics (Go 1.18+)
// type parameter
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

Max(3, 7)     // 7
Max("a", "b") // "b"

// slice genérico
func Map[T, U any](s []T, f func(T) U) []U {
    res := make([]U, len(s))
    for i, v := range s {
        res[i] = f(v)
    }
    return res
}

Los generics permiten funciones y tipos parametrizados por tipo, con [T ...]. La restricción constraints.Ordered limita T a tipos ordenables. Reduce el código duplicado manteniendo la seguridad de tipos.