Empecemos por conocer qué es GoLang o simplemente "Go":

GoLang, también conocido como simplemente ‘Go’, es un lenguaje de programación creado por Rob Pike, Ken Thompson y Robert Griesemer en 2007, lanzado en 2009, que cuenta con el patrocinio de Google. Está diseñado para trabajar en soluciones que requieren un alto rendimiento y escalabilidad, especialmente en el procesamiento de datos y las conexiones de red.

Características claves de Go:

  1. Compilado: Go se compila y genera código de máquina, lo que lo hace rápido y eficiente. Ya que nos evitamos el tiempo de interpretación de código que otros lenguajes como Javsscript o Python requieren.
  2. Tipado estático: Las variables deben declararse con un tipo específico, esto garantiza el orden y permite evitar problemas por inconsistencias a la hora de usar las variables.
  3. Programación concorrente/paralela nativa: Go tiene excelentes capacidades para manejar múltiples hilos de ejecución. Esto hace de Go eficiente y orientado a aplicaciones donde la cantidad de solicitudes sea alta.
  4. Garbage Collector Nativo: Go gestiona automáticamente la memoria, de esta manera nosotros no debemos preocuparnos por alocar o liberar la memoria como en otros lenguajes como C++.
  5. Simplista al extremo: Go elimina características complejas como clases y herencias, sobreescrituras de métodos, hasta el famoso try/catch fue removido, esto porque Go se enfoca en la velocidad y el perfomance, pero ojo es de igual forma podemos aplicar el paradigma de la programación orientada a objetos.

Diferencias entre lenguajes compilados e interpretados:

  • Compilado: En Go, todas las instrucciones del código fuente se convierten en lenguaje de máquina antes de la ejecución. Esto proporciona mayor velocidad y por lo tanto mayor rendimiento.
  • Interpretado: En lenguajes interpretados, como Python o JavaScript, el código se ejecuta línea por línea en tiempo real, lo cual puede tener una perdida de velocidad, pero también se gana la facilidad de realizar cambios.

Recuerden no todo es ganancia o pérdida, aunque en el caso anterior me quedo con los lenguajes compilados.

Instalación del ambiente

Para trabajar con Go, debemos hacer lo que denominamos la instalación de la infraestructura de trabajo, que se reduce a tener el compilador disponible en nuestro computador.

Go, para recordar es un proyecto Open Source y multiplataforma, es decir lo podemos usar en Windows, Linux o Mac.  Tenemos procesos de instalación para cada uno de los sistemas operativos, sólo debemos acceder al sitio web de Go (https://go.dev) y buscar el correspondiente a nuestro sistema operativo.

Veamos los pasos para cada uno de ellos:

  • Windows.  Siempre simple, es sólo hacer la descarga del programa de instalación y seguir el paso a paso de asistente hasta finalizar.
  • Linux. Lo instalamos vía línea de comandos, desde una terminal, ejecutando el comando:
sudo snap install go --classic
  • Mac. Es bajar el archivo .pkg y realizar el proceso de desempaquetado.

Finalizado el proceso, podemos comprobar si todo está bien, ejecutando el comando:

go version

Este comando debe mostrar algo como esto:

image-1

Al llegar a este punto, ya tenemos todo lo necesario para iniciar nuestro camino con Go.

Hola Mundo en Go

Como siempre, cuando iniciamos en un lenguaje de programación, lo tradicional es que iniciemos con un primer programa, el cual llamamos "Hola Mundo', donde tenemos ese primer contacto y ya nos comenzamos a empoderar de la nueva herrramienta de desarrollo.

En el caso de Go, no será la excepción, empecemos con este "Hola Mundo", el cual sigue a continuación:

package main

import "fmt"

func main() {
	// De esta forma comentamos el código
	fmt.Println("Hola Mundo!")
}


Revisemos las líneas de código anteriores y destaquemos algunas características:

  • Existe la importación de un paquete llamado main, el cual define la estructura de la aplicación, todo programa Go usa este paquete.
  • La función main, es el punto de inicial o de entrada de nuestra aplicación. A partir de allí desarrollamos nuestra aplicación.
  • Luego tenemos las importaciones, Go trabaja con paquetes, de entrada él tiene lo que se denomina la librería standard, que ya nos provee de una serie de funcionalidades interesantes (https://pkg.go.dev/std), en esta caso de esa librería, tomamos a "fmt" para realizar operaciones de escritura en la pantalla.

Con ello ya tenemos nuestra primera aplicación Go ejecutandose!

Tipos de Datos en Go

En Go podemos manejar datos de los tipos primitivos, así como en cualquier lenguaje de programación, entremos en detalle:

Tipo de datos númericos enteros:

En el caso de los números enteros, en Go, podemos decir si vamos a manejar con signo positivo o negativo, o simplemente los positivos. Además tienen la particularidad, de definir el rango, de esta forma podemos ser óptimos y usar los recursos de memoria que realmente necesitamos.

Enteros con signo:

  • int8: Puede almacenar valores desde -128 hasta 127. Esto se denominan enteros de 8 bits.
  • int16: Almacena enteros con signo de 16 bits, con un rango de -32,768 a 32,767.
  • int32 (también conocido como rune): Utilizado para caracteres Unicode y tiene un rango de -2,147,483,648 a 2,147,483,647.
  • int64: Maneja números enteros con signo de 64 bits, con un rango de -9,223,372,036,854,775,808 a 9,223,372,036,854,775,807.
  • int: Su tamaño depende de la arquitectura subyacente (32 o 64 bits).

Enteros sin signo, o sólo valores positivos.

  • uint8 (también conocido como byte): Representa enteros sin signo de 8 bits, con valores entre 0 y 255.
  • uint16: Números desde 0 a 65,535. Almacena enteros sin signo de 16 bits.
  • uint32: Utilizado para números grandes sin signo, con un rango de 0 a 4,294,967,295.
  • uint64: Almacena enteros sin signo de 64 bits, con un rango de 0 a 18,446,744,073,709,551,615.
  • uint: Su tamaño también depende de la arquitectura (32 o 64 bits).

Tipos de datos números decimales o de punto flotante:

En el caso de números decimales, no tenemos tanta variedad de tipos, sólo tenemos una diferencia y es por la arquitectura donde se ejecuta el programa (32 o 64 bits), pero vale la pena conocer sus características:

float32:

  • Representa números de punto flotante de 32 bits.
  • Puede almacenar valores con precisión hasta 7 dígitos decimales.
  • Es adecuado para cálculos donde la precisión no necesita ser extremadamente alta.

float64:

  • Es el tipo de punto flotante más común en Go.
  • Utiliza 64 bits para representar números de punto flotante.
  • Ofrece mayor precisión que float32, con capacidad para almacenar hasta 15 dígitos decimales.

Es común usar float64 por su mayor precisión a la hora de almacenar datos (15 decimales!)

Otros tipos de datos primitivos

En Go, podemos manejar:

  • string: Con este tipo de datos podemos almacenar cadenas alfanuméricas, para definir sus valores, basta usar comillas dobles ("") o comillas simples (''), el compilador las reconoce de igual forma.
  • bool:  A partir de este tipo de dato podemos manejar valores lógicos: true o false, los cuales son muy útiles cuando manejamos condiciones.
  • struct: Este tipo de dato es muy útil, debido a que con el podemos definir datos personalizados. En Go no hay clases, pero con struct, es posible crear una suerte de interface o clase de sólo propiedades. De esta forma podemos una entidad con sus campos y manejarla de forma agrupada.

Tipos de datos compuestos

En Go podemos los siguientes tipos de datos que son combinaciones o mezcla de tipos de datos primitivos:

  • Arreglos: En un tipo de datos que representa una colección de elementos del mismo tipo. La longitud de un array es fija y se especifica al declararlo, a diferencia de otros lenguajes donde declaramos arreglos sin tamaño, en Go debemos indicar el tamaño al declararlo.

Ejemplo:

var miArray [5]int // Un array de 5 enteros

En Go, no es posible mezclar los tipos de datos de los valores que son parte de un arreglo, como por ejemplo si podemos hacer en Javascript.

  • Slices: Son arreglos sin tamaño, es decir, si hay posibilidad de definir arreglos sin tamaño pero Go, los denonima como slides. Varía un poco su forma de declararlos.

Ejemplo:

paises := []string{"Venezuela","Brasil","Chile"}
  • Mapas: Son un tipo de datos usado por excelencia, se definen como lista basada en el par: clave-valor, es decir cada valor está identificado por un índice personalizado (a diferencia de los arreglos que son identificado por un índice numérico secuencial).

Algunas características de los maps:

  • Las claves deben ser únicas y comparables (por ejemplo, strings, ints, etc.).
  • Los valores pueden ser de cualquier tipo y se define al declararlo.
  • Para declarar un mapa, utilizamos la sintaxis map[TipoClave]TipoValor.
  • Los mapas no siguen un orden, ni mantienen el orden de la declaración, hay que tener eso en cuenta.

Ejemplo:

miMapa := map[string]string{
   "clave1": "valor1",
   "clave2": "valor2",
   "clave3": "valor3",
}

Para agregar nuevos valores al mapa, basta apenas indicar la nueva clave y su valor como sigue:

miMapa["clave4"] = "Valor4"

Para borrar una de las propiedades del mapa usamos el método delete:

delete(miMapa, "clave1")

En el caso anterior estariamos borrando la clave clave1 de miMapa .

Adicionalmente en Go, tenemos la posibilidad gestionar las variables, a nivel de memoria, este tipo de datos se denomina Punteros. En realidad los punteros no son variables sino referencias a variables, esto es especialmente útil para poder reutilizar espacios de memoria por ejemplo al usar parámetros (que por defecto hacen una copia y consumen nuevamente los recursos).declarar

Veamos como se usa un puntero sobre una variable numerica:

package main

import "fmt"

func main() {
    var miVariable int = 10
    var miPuntero *int = &miVariable

    fmt.Println("Valor de miVariable:", miVariable)
    fmt.Println("Valor de miPuntero (dirección de memoria):", miPuntero)
    fmt.Println("Valor apuntado por miPuntero:", *miPuntero)
}

Observa que si pedimos el valor del puntero, lo que vamos a obtener es la dirección de memoria de la caja o variable. Este tipo de datos es super útil cuando queremos tener control de la gestión de memoria.

Declaración de variables en Go

En Go tenemos 2 formas de declarar variables, veamos en detalle:

  • Declaración formal:
var nombreVariable <tipo de datos>
  • Inferencia del tipo a partir del valor inicial:
var nombreVariable = <Valor>

La variable toma el tipo de dato del valor indicado.

Adicionalmente tenemos una forma abreviada de declarar variables:

nombreVariable := <Valor>

De lo anterior, Go infiere de igual forma el tipo de dato a partir del valor indicado.

Puedes profundizar en el uso de los tipos de datos y declaración de variables en el siguiente video:

Condicionales en Go

En Go, similar a otros lenguajes de programación, usamos las palabras reservadas if, else, switch, para poder evaluar expresiones y ejecutar código de forma condicional, es decir ejecutar código cuando se cumple o no cumple alguna condición.

Una característica interesante de Go, es que las condiciones no necesitan parentesis para ser evaluadas, a diferencia de otros lenguajes de programación donde es obligatorio encerrar entre paréntesis la condición, así sea simple.

Ejemplos:

  1. Condición simple.
package main

import (
	"fmt"
)

func main() {
	var userInput int
	fmt.Print("Ingresa un valor: ")
	fmt.Scan(&userInput)

	if userInput > 10 {
		fmt.Print("Valor ingresado mayor a 10: ")
	}
}

2. Condicional evaluando cuando se cumple y cuando no se cumple la condición

package main

import (
	"fmt"
)

func main() {
	var userInput int
	fmt.Print("Ingresa un valor: ")
	fmt.Scan(&userInput)

	if userInput > 10 {
		fmt.Print("Valor ingresado mayor a 10: ")
	} else {
		fmt.Print("Valor ingresado es menor o igual 10: ")
	}
}

3. Condicionales anidados, donde evaluamos múltiples condiciones:

package main

import (
	"fmt"
)

func main() {
	var userInput int
	fmt.Print("Ingresa un valor: ")
	fmt.Scan(&userInput)

	if userInput > 10 {
		fmt.Print("Valor ingresado mayor a 10: ")
	} else if userInput < 10 {
		fmt.Print("Valor ingresado es menor a 10: ")
	} else {
		fmt.Print("Valor ingresado es 10: ")
	}
}

Veamos ahora como funciona el comando switch, el cual nos permite de forma fácil evaluar múltiples condiciones, permitiendo ejecutar bloques de código. Sigue un ejemplo:

package main

import (
	"fmt"
)

func main() {
	var fruta string

	fmt.Print("Ingresa un valor alfanumérico: ")
	fmt.Scan(&fruta)

	switch fruta {
	case "manzana":
		fmt.Println("Has ingresado 'manzana'.")
	case "banana":
		fmt.Println("Has ingresado 'banana'.")
	case "pera":
		fmt.Println("Has ingresado 'pera'.")
	default:
		fmt.Println("No reconozco ese valor.")
	}
}

Lazos en Go

Es tarea cotidiana, dentro del desarrollo de software, que existan procesos o bloques de código que necesitamos ejecutar más de una vez, Go implementa las estructuras de repetición usando sólo la palabra reservada for.

La sintaxis general del for es la siguiente:

for [Instrucción Inicial]; [Condición]; [Instrucción Posterior] {
    // Acción a realizar en cada iteración
}

Veamos como implementamos algnos procesos de repetición:

  1. Repita para, donde definimos la cantidad de veces que el proceso se va a repetir. Por ejemplo queremos ejecutar la suma de los primeros 100 numeros.
package main

import "fmt"

func main() {
    sum := 0

    for i := 0; i <= 100; i++ {
        if i%2 != 0 {
            sum += i
        }
    }

    fmt.Printf("La suma de los números impares entre 0 y 100 es: %d\n", sum)
}

Observa que se definen:

  • Instrucción Inicial: Se asigna el valor 0 al contador.
  • Condición: Se verifica si ya llegamos al número 100.
  • Instrucción Posterior: Avanzamos al siguiente número.

2. Ahora simulemos lo que en otros lenguajes conocemos como for-each, usando for

package main

import (
	"fmt"
)

func main() {

	var m map[string]string = map[string]string{
		"clave1": "valor1",
		"clave2": "valor2",
		"clave3": "valor3",
		"clave4": "valor4",
	}

	for k, v := range m {
		fmt.Println(k, v)
	}

}

3. Implementando un repita mientras usando for, en Go no tenemos while o do-while, pero podemos representar la acción de la siguiente forma:

package main

import (
	"fmt"
	"strings"
)

func main() {
	var fruta string

	for {
		fmt.Print("Ingresa una fruta: ")
		fmt.Scan(&fruta)

		// Convertimos la entrada a minúsculas para evitar problemas de capitalización
		fruta = strings.ToLower(fruta)

		if fruta == "naranja" {
			fmt.Println("¡Has ingresado una naranja! El bucle se detiene.")
			break
		} else {
			fmt.Println("No es una naranja. Inténtalo nuevamente.")
		}
	}
}

Observa que usamos el for sin otros parámetros:

for {
}

Mediante lo anterior generamos un bucle infinito y es nuestra responsabilidad a lo interno validar la condición de salida. Es este punto el que no define si es un repita mientras o un repita hasta, recordando que la diferencia entre ellos es que el repita mientras se puede o no ejecutar, pues la condición se debe evaluar al inicio del proceso y el repita hasta al menos se ejecuta una vez. El código anterior es un repita hasta si lo analizamos en detalle porque se lee la fruta al menos una vez, veamos como seria en forma de repita mientras.

package main

import (
	"fmt"
	"strings"
)

func main() {
	fruta := "banana"

	for {

		if fruta == "naranja" {
			fmt.Println("¡Has ingresado una naranja! El bucle se detiene.")
			break
		} else {
			fmt.Println("No es una naranja. Inténtalo nuevamente.")
		}

		fmt.Print("Ingresa una fruta: ")
		fmt.Scan(&fruta)

		// Convertimos la entrada a minúsculas para evitar problemas de capitalización
		fruta = strings.ToLower(fruta)

	}
}


Go, es un lenguaje altamente demandado actualmente, usado por empresas de gran tamaño, así que es una herramienta super atractiva de aprender, para más contenidos puedes verificar mis perfiles en: