🐹 Go — 2009

⬅️ Atlas · 🗂️ Todas las fichas · 📚 Índice de clases

Go se diseñó a partir de una queja concreta: los programas grandes tardaban demasiado en compilar y demasiado en entenderse. Su respuesta fue radical y poco habitual — quitar características — y el resultado es un lenguaje que se aprende en un fin de semana y que mueve buena parte de la infraestructura de Internet.

🎯 Por qué está en este programa

Go es uno de los diez lenguajes del núcleo y, junto a Rust, el representante de la familia de sistemas (Atlas).

Aporta al programa el modelo de concurrencia que ningún otro del núcleo enseña: CSP con gorrutinas y canales (clases 133 y 134), la idea de Hoare de 1978 llevada a un lenguaje mayoritario. Y aporta una lección de diseño que el curso repite desde varios ángulos (clases 155 y 164): un lenguaje pequeño es una decisión, y lo que prohíbe es lo que permite prometer.

Año 2009; 1.0 en 2012, con promesa de compatibilidad; 1.18 con genéricos (2022)
Autoría Robert Griesemer, Rob Pike, Ken Thompson, Google
Familia Sistemas; herencia de C, Pascal/Oberon (Wirth) y de CSP (Hoare)
Paradigma Imperativo y concurrente; composición en vez de herencia
Tipado Estático y fuerte, con interfaces estructurales e inferencia local
Memoria Recolección de basura de latencia muy baja, concurrente
Ejecución Compilado a nativo, estático por defecto, con compilación rapidísima
Estado 🟢 Estándar de facto en infraestructura, nube y herramientas de línea de comandos

📜 Historia

La historia de Go empieza, según cuenta Rob Pike, esperando a que compilara un binario de C++ en Google. Tardaba tanto que en esa espera empezó la conversación que llevó al lenguaje.

Ken Thompson —coautor de Unix y de C—, Rob Pike —de Plan 9 y de UTF-8— y Robert Griesemer —de la máquina virtual de JavaScript V8 y discípulo de Wirth— diseñaron en 2007 un lenguaje con tres objetivos explícitos:

  1. Compilar rapidísimo, incluso proyectos enormes.
  2. Ser fácil de leer por alguien que no lo escribió, en una empresa con miles de programadores.
  3. Hacer la concurrencia natural, porque los servidores ya eran multinúcleo.

Y para conseguirlo quitaron cosas: sin herencia de clases, sin excepciones, sin genéricos —durante trece años—, sin sobrecarga de operadores ni de funciones, sin macros, sin constructores, sin aritmética de punteros.

Go 1.0 (2012) vino con una promesa que ha cumplido: compatibilidad hacia atrás. Código de 2012 compila hoy. Es la misma disciplina de Tcl y del mainframe (clase 154), y es una de las razones de su adopción industrial.

Go 1.18 (2022) añadió genéricos tras una discusión pública de una década — y el resultado es deliberadamente limitado, coherente con la filosofía.

🏭 Dónde vive hoy

🧠 Lo que enseña: CSP, y por qué es distinto de los hilos

Go implementa CSP —Communicating Sequential Processes, de Tony Hoare, 1978— con dos primitivas:

go trabajar(datos)              // una GORRUTINA: unos pocos KB, no un hilo del sistema
canal <- valor                   // enviar
valor := <-canal                  // recibir, bloqueando hasta que haya algo

Y el lema del lenguaje resume la idea:

"No te comuniques compartiendo memoria; comparte memoria comunicándote."

Es lo contrario del modelo de hilos con cerrojos de Java y C++ (clase 135): en lugar de proteger un dato compartido, el dato se pasa por un canal y solo una gorrutina lo tiene a la vez.

Y las gorrutinas son ligeras de verdad: arrancan con unos pocos kilobytes de pila que crece sola, y el planificador de Go multiplexa cientos de miles sobre unos pocos hilos del sistema.

Y la honestidad exige decir el límite (clase 136): Go no impide las carreras de datos. Los canales son la forma recomendada, no la obligatoria, y compartir un mapa entre gorrutinas sin sincronizar es un error que compila. Por eso existe go test -race, el detector de carreras, y por eso conviene usarlo siempre. Rust resolvió el mismo problema por el otro camino: hacerlo imposible en el sistema de tipos.

Y el segundo concepto que Go enseña es la interfaz estructural:

type Escritor interface { Write(p []byte) (int, error) }

Cualquier tipo con ese método la cumple, sin declararlo — como TypeScript y a diferencia de Java (clase 112). Eso permite definir la interfaz donde se consume, no donde se implementa, que es la inversión de dependencias hecha idioma.

🔄 Lo que se ha modernizado

⚙️ Cómo se ejecuta hoy

go run main.go < entrada.txt        # el comando de la clase 041
go build -o venta .                  # binario estático, sin dependencias

go vet ./... && gofmt -l .           # calidad: gofmt NO tiene opciones (clase 146)
go test -race ./...                   # pruebas con detector de carreras (clase 136)

gofmt merece la mención: no tiene opciones de configuración. La comunidad renunció a la discusión de estilo eliminando la posibilidad de tenerla, y es probablemente la decisión de la clase 146 mejor ejecutada de cualquier ecosistema.

🧪 El programa de la clase 041 en Go

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    linea, _ := reader.ReadString('\n')
    campos := strings.Fields(linea)

    // Go: tipado estático explícito; conversión float64(cantidad) obligatoria.
    precioUnitario, _ := strconv.ParseFloat(campos[0], 64)
    cantidad, _ := strconv.Atoi(campos[1])
    descuento, _ := strconv.ParseFloat(campos[2], 64)

    subtotal := precioUnitario * float64(cantidad)
    total := subtotal * (1 - descuento)

    fmt.Printf("Total: %.2f\n", total)
}

Lo que hay que ver.

📚 Fuentes y bibliografía


⏮️ Volver al Atlas · 🗂️ Todas las fichas · 🔗 Relacionadas: Rust · C · Zig · Erlang · Pascal