Programación · Lenguajes de programación
Tutorial de Go para principiantes
Respuesta corta
Go (a menudo llamado Golang) es un lenguaje sencillo, compilado y de tipado estático creado por Google, pensado para compilaciones rápidas, concurrencia fácil y servidores fiables. Instala Go, escribe un paquete main y ejecútalo con go run hello.go. Las ideas centrales son un conjunto pequeño de tipos, slices y maps, structs con métodos, interfaces implícitas, goroutines y canales para la concurrencia, y errores devueltos como valores ordinarios.
Qué es Go y para qué se usa
Go fue diseñado en Google por Robert Griesemer, Rob Pike y Ken Thompson, y se publicó como código abierto en 2009. Compila a un único binario estático, arranca rápido, tiene recolector de basura y una rica biblioteca estándar para redes. Se usa en infraestructura en la nube y herramientas de línea de comandos: Docker, Kubernetes, Terraform y Prometheus están escritos en Go, y muchas empresas lo usan para APIs web y microservicios. El lenguaje es deliberadamente pequeño, por lo que el código de distintos equipos se parece.
Instalar Go y ejecutar Hello, world
Descarga Go desde go.dev/dl y compruébalo con go version. Crea una carpeta, inicia un módulo y añade main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}go mod init example.com/hello
go run .
# Hello, world
go build # produces a standalone binary named helloLa ejecución empieza en func main dentro de package main. Ejecuta gofmt (o go fmt) para dar formato al código; todo el código Go usa el mismo estilo, con tabuladores para la indentación en los archivos reales.
Variables y tipos básicos
Declara variables con var name type = value, o dentro de funciones con la forma corta name := value, que infiere el tipo. Las variables a las que no se asigna nada reciben un valor cero: 0, "", false o nil.
| Tipo | Ejemplo | Valor cero |
|---|---|---|
int, int64 | n := 42 | 0 |
float64 | x := 3.14 | 0 |
bool | ok := true | false |
string | s := "hi" | "" |
rune | r := 'é' | 0 (un punto de código Unicode) |
byte | b := byte(65) | 0 (alias de uint8) |
package main
import (
"fmt"
"strconv"
)
func main() {
name := "Ada"
count := 7
avg := float64(count) / 2 // explicit conversion is required
fmt.Printf("%s has %d items, avg %.1f\n", name, count, avg) // Ada has 7 items, avg 3.5
n, err := strconv.Atoi("12")
fmt.Println(n+1, err) // 13 <nil>
}Go nunca convierte implícitamente entre tipos numéricos, y el compilador rechaza las variables y los imports sin usar.
Control de flujo y funciones
No hay paréntesis alrededor de las condiciones y las llaves son obligatorias. for es la única palabra clave de bucle: cubre los bucles clásicos, los de estilo while y los de rango. Las funciones pueden devolver varios valores.
package main
import "fmt"
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func main() {
score := 72
if score >= 90 {
fmt.Println("A")
} else if score >= 70 {
fmt.Println("B") // B
} else {
fmt.Println("C")
}
for i := 0; i < 3; i++ {
fmt.Print(i, " ") // 0 1 2
}
fmt.Println()
n := 10
for n > 1 { // while-style loop
n /= 2
}
q, r := divmod(17, 5)
fmt.Println(n, q, r) // 1 3 2
switch day := "SAT"; day {
case "SAT", "SUN":
fmt.Println("weekend") // no break needed
default:
fmt.Println("weekday")
}
}Slices, maps y structs
Un slice es una vista ampliable de un array y es el tipo de lista de uso diario. Un map es una tabla hash. Un struct agrupa campos con nombre, y los métodos son funciones con un receptor.
package main
import (
"fmt"
"sort"
)
type Point struct {
X, Y int
}
func (p Point) Add(o Point) Point { return Point{p.X + o.X, p.Y + o.Y} }
func (p *Point) Scale(k int) { p.X *= k; p.Y *= k } // pointer receiver modifies p
func main() {
nums := []int{3, 1, 2}
nums = append(nums, 5)
sort.Ints(nums)
fmt.Println(nums, len(nums), nums[1:3]) // [1 2 3 5] 4 [2 3]
ages := map[string]int{"ann": 31, "bob": 25}
ages["cy"] = 40
age, ok := ages["zed"]
fmt.Println(age, ok, len(ages)) // 0 false 3
for i, n := range nums {
fmt.Print(i, ":", n, " ") // 0:1 1:2 2:3 3:5
}
fmt.Println()
p := Point{1, 2}.Add(Point{3, 4})
p.Scale(2)
fmt.Printf("%+v\n", p) // {X:8 Y:12}
}El orden de iteración de un map es aleatorio a propósito; ordena las claves si necesitas un orden estable. Los nombres que empiezan con mayúscula (Point, Add) se exportan desde el paquete.
Interfaces
Una interfaz enumera firmas de métodos. Un tipo satisface una interfaz simplemente por tener esos métodos; no existe la palabra clave implements.
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ R float64 }
type Rect struct{ W, H float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func main() {
shapes := []Shape{Circle{1}, Rect{2, 3}}
total := 0.0
for _, s := range shapes {
total += s.Area()
}
fmt.Printf("%.2f\n", total) // 9.14
}Go también tiene genéricos (desde Go 1.18), por ejemplo func Max[T int | float64](a, b T) T, que usarás sobre todo a través de funciones de biblioteca como las de los paquetes slices y maps.
Característica distintiva: goroutines y canales
Una goroutine es un hilo ligero que se inicia con la palabra clave go; un programa puede ejecutar miles de ellas. Las goroutines se comunican mediante canales, que pasan valores de forma segura entre ellas. sync.WaitGroup espera a que termine un grupo de goroutines.
package main
import (
"fmt"
"sync"
)
func square(n int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
results <- n * n
}
func main() {
results := make(chan int, 5)
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go square(i, results, &wg)
}
wg.Wait()
close(results)
sum := 0
for r := range results {
sum += r
}
fmt.Println(sum) // 55
}defer programa una llamada para que se ejecute cuando retorne la función que la contiene, que es como el código Go cierra archivos y libera mutex. Usa go run -race . para detectar condiciones de carrera durante el desarrollo.
Manejo de errores
Go no tiene excepciones para los fallos ordinarios. Una función que puede fallar devuelve un error como último resultado, y quien la llama lo comprueba enseguida. Envuelve los errores con %w para añadir contexto y mantener el error original inspeccionable con errors.Is.
package main
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
return data, nil
}
func main() {
_, err := readConfig("missing.json")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("no config file:", err)
}
}panic y recover existen, pero se reservan para errores de programación y estados realmente inesperados, no para el manejo normal de errores.
Un programa completo pequeño: un servidor web mínimo
Con solo la biblioteca estándar, este programa sirve JSON por HTTP. Ejecútalo con go run . y abre http://localhost:8080/hello?name=Ann.
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Greeting struct {
Message string `json:"message"`
Time time.Time `json:"time"`
}
func hello(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "world"
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(Greeting{"Hello, " + name, time.Now()})
if err != nil {
log.Println("encode:", err)
}
}
func main() {
http.HandleFunc("/hello", hello)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}El texto que sigue al tipo de cada campo es una etiqueta de struct (struct tag) que fija el nombre de la clave JSON. Cada petición se atiende automáticamente en su propia goroutine.
Errores comunes de principiantes
- Ignorar errores con
_. Comprueba cadaerrordevuelto, salvo que estés seguro de que no puede importar. - Esperar que
appendmodifique el slice original en el sitio; usa siempre el resultado, como ens = append(s, x). - Iniciar goroutines y dejar que
mainretorne antes de que terminen. Espera consync.WaitGroupo con canales. - Usar un receptor por valor cuando el método debe modificar el struct; usa un receptor puntero como
func (p *Point).
Adónde ir después
Haz el Tour of Go interactivo y lee después Effective Go y la documentación de la biblioteca estándar en pkg.go.dev. Aprende go test para pruebas unitarias y benchmarks, el paquete context para tiempos de espera y cancelación, y los módulos para las dependencias.
Preguntas frecuentes
¿Se llama Go o Golang?
El nombre oficial es Go. «Golang» viene de la antigua dirección web golang.org y se sigue usando como término de búsqueda.
¿Go es orientado a objetos?
En parte. Go tiene métodos e interfaces, pero no clases ni herencia; los tipos se construyen incrustando structs y componiendo interfaces pequeñas.
¿Go es más rápido que Python?
En la mayoría de las tareas limitadas por CPU, sí, porque Go compila a código máquina nativo y tiene tipos estáticos. Python aún puede ser rápido cuando el trabajo pesado ocurre dentro de bibliotecas escritas en C.
¿Cuál es la diferencia entre una goroutine y un hilo?
Una goroutine la gestiona el entorno de ejecución de Go y empieza con una pila pequeña que crece según haga falta. El entorno de ejecución planifica muchas goroutines sobre unos pocos hilos del sistema operativo, por lo que crear goroutines es mucho más barato.