Sintaxis esencial de Go: tipos, control de flujo y funciones

Capítulo 2

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Declaraciones, variables, constantes e inferencia de tipos

En Go, la mayoría del código se construye a partir de declaraciones simples: variables, constantes, asignaciones cortas y llamadas a funciones. La idea clave es que el compilador puede inferir tipos con frecuencia, pero tú sigues teniendo control explícito cuando lo necesitas.

Variables: var vs :=

Usa var cuando quieras declarar con tipo explícito, declarar sin inicializar (valor cero) o declarar a nivel de paquete. Usa := dentro de funciones para declarar e inicializar de forma concisa.

package main

import "fmt"

func main() {
	var edad int = 30
	var nombre = "Ana"      // tipo inferido: string
	ciudad := "Lima"        // declaración corta (solo dentro de funciones)
	var activo bool          // valor cero: false
	var saldo float64        // valor cero: 0

	fmt.Println(edad, nombre, ciudad, activo, saldo)
}

Constantes: const y constantes sin tipo

Las constantes pueden ser tipadas o no tipadas. Las no tipadas son útiles porque se adaptan al contexto (por ejemplo, pueden caber en distintos tipos numéricos si no hay pérdida).

const Pi = 3.1415926535      // constante sin tipo
const MaxUsuarios int = 1000 // constante tipada

func main() {
	radio := 2.0
	area := Pi * radio * radio // Pi se adapta a float64 por el contexto
	_ = area
}

Declaración en bloque

Para agrupar variables o constantes relacionadas, usa bloques. Esto mejora legibilidad y reduce repetición.

var (
	appName = "MiServicio"
	port    = 8080
)

const (
	statusOK    = 200
	statusError = 500
)

Tipos básicos y modelado de datos

Go tiene un conjunto pequeño de tipos primitivos que cubren la mayoría de casos. Elegir bien el tipo ayuda a expresar intención y evitar errores.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

CategoríaTipos comunesUso típico
Enterosint, int64, uintcontadores, índices, IDs (según rango)
Decimalesfloat32, float64mediciones, cálculos con decimales
Textostringnombres, mensajes, JSON
Booleanosboolbanderas, condiciones
Bytes/Runasbyte, runeprocesamiento de datos y Unicode

Valores cero (zero values)

Si declaras una variable sin inicializar, Go asigna un valor cero seguro: 0 para números, false para booleanos, "" para strings. Esto reduce la necesidad de inicializaciones defensivas.

var n int       // 0
var ok bool     // false
var s string    // ""

Conversión de tipos (no hay coerción implícita)

Go no convierte automáticamente entre tipos numéricos distintos. Debes convertir explícitamente, lo cual evita errores silenciosos.

func main() {
	var a int = 10
	var b int64 = 20
	// total := a + b // ERROR: tipos distintos
	total := int64(a) + b
	_ = total
}

También es común convertir entre string y números usando el paquete strconv.

import (
	"fmt"
	"strconv"
)

func main() {
	edadStr := "42"
	edad, err := strconv.Atoi(edadStr)
	if err != nil {
		fmt.Println("edad inválida")
		return
	}

	precio := 19.99
	precioStr := strconv.FormatFloat(precio, 'f', 2, 64)
	fmt.Println(edad, precioStr)
}

Operadores esenciales

  • Aritméticos: +, -, *, /, %
  • Comparación: ==, !=, <, <=, >, >=
  • Lógicos: &&, ||, !
  • Asignación: =, +=, -=, *=, etc.

Ejemplo práctico: validar un rango y calcular un descuento.

func descuento(precio float64, esMiembro bool) float64 {
	if precio >= 100 && esMiembro {
		return precio * 0.90
	}
	return precio
}

Control de flujo: condicionales y bucles con casos prácticos

if con inicialización

Go permite ejecutar una declaración corta antes de evaluar la condición. La variable vive solo dentro del if/else, lo que ayuda a limitar alcance.

import (
	"fmt"
	"strconv"
)

func main() {
	input := "123"
	if n, err := strconv.Atoi(input); err == nil {
		fmt.Println("número:", n)
	} else {
		fmt.Println("entrada inválida")
	}
}

switch expresivo (sin break obligatorio)

switch en Go es flexible: puede comparar valores, evaluar condiciones o agrupar casos. Por defecto, no hay caída (fallthrough) entre casos.

func categoriaEdad(edad int) string {
	switch {
	case edad < 0:
		return "inválida"
	case edad < 18:
		return "menor"
	case edad < 65:
		return "adulto"
	default:
		return "mayor"
	}
}

Bucles: solo existe for

Go usa for para todos los estilos: contador, estilo while y bucles infinitos controlados.

func sumaHasta(n int) int {
	suma := 0
	for i := 1; i <= n; i++ {
		suma += i
	}
	return suma
}
func esperarHastaListo(maxIntentos int) bool {
	intentos := 0
	for intentos < maxIntentos { // estilo while
		intentos++
		listo := (intentos == 3)
		if listo {
			return true
		}
	}
	return false
}

break y continue con caso práctico

Ejemplo: sumar solo números positivos hasta encontrar un cero (corte) e ignorar negativos (continue).

func sumarPositivosHastaCero(nums []int) int {
	suma := 0
	for _, n := range nums {
		if n == 0 {
			break
		}
		if n < 0 {
			continue
		}
		suma += n
	}
	return suma
}

Funciones: composición, retornos múltiples y variádicos

Las funciones son la unidad principal de composición. En Go es común escribir funciones pequeñas, con nombres claros, que devuelven valores y errores cuando aplica.

Firma básica y retorno nombrado

Puedes devolver valores con nombres (útil en funciones cortas), pero úsalo con moderación para mantener claridad.

func dividir(a, b float64) (resultado float64, ok bool) {
	if b == 0 {
		return 0, false
	}
	return a / b, true
}

Retornos múltiples (patrón valor + error)

Un patrón muy común es devolver el resultado y un error. Esto hace explícito el manejo de fallos sin excepciones.

import "errors"

func promedio(nums []float64) (float64, error) {
	if len(nums) == 0 {
		return 0, errors.New("lista vacía")
	}
	suma := 0.0
	for _, n := range nums {
		suma += n
	}
	return suma / float64(len(nums)), nil
}

Parámetros variádicos

Un parámetro variádico permite pasar cero o más argumentos. Dentro de la función se comporta como un slice.

func maximo(nums ...int) (int, bool) {
	if len(nums) == 0 {
		return 0, false
	}
	m := nums[0]
	for _, n := range nums[1:] {
		if n > m {
			m = n
		}
	}
	return m, true
}

func main() {
	m, ok := maximo(3, 9, 2, 10)
	_, _ = m, ok
}

Si ya tienes un slice, puedes expandirlo con ....

nums := []int{5, 1, 8}
m, ok := maximo(nums...)
_, _ = m, ok

Funciones anónimas y cierres (closures)

Una función anónima puede asignarse a una variable o ejecutarse al instante. Si captura variables del entorno, se convierte en un cierre: mantiene acceso a esas variables.

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

func main() {
	next := contador()
	_ = next() // 1
	_ = next() // 2
}

Funciones como valores: mini utilidades componibles

Pasar funciones como parámetros permite construir utilidades reutilizables. Ejemplo: filtrar enteros con una condición.

func filtrar(nums []int, pred func(int) bool) []int {
	salida := make([]int, 0, len(nums))
	for _, n := range nums {
		if pred(n) {
			salida = append(salida, n)
		}
	}
	return salida
}

func main() {
	nums := []int{1, 2, 3, 4, 5, 6}
	pares := filtrar(nums, func(n int) bool { return n%2 == 0 })
	_ = pares
}

Alcance de variables y sombras (shadowing)

El alcance (scope) define dónde una variable existe. En Go, es fácil crear variables nuevas con := dentro de bloques y, sin querer, “sombrear” (shadow) una variable externa con el mismo nombre.

Ejemplo de sombra con :=

import "fmt"

func main() {
	err := fmt.Errorf("fallo inicial")
	if true {
		err := fmt.Errorf("fallo interno") // SOMBRA: crea una nueva variable err
		fmt.Println("dentro:", err)
	}
	fmt.Println("fuera:", err)
}

Guía práctica para evitar problemas: usa = cuando quieras reasignar una variable existente; usa nombres más específicos en bloques internos; limita el alcance con inicialización en if cuando sea apropiado.

Alcance en if con inicialización (recomendado)

import (
	"fmt"
	"strconv"
)

func main() {
	if n, err := strconv.Atoi("10"); err == nil {
		fmt.Println("ok:", n)
	} else {
		fmt.Println("error:", err)
	}
	// n y err no existen aquí
}

Guía paso a paso: construir utilidades pequeñas con composición

Utilidad 1: normalizar y validar un puntaje

Objetivo: recibir un puntaje como string, convertirlo a entero, validar rango 0..100 y devolver una categoría.

  • Paso 1: convertir string a int con strconv.Atoi.
  • Paso 2: validar rango con if.
  • Paso 3: categorizar con switch.
import (
	"errors"
	"strconv"
)

func parsePuntaje(s string) (int, error) {
	n, err := strconv.Atoi(s)
	if err != nil {
		return 0, errors.New("puntaje no numérico")
	}
	if n < 0 || n > 100 {
		return 0, errors.New("puntaje fuera de rango")
	}
	return n, nil
}

func categoriaPuntaje(n int) string {
	switch {
	case n >= 90:
		return "A"
	case n >= 80:
		return "B"
	case n >= 70:
		return "C"
	case n >= 60:
		return "D"
	default:
		return "F"
	}
}

func evaluarPuntaje(input string) (string, error) {
	n, err := parsePuntaje(input)
	if err != nil {
		return "", err
	}
	return categoriaPuntaje(n), nil
}

Utilidad 2: formatear una lista de precios con impuestos

Objetivo: dado un conjunto variádico de precios, aplicar un impuesto si el precio es válido (mayor que 0) y devolver el total y la cantidad de elementos ignorados.

  • Paso 1: recorrer con for.
  • Paso 2: usar continue para ignorar inválidos.
  • Paso 3: devolver múltiples valores (total, ignorados).
func totalConImpuesto(tasa float64, precios ...float64) (total float64, ignorados int) {
	for _, p := range precios {
		if p <= 0 {
			ignorados++
			continue
		}
		total += p * (1 + tasa)
	}
	return total, ignorados
}

Utilidad 3: generador de validadores con closures

Objetivo: crear una función que genere validadores de rango reutilizables para enteros.

  • Paso 1: escribir una función que reciba min y max.
  • Paso 2: devolver una función anónima que capture esos valores.
  • Paso 3: usarla para filtrar datos.
func enRango(min, max int) func(int) bool {
	return func(n int) bool {
		return n >= min && n <= max
	}
}

func filtrar(nums []int, pred func(int) bool) []int {
	salida := make([]int, 0, len(nums))
	for _, n := range nums {
		if pred(n) {
			salida = append(salida, n)
		}
	}
	return salida
}

func main() {
	edades := []int{12, 17, 18, 25, 70}
	adultos := filtrar(edades, enRango(18, 65))
	_ = adultos
}

Ejercicios progresivos

Ejercicio 1: conversión y validación

Escribe una función parseEnteroEnRango(s string, min, max int) (int, error) que convierta un string a entero y valide el rango. Casos: "10" válido, "-1" fuera de rango, "abc" inválido.

Ejercicio 2: calculadora de carrito

Crea subtotal(precios ...float64) (float64, int) que sume solo precios positivos y devuelva también cuántos fueron ignorados. Luego crea total(sub float64, tasa float64) float64 y compón ambas en checkout(tasa float64, precios ...float64) (float64, int).

Ejercicio 3: categorización con switch

Implementa prioridad(severidad int) string con reglas: 1-2 baja, 3 media, 4 alta, 5 crítica, cualquier otro valor inválida. Usa switch sin expresión (por condiciones).

Ejercicio 4: bucles y cortes

Escribe primerMultiplo(nums []int, k int) (int, bool) que recorra la lista y devuelva el primer número múltiplo de k. Si k es 0, devuelve false. Usa break cuando lo encuentres.

Ejercicio 5: closures para contadores

Crea nuevoAcumulador() func(int) int que devuelva una función que sume el valor recibido a un total interno y devuelva el total actualizado. Prueba llamando varias veces con diferentes incrementos.

Ejercicio 6: composición con funciones como valores

Implementa mapear(nums []int, f func(int) int) []int y úsala para: duplicar valores, convertir a valor absoluto, y aplicar un tope máximo (clamp) usando un closure que capture el límite.

Ahora responde el ejercicio sobre el contenido:

¿Cuál afirmación describe correctamente el comportamiento de := en Go y cómo puede causar shadowing?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

En Go, := declara e inicializa de forma concisa dentro de funciones. Si se usa en un bloque interno con un nombre ya existente, crea una nueva variable y puede sombrear la externa. Para reasignar la existente se usa =.

Siguiente capítulo

Estructuras de datos en Go: arrays, slices, mapas y cadenas

Arrow Right Icon
Portada de libro electrónico gratuitaGo desde Cero: Programación Moderna, Rápida y Escalable
17%

Go desde Cero: Programación Moderna, Rápida y Escalable

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.