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.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
| Categoría | Tipos comunes | Uso típico |
|---|---|---|
| Enteros | int, int64, uint | contadores, índices, IDs (según rango) |
| Decimales | float32, float64 | mediciones, cálculos con decimales |
| Texto | string | nombres, mensajes, JSON |
| Booleanos | bool | banderas, condiciones |
| Bytes/Runas | byte, rune | procesamiento 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, okFunciones 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
continuepara 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
minymax. - 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.