DEV Community

¡Go, Go, Go! ¡Es golang!

El lenguaje de programación Go se ha alzado como el dominante entre los lenguajes de programación actuales para servidores. ¿Por qué? Porque genera un ejecutable. Un solo ejecutable. Solo un archivo. La instalación es trivial: solo copia el archivo en el servidor, y se acabó.

Desde el principio, los creadores de Go se decidieron a solucionar los típicos problemas presentes en C y C++. El primero de todos, era la velocidad de compilación. El segundo ya lo hemos visto: la necesidad de mantener una infraestructura de DLL con las versiones correctas, frente a la decisión tomada de generar un ejecutable estático, es decir, que lo incluye todo, sin dependencias. El tercero, solucionar las ambigüedades presentes en C, además de las posibilidades de dispararte en el pie gracias a C++.

"C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off." Bjarne Stroustrup

"Con C es fácil dispararte en tu propio pie; C++ lo hace más difícil, pero cuando lo haces, te has volado la pierna entera." Bjarne Stroustrup

Con Go, no existe un preprocesador. La instrucción import acepta el nombre de un módulo entre comillas. Por ejemplo, Printf() está en el módulo fmt. ¿Printf con mayúscula? Pues sí, porque los símbolos públicos de un módulo llevan la inicial en mayúscula. Y siempre se antepone el nombre del módulo, separado por un punto del identificador. Las funciones se marcan con la palabra clave func. No hay void, simplemente no se pone nada.

package main

import "fmt"

func main() {
    fmt.Printf("Hello, world!")
}
Enter fullscreen mode Exit fullscreen mode

Las variables locales se marcan con var (a no ser que su tipo se deduzca de la inicialización, en ese caso se omite var y se utiliza ':='). Los tipos siempre se colocan a la derecha del identificador... es práctico imaginarse que hay un ':' tras el nombre del identificador.

func distanciaPuntos(x1, y1, x2, y2 float64) float64 {
    distX := x1 - x2
    distY := y1 - y2

    distX *= distX
    distY *= distY

    return math.Sqrt(distX + distY)
}
Enter fullscreen mode Exit fullscreen mode

Los tipos de números reales son dos: float32 y float64. La función Sqrt() se encuentra en el módulo math.

Y no, no se ponen puntos y coma (';'), a no ser que quieras introducir dos instrucciones en la misma línea. Las llaves de apertura ('{'), siempre se abren en la última posición de la instrucción que genera el bloque de código. El cierre de llave ('}'), siempre va en una línea aparte.

La palabra package le da su nombre al módulo; aquel que tiene la función main() siempre es el módulo main.

$ go run hello.go
Hello, world!
Enter fullscreen mode Exit fullscreen mode

Y hay que reconocer que impresiona la velocidad de compilación de Go. La apariencia es que el programa se ejecuta inmediatamante. Pero.. ¿qué ha generado?

$ go build hello.go
$ ls
hello hello.go
$ ls -l
2424611 oct  9 09:29 hello
     65 oct  9 09:27 hello.go
Enter fullscreen mode Exit fullscreen mode

Efectivamente, se trata de un ejecutable estático, o también conocido como fat binary. Y es que el programa tiene un tamaño de casi 2.5 Mb.

El programa equivalente en C++ es el ya conocido.

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello, world!" << endl;
}
Enter fullscreen mode Exit fullscreen mode

Generar el ejecutable es una cuestión ya conocida.

$ g++ hello.cpp
$ ./a.out
Hello, world!
$ ls
a.out  hello  hello.cpp  hello.go
$ ls -l
   16288 oct  9 09:32 a.out
 2424611 oct  9 09:29 hello
      94 oct  9 09:32 hello.cpp
      65 oct  9 09:27 hello.go
Enter fullscreen mode Exit fullscreen mode

Parece que no hay color, ¿verdad? El ejecutable de C++ solo ocupa 16 Kb. Pero espera. C++ no genera ejecutables estáticos por defecto, sino que utiliza la DLL de la librería estándar de C++.

$ ls -l /usr/lib/libstdc++*
     319 ago 10 22:04 /usr/lib/libstdc++.modules.json
      19 ago 10 22:04 /usr/lib/libstdc++.so -> libstdc++.so.6.0.36
      19 ago 10 22:04 /usr/lib/libstdc++.so.6 -> libstdc++.so.6.0.36
 2939928 ago 10 22:04 /usr/lib/libstdc++.so.6.0.36
Enter fullscreen mode Exit fullscreen mode

¡Pues casi 3 Mb! Vamos a generar el ejecutable estático, en cualquier caso.

$ g++ --static hello.cpp
$ ls -l
total 4836
 2517752 oct  9 09:40 a.out
 2424611 oct  9 09:29 hello
      94 oct  9 09:32 hello.cpp
      65 oct  9 09:27 hello.go
Enter fullscreen mode Exit fullscreen mode

Ya vemos que no hay mucha diferencia en realidad.

Pongamos un ejemplo práctico. Un ejercicio estrella es... la sucesión de Fibonacci. Pero vamos a crearlo como lo haríamos con Python, generando un array (lo más parecido a una lista en Go). Efectivamente, Go permite el manejo de arrays dinámicos de forma sencilla. Si queremos crear un literal de array, solo tenemos que utilizar las llaves tras el tipo... ¡los corchetes van a la izquierda! A continuación, vemos un array con el inicio de la sucesión de Fibonacci. Además, append() permite añadir un elemento nuevo al array de forma sencilla.

[]uint64{1, 1, 2, 3, 5, 8}
Enter fullscreen mode Exit fullscreen mode

Hay varios tipos predefinidos, varios de ellos son los enteros de diferentes tamaños. int8 es un entero de 8 bits, aceptando valores de -128 a 127. uint8 es un entero sin signo de 8 bits (o 1 byte), y por eso tiene un alias, byte, ya que acepta valores de 0 a 255. Tenemos enteros, y enteros sin signo, de 16 bits (2 bytes, -32768 a 32767), 32 bits (4 bytes, de -2 mil millones a 2 mil millones aprox.), y 64 bits (8 bytes, una locura). El tipo int equivale al entero con signo del mismo tamaño que el tamaño de palabra del sistema, típicamente int32, o int64.

El tipo escogido es uint64, pues la sucesión de Fibonacci no tiene negativos, y crece muy rápidamente.

El bucle for puede sorprendernos; de hecho es el único bucle disponible, for <condición> es el bucle while. El típico bucle for de C es prácticamente el mismo. Eso sí, al igual que con if, la condición no va entre paréntesis.

package main

import "fmt"

func fibo(n uint64) []uint64 {
    var fibo []uint64 = []uint64{1, 1, 2, 3, 5, 8}
    var lenFibo uint64 = uint64(len(fibo))

    if n <= lenFibo {
        return fibo[:n]
    }

    var i uint64 = lenFibo
    toret := fibo
    for ; i < n; i++ {
        toret = append(toret, toret[i - 1] + toret[i - 2])
    }

    return toret
}

func main() {
    fmt.Printf("Fibo\n\n")

    var i uint64 = 0
    for ; i < 20; i++ {
        fmt.Printf("Fibo %03d: %v\n", i, fibo(i))
    }
}
Enter fullscreen mode Exit fullscreen mode

La salida del programa es:

❯ go run fibo.go
Fibo

Fibo 000: []
Fibo 001: [1]
Fibo 002: [1 1]
Fibo 003: [1 1 2]
Fibo 004: [1 1 2 3]
Fibo 005: [1 1 2 3 5]
Fibo 006: [1 1 2 3 5 8]
Fibo 007: [1 1 2 3 5 8 13]
Fibo 008: [1 1 2 3 5 8 13 21]
Fibo 009: [1 1 2 3 5 8 13 21 34]
Fibo 010: [1 1 2 3 5 8 13 21 34 55]
Fibo 011: [1 1 2 3 5 8 13 21 34 55 89]
Fibo 012: [1 1 2 3 5 8 13 21 34 55 89 144]
Fibo 013: [1 1 2 3 5 8 13 21 34 55 89 144 233]
Fibo 014: [1 1 2 3 5 8 13 21 34 55 89 144 233 377]
Fibo 015: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610]
Fibo 016: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987]
Fibo 017: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597]
Fibo 018: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584]
Fibo 019: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181]
Fibo 020: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765]
Enter fullscreen mode Exit fullscreen mode

La idea con la que me quedo de Go es que es... muy práctico. Esto es normal, debido al interés de sus diseñadores en resolver problemas con los que se enfrentaban todos los días. Concretamente, estaban hartos de tener que esperar y esperar incluso más de 15 minutos para que sus programas compilasen. Y de hecho, fue en esas esperas cuando empezaron a diseñar Go.

El uso de módulos por un lado, y estructuras con interfaces por el otro, permite un cierto tipo de programación orientada a objetos, aunque se construya de una manera mucho más primitiva. Pero eso ya lo veremos en la siguiente entrega.

Top comments (0)