====== Go ====== Lenguaje de programación concurrente y compilado inspirado en la sintaxis de C, que intenta ser dinámico como Python y con el rendimiento de C o C++. * [[https://golang.org/|Web oficial]] Las notas aquí recogidas están centradas en la versión 1.17 de Go ===== Lo bueno ===== * Número bajo de palabras reservadas (//keywords//) * Rapidez como C++ * ''go doc'': permite consultar y generar documentación * [[https://blog.golang.org/gofmt|gofmt]]: herramienta oficial que automáticamente formatea código Go. Tan sencillo como ''gofmt -w codigo.go'' * Compilación cruzada: poder compilar en diferentes sistemas y hacia cualquier sistema. * Manejo muy eficiente de concurrencia Go te obliga a mantener el código limpio. Si declaras la importación de un paquete y no lo usas, Go se quejará y no compilará: package main import "fmt" func main() { } El código anterior no compilará. Go dirá //imported and not used: "fmt"// Tampoco permite tener variables o sentencias que no se usen: package main func main() { a := 0 } El código anterior no compilará. Go dirá //a declared and not used// ===== Ejemplos ===== package main // Varias formas de importar paquetes import ( "fmt" . "fmt" _ "log" ) const language = "language" var global = true func main() { // Método largo de declaración e inicialización de variables var a float64 = 2 // Método abreviado b := 0 fmt.Println(a) Println(b) } Ejemplo de **bucles**: package main import ( "fmt" . "fmt" _ "log" ) func main() { // Sintaxis normal de bucle 'for' for j := 0; j < 5; j++ { Println(j) } // Creación de un array de 3 enteros nums := []int{10, 20, 30} // Forma de recorrer array con 'range' (palabra reservada) // 'range' devuelve 2 valores: índice del valor // que se tiene en la iteración y el otro es el valor // Con el identificador blanco (_) ignoramos una de las variables // que devuelve 'range' for _, n := range nums { Println(n) } } Ejemplo de **switch**: package main import ( "fmt" . "fmt" _ "log" ) func main() { bestLanguage := "Go" // A diferencia de otros lenguajes, no hay que terminar // cada 'case' con un 'break'. En Go funciona al revés, // es decir, si ponemos un break, continúa al siguiente // 'case' switch bestLanguage { case "Python": fmt.Println("Ouch, no! It's for scripting") case "Scala": fmt.Println("Ouch, no! It's funcional") case "Go": fmt.Println("Awww yeees!!!") } } **Funciones** en Go: package main import ( . "fmt" _ "log" ) // Si el nombre de la función está en mayúscula, la // función se puede "exportar", es decir, sería // pública, expuesta fuera de este paquete func Message() string { return "Hello World" } // Podemos devolver el mensaje de forma implícita // Es como si declarásemos una variable con un valor func MessageImplicit() (msg string() { msg = "Hello World" return } func main() { } ===== Instalación ===== ==== Linux ==== Para instalar el compilador estándar de Go podemos ir a la [[https://golang.org/dl/|web oficial]] o buscar el paquete para nuestra distribución. Por ejemplo, para el caso de Arch Linux: pacman -S go Comprobamos si todo está instalado y configurado correctamente: $ go version go version go1.20.3 linux/amd64 ==== Linux manual ==== - [[https://golang.org/dl/|Descargar]] de la página oficial Extraemos el contenido en ''/usr/local'': rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.linux-amd64.tar.gz El comando anterior borrará la instalación (si la hubiera) de ''/usr/local/go'' Añadimos ''/usr/local/go/bin'' a la variable de entorno ''PATH'', por ejemplo añadiendo la siguiente línea a ''$HOME/.profile'' (o ''/etc/profile'' para todos los usuarios): export PATH=$PATH:/usr/local/go/bin Para que se apliquen los cambios en la variable de entorno, tenemos que cerrar la sesión y volver a entrar o podemos aplicarlos inmediatamente con ''source $HOME/.profile'' Verificamos que la instalación ha sido correcta: go version Ejemplo de salida: go version go1.21.0 linux/amd64 ===== Configuración ===== Para ver las variables de entorno y configuraciones que usa Go: go env ==== GOPATH ==== Muestra la localización del directorio de trabajo, es decir, donde trabajaremos con archivos de Go. Actualmente esta variable en vías de desaparición Por defecto, está establecido en el directorio personal del usuario. ''home/usuario/go'' en Linux, por ejemplo. El directorio ''$GOPATH/src'' se usa para almacenar el código fuente de los paquetes. Cuando se compila un programa Go, también se crean los directorios ''bin'' para ejecutables y ''pkg'' como caché para los paquetes individuales. En Go, cada programa ejecutable necesita su propio directorio: $GOPATH |- src |- foo |- main.go ===== Terminología ===== ==== Paquetes ==== Un paquete (//package//) es una forma de agrupar funciones. Está formado por una serie de ficheros contenidos en un mismo directorio. ==== Módulos ==== En un módulo se agrupan paquetes relacionados que contienen funciones útiles. El código de Go está agrupado en paquetes y los paquetes están agrupados en módulos. ==== Biblioteca estándar de Go ==== //Standard library// la forman paquetes y utilidades incluidas en la instalación de Go, es decir, que no necesitamos recursos externos para utilizarlas en nuestro código. Esto permite ampliar las características básicas del lenguaje. Esta biblioteca no solo incluye documentación sino trozos de código de ejemplo. Para usarla en nuestros programas, sencillamente hacer la llamada mediante la palabra reservada ''import'' del paquete que queramos de esa biblioteca: import "fmt" * [[https://pkg.go.dev/std|Listado de paquetes de la biblioteca estándar]] ===== Herramientas ===== Utilidades que vienen con la instalación de Go ==== go fmt ==== Formatea el código fuente de Go para respetar las especificaciones. gofmt -w main.go Si no ponemos la opción ''-w'', **gofmt** mostraría los cambios que va a realizar. ==== go run ==== Compila y ejecuta los ficheros Go: go run main.go ==== go build ==== Compila para poder obtener el binario: go build -o main main.go Con la opción ''-o'' indicamos el nombre del archivo resultante. La compilación genera un binario adaptado a la arquitectura donde se ejecuta la compilación. Si queremos compilarlo para una arquitectura diferente, se puede indicar como argumento. ==== go mod ==== Dentro del directorio de nuestro proyecto: go mod ===== Hola, mundo ===== package main import "fmt" func main() { fmt.Println("Hola, mundo!") } * La cláusula ''package'' debe ser lo primero que escribamos en nuestro código fuente. * ''main'' es el nombre del paquete al que pertenece el archivo que creamos. * ''func'' define una función, un bloque de código reutilizable. * ''main'' es una función especial que Go ejecuta automáticamente. * ''fmt'' (//formatting//), paquete que incluye la biblioteca estándar de Go. Cada programa Go debe pertenecer a un único paquete En Go, el código que será ejecutado como una aplicación debe estar en el paquete ''main'' Si existe una función ''main'', se llama por defecto al ejecutar el paquete ''main'' ==== Compilación ==== El código escrito en Go debe pasar por un proceso de compilación para que sea traducido a lenguaje máquina y el sistema operativo lo entienda y sea capaz de ejecutarlo. La compilación se realiza con ''go build''. Nos situamos en el directorio que contiene el código Go y ejecutamos: go build Se creará un ejecutable llamado igual que el directorio: [usuario@machine hello]$ ls -l total 1988 -rwxr-xr-x 1 tempwin wheel 2030685 dic 1 17:17 hello -rw-r--r-- 1 tempwin wheel 76 dic 1 17:11 main.go Podremos ejecutarlo con ''./hello'' === Por Sistema Operativo === A la hora de compilar un programa de Go, podemos indicar en qué sistema operativo se utilizará: * Linux: ''GOOS=linux GOARCH=arm GOARM=7 go build'' * Window: ''GOOS=windows GOARCH=386 go build'' * OS X: ''GOOS=darwin GOARCH=386 go build'' Combinaciones válidas: ^ $GOOS ^ $GOARCH ^ | aix | ppc64 | | android | 386 | | android | amd64 | | android | arm | | android | arm64 | | darwin | amd64 | | darwin | arm64 | | dragonfly | amd64 | | freebsd | 386 | | freebsd | amd64 | | freebsd | arm | | illumos | amd64 | | js | wasm | | linux | 386 | | linux | amd64 | | linux | arm | | linux | arm64 | | linux | ppc64 | | linux | ppc64le | | linux | mips | | linux | mipsle | | linux | mips64 | | linux | mips64le | | linux | s390x | | netbsd | 386 | | netbsd | amd64 | | netbsd | arm | | openbsd | 386 | | openbsd | amd64 | | openbsd | arm | | openbsd | arm64 | | plan9 | 386 | | plan9 | amd64 | | plan9 | arm | | solaris | amd64 | | windows | 386 | | windows | amd64 | * https://golang.org/doc/install/source#environment ==== Compilación y ejecución ==== ''go build'' está muy bien para grandes aplicación o si queremos desplegarlo en algún sitio, pero no es nada cómodo para el desarrollo. Hay otra herramienta para poder compilar el programa y ejecutarlo a continuación: go run main.go ''go run'' no crea un ejecutable, solo compila y ejecuta el código compilador Si queremos ver lo que hace esa herramienta, podemos utilizar la opción ''-x'': go run -x main.go Si queremos ejecutar todo lo que hay en el directorio actual: go run . También podríamos hacerlo como ''go run *.go'' o ''go run main.go otro.go otro-mas.go'' ===== Palabras reservadas ===== Las palabras reservadas son aquellas propias del lenguaje y que no pueden ser utilizada como identificadores: * ''break'' * ''default'' * ''func'' * ''interface'' * ''select'' * ''case'' * ''defer'' * ''go'' * ''map'' * ''struct'' * ''chan'' * ''else'' * ''goto'' * ''package'' * ''switch'' * ''const'' * ''fallthrough'' * ''if'' * ''range'' * ''type'' * ''continue'' * ''for'' * ''import'' * ''return'' * ''var'' * [[https://golang.org/ref/spec#Keywords|Keywords in Go]] ===== Sentencias ===== Las sentencias (//statements//) son instrucciones que controlan el flujo de ejecución del programa. package main import "fmt" func main() { fmt.Println("Hola, mundo!") } El código está formado por sentencias que se ejecutan de arriba a abajo, salvo los bucles y condicionales. Una sentencia por línea. Si quisiéramos poner más de una instrucción por línea, usaríamos el punto y coma ('';'') para separarlos: fmt.Println("Hola, mundo!"); fmt.Println("Adiós, mundo!") ==== Incremento/decremento ==== Aumenta o reduce los valores numéricos en 1: var n int n++ // es lo mismo que n = n + 1 o n += 1 n-- // es lo mismo que n = n - 1 o n -= 1 Al ser considerado una sentencia, el incremento y decremento no pueden ser usados en expresiones. ''5 + n%%--%%'' sería incorrecto. ===== Expresiones ===== Una expresión es un código que produce uno o varios valores. package main import "fmt" func main() { // Concatenamos cadenas de caracteres con el operador + fmt.Println("Hola, " + "mundo!") } ===== Comentarios ===== Texto que el compilador de Go no interpretará. Se utiliza para añadir notas en el código, explicaciones, etc. // Comentario de una línea package main import "fmt" /* Comentarios de bloque, para poder escribir varias líneas sin tener que precederlas de // */ func main() { } ===== Tipos de datos ===== ==== Enteros ==== * ''int'' * ''int32'': 4 bytes Ejemplos de literales enteros: * -1 * 0 * 27 En programación un **literal** es un valor en el código fuente. ==== Decimales ==== ''float64'' Ejemplos: * ''-0.5'' * ''-.5'' * ''0.0'' * ''0.'' * ''1.0'' * ''1.'' ==== Lógicos ==== ''bool'' * ''true'' * ''false'' ==== Cadenas ==== ''string'' Los literales de cadena en Go se codifican en UTF8. * ''%%"%%hola%%"%%'' Un string es una serie de bytes Las runas representan un codepoint. Si queremos un string con varias líneas, utilizamos el acento grave (''`''): (...) var s string s = "hola" s = `hola` fmt.Println(s) s = "\n\t\"Hello\"\n" // Lo podemos hacer más legible: s = ` "Hello" ` fmt.Println(s) Las cadenas crudas (//raw string literal//) no son interpretadas por Go, se muestra tal como se escriben, así que no son necesarias las secuencias de escape. ==== Byte ==== "hey" se puede representar como ''[]byte{104, 101, 121}'' ==== Tipos compuestos ==== Tipos de datos compuestos por otros tipos de datos. * Arrays: colección de elementos. Tamaño fijo. * Slices: colección de elementos. Tamaño variable. * String Internals: slices de bytes * Maps: colección de pares de clave-valor * [[informatica:programacion:go#structs|Structs]]: agrupación de diferentes tipos de variables juntos. ===== Sistema de tipos ===== Go utiliza un sistema de tipos estático. A diferencia de los dinámicos, en los estáticos la comprobación de los tipos de datos se hace en tiempo de compilación, antes de ejecutar el programa. Un sistema de tipos responde a las siguientes preguntas: * ¿Qué clase de tipos están disponibles? * ¿Cómo interactúan esos tipos entre ellos? * ¿Qué está permitido y qué no? * ¿Cómo se crean nuevos tipos? ==== Tipos predeclarados ==== Tipo integrado en el lenguaje así que puede ser usado inmediatamente. En Go: * ''bool'' * ''int'' (se comporta como int32 o int64 dependiendo de la máquina), ''int8'', ''int16'', ''int32'', ''int64'' (los números indican los bits de longitud) * ''uint'', ''uint8'', ''uint16'', ''uint32'', ''uint64'' (enteros positivos) * ''string'' * ''float32'', ''float64'' * ''complex64'' * ''complex128'' ==== Tipos definidos ==== Son tipos creados a partir de tipos ya existentes. type Duration int64 ''Duration'' sería un nuevo tipo creado a partir del tipo ''int64'' Aunque un tipo se defina a partir de otro, no son iguales. Para el ejemplo anterior, Duration <> int64. Tendríamos que convertirlo de esta manera: ns = Duration(ms); ms = int64(ns) Ejemplo: // Convierte gramos en onzas package main import "fmt" type gramo float64 type onza float64 func main() { var g gramo = 1000 var o onza // Debemos convertir 'gramo' en el tipo definido 'onza' o = onza(g) * 0.035274 fmt.Print("%g gramos son %.2f onzas\n", g, o) } Los tipos definidos heredan las operaciones de los tipos en los que se basan, pero no sus métodos. Las razones para definir nuevos tipos: * Permite declarar nuevos métodos * Seguridad de tipos * Legibilidad ===== Variables ===== Una variable es un contenedor donde se guarda alguna información. El nombre de la variable nos permite acceder al valor que contiene. Go es un lenguaje de **tipos estáticos**, es decir, cada variable tiene un tipo y no puede ser cambiado. Si decidimos que una variable almacena un número entero, no podremos usarla para almacenar otro tipo de dato. ==== Declaración ==== Antes de poder usar una variable, hay que declararla, es decir, darle un nombre: var nombre int * ''var'': abreviatura de //variable// que indica que vamos a declarar una variable * ''nombre'' es el nombre de la variable, su identificador. * ''int'': tipo de dato que contendrá la variable. Las variables deben comenzar siempre por una letra o un guión bajo (''_''). Las variables declaradas deben ser usadas o Go se quejará. Las buenas prácticas de Go indican que la nomenclatura de las variables debe ser camelCase **Declaración múltiple**: var ( speed int heat float64 off bool brand string ) Se pueden declarar también agrupándolas por tipo de dato: var speed, velocity int Tras declarar una variable, Go inicializa automáticamente al valor cero que corresponda: * int: ''0'' * float64: ''0.0'' * string: ''%%""%%'' * bool: ''false'' ==== Inicialización ==== Declaramos una variable y le damos un valor inicial: var seguro bool = true Go utiliza inferencia de tipos para deducir el tipo de la variable según el valor, de tal manera que podríamos ahorrarnos indicar el tipo: var seguro = true La **declaración corta** nos ahorra aún más código: seguro := true La declaración breve permite también la declaración múltiple: seguridad, velocidad := true, 50 * ''seguridad'': se autodeclara como ''bool'' y se asigna a ''true'' * ''velocidad'': se autodeclara como ''int'' y se asigna a 50 La declaración abreviada no se puede usar fuera de las funciones. A nivel de paquete las declaraciones deben empezar con las palabras reservadas (''package'', ''var'', ''func'') ==== Asignación ==== Una vez declarada, podemos cambiar el valor de una variable mientras sea el mismo tipo de dato. El operador de asignación es el igual (''=''): // Se inicializa a 0 var velocidad int // Cambiamos el valor de la variable velocidad = 100 Para hacer una **asignación múltiple**: var velocidad int var seguro bool velocidad, seguro = 100, true Podemos asignar valores desde una función: package main import ( "fmt" "path" ) func main() { var dir, file string dir, file = path.Split("ruta/fichero.ext") fmt.Println("dir: ", dir) fmt.Println("file: ", file) } La función ''Split()'' devuelve dos valores y los asignamos a sendas variables. Si una función devuelve más de un valor y solo nos interesa uno de ellos, podemos utilizar el identificador vacío para descartarlo: (...) func main() { var file string // Solo nos interesa el nombre del fichero _, file = path.Split("ruta/fichero.ext") fmt.Println("file: ", file) Versión resumida: ...) func main() { // Solo nos interesa el nombre del fichero _, file := path.Split("ruta/fichero.ext") fmt.Println("file: ", file) ==== Nomenclatura ==== El nombrado de variables, constantes... es importante porque tiene que ver con la legibilidad lo que lo hace importante para el mantenimiento de un programa. Abreviaturas populares en Go: var s string // string var i int // índice var num int // número var msg string // mensaje var v string // valor var val string // valor var fv string // valor de bandera var err error // valor de error var args []string // argumentos var seen bool // se ha visto? var parsed bool // se ha 'parseado' correctamente? var buf []byte // buffer var off int // offset var op int // operacion var opRead int // operación de lectura var l int // logitud var n int // número o número de var m int // otro número var c int // capacidad var c int // caracter var a int // array var r rune // runa var sep string // separador ===== Constantes ===== Las constantes no cambian su valor. const nombre_constante = valor Ejemplo: (...) func main() { // Definimos una constante const metros int = 100 cm := 100 m := cm / metros fmt.Println("%dcm son $dm\n", cm, m) } No es necesario declarar el tipo de las constantes, Go lo infiere de acuerdo al valor que le asignemos. **Declaración múltiple** de constantes: const min, max int = 1, 100 Otra forma: const ( min int = 1 max int = 100 ) Las constantes toman el tipo de la anterior así como su valor si no se especifica: const ( min int = 1 max ) fmt.Println(min, max) Devolverá: 1 1 ==== iota ==== Generador de constantes integrado en el lenguaje Go. Genera todos los números de forma incremental. func main() { const ( lunes = iota martes miercoles jueves viernes sabado domingo ) fmt.Println(lunes, martes, miercoles, jueves, sabado, domingo) } Al ejecutarlo, imprimirá por pantalla: 0 1 2 3 4 5 6 **iota** comienza en 0 * https://golang.org/ref/spec#Iota ===== Operadores ===== ==== Aritméticos ==== 8 * 2 8 y 2 son operandos y * es el operador, el que indica la operación a realizar. ^ Nombre ^ Operador ^ Expresión ^ Resultado ^ | Negación | - | -(-2) | 2 | | Producto | * | 8 * -4.0 | -32.0 | | Cociente | / | -4 / 2 | -2 | | Módulo | % | 5 % 2 | 1 | | Suma | + | 1 + 2.5 | 3.5 | | Resta | - | 2 - 3 | -1 | Si alguno de los operandos es decimal, el resultado será decimal. ==== Comparación ==== ^ Nombre ^ Operador ^ Expresión ^ Resultado ^ | Igualdad | ''=='' | 3 == 3 | true | | No igualdad | ''!='' | 2 != 3 | true | | Menor | ''<'' | 3 < 2.5 | false | | Menor o igual | ''%%<=%%'' | 4.0 <= 4 | true | | Mayor | ''>'' | 3 > 2.5 | true | | Mayor o igual | ''>='' | 4.0 >= 4 | true | ==== Lógicos ==== ^ Nombre ^ Operador ^ Expresión ^ Resultado ^ | AND | && | true && false | true | | OR | ''||'' | 2 != 3 | true | | NOT | ''!'' | ''!false'' | true | Para la operación ''AND'' Go utiliza lo que se conoce como corto-circuito: solo será cierto si todos los operandos son ciertos. Para la operación lógica ''OR'' solo devuelve falso si todos los operandos son falsos. ==== Asignación ==== El igual (''='') se usa para la asignación de valores a variables. Para hacer alguna operación sobre una variable y asignarle el nuevo valor, se pueden utilizar las **operaciones de asignación**: area := 20 area -= 10 // Reduce el área en 10 area += 10 // Aumenta el área en 10 area *= 2 // Duplica el área area /= 2 // Divide el área ==== Precedencia ===== La precedencia determina el orden de las operaciones. Las operaciones se evalúan de izquierda a derecha y con la siguiente prioridad: ^ Precedencia ^ Operador ^ | 5 | * / % ''<<'' ''>>'' ''&'' ''&^'' | | 4 | + - ''|'' ''^'' | | 3 | == != ''<'' ''%%<=%%'' ''>'' ''>='' | | 2 | ''&&'' | | 1 | ''||'' | * https://golang.org/ref/spec#Operator_precedence ==== Cadenas ==== Para **concatenar** (combinar) cadenas se utiliza el operador +: (...) nombre, apellido := "Carl", "Sagan" fmt.Println(nombre + " " + apellido) ===== Conversión de tipos ===== Go permite la conversión de tipos, también llamada casting: tipo(valor) Ejemplos: velocidad := 100 // int fuerza := 2.5 // float64 velocidad = velocidad * fuerza // error, el resultado sería float64 Realizamos una conversión: velocidad = velocidad * int(fuerza) // funciona porque convertimos a entero Las conversiones son destructivas. Por ejemplo, si tenemos el valor 2.5 y hacemos ''int(2.5)'', el resultado será ''2'', perdiendo la parte decimal. ===== Funciones ===== Bloques de código reutilizables. Para definir una función se utiliza la palabra reservada ''func'': func suma(a int, b int) int { return a + b } ===== Documentación ===== ==== Local ==== Si queremos ver la documentación de cierta función de un determinado paquete: go doc -src fmt Println * ''-src'': opción para indicar que queremos ver el código * ''fmt'': paquete que contiene la función que nos interesa. * ''Println'': función de la que queremos obtener información. Ejemplo de salida: package fmt // import "fmt" // Println formats using the default formats for its operands and writes to standard output. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) } Si solo queremos ver la documentación: go doc fmt Println Ejemplo de salida: package fmt // import "fmt" func Println(a ...interface{}) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered. ===== Paquetes ===== Un paquete es una forma de agrupar funciones. * [[https://golang.org/pkg/|Documentación de los paquetes estándar]] En Go todo depende de paquetes. Los paquetes están alojados en una estructura de directorios. Si van a estar en un repositorio externo (Github, Gitlab...), también hay que indicarlo on ''go mod'' Todos los ficheros que pertenezcan a un paquete deben estar en el mismo directorio. package main La cláusula ''package'' solo puede aparecer una vez en el código, ya que un fichero solo puede pertenecer a un único paquete. Hay dos clases de paquetes en Go: * Paquetes ejecutables: contienen ''func main()''. Se crean para ser **ejecutados**. * Paquetes de biblioteca: solo se pueden **importar**. Se crean para poder reutilizarlos. ^ Biblioteca ^ Ejecutable ^ | Creados para reutilizar | Creados para ejecutarse | | No ejecutable | Ejecutable | | Se pueden importar | No se pueden importar | | Pueden tener cualquier nombre | El nombre debe ser ''main'' | | No contienen ''func main'' | Contienen ''func main'' | ==== Paquetes de biblioteca ==== Una buena práctica es llamar a nuestro paquete de la misma manera que el nombre del directorio. Por ejemplo, creamos un directorio llamado ''printer'' y dentro de él un fichero llamado ''printer.go'': package printer import "fmt" fun hello() { fmt.Println("unexported hello") } Un paquete de biblioteca no puede ser ejecutado y no tiene que compilarse. Puede ser importado directamente. Si queremos instalarlo, utilizaríamos el comando ''go install''. Esto haría que se crease un directorio dentro de ''$GOPATH/pkg/arquitectura/ruta_proyecto/'' con el contenido: * ''printer.a'': paquete ya compilado y archivado (ocupa menos espacio) Las funciones/métodos de las bibliotecas que se pueden exportar son los que empiezan por mayúscula. Lo mismo sucede para variables y constantes. Creamos un fichero que usará el paquete creado anteriormente. Por convención, el ejecutable debería ir en la carpeta ''cmd'' dentro de nuestro proyecto (en este caso ''printer'': // main.go package main import "proyecto/printer" func main() { printer.hello() } Si intentamos compilarlo o ejecutarlo, mostrará un error porque ''hello()'' no se puede exportar. Para solucionarlo, tendríamos que ir al fichero ''printer.go'' y hacer el cambio en el nombre de la función: package printer import "fmt" func Hello() { fmt.Println("unexported hello") } Entonces podremos llamar a la función ''Hello()'' desde nuestro ''main.go'': // main.go package main import "proyecto/printer" func main() { printer.hello() } ===== Ámbito / scope ===== Visibilidad de quién puede ver qué y hacer qué. ==== Variables ==== Cuando hacemos una declaración, esta debe ser única, no puede aparecer otra vez dentro del mismo ámbito. Por ejemplo, no podemos tener dos funciones ''main'' dentro del paquete ''main''. package main // Solo será visible en este fichero import "fmt" const ok = true func main() { // La siguiente variable solo será visible dentro de este bloque // Fuera del bloque de esta función, nada en el programa podrá // acceder a esta variable var hello = "Hello!" fmt.Println(hello, ok) } package main import "fmt" func nope() { const ok = true var hello = "Hello!" } func main() { // Producirá un error porque la función *nope* está fuera // del ámbito/alcance/scope de la función *main* fmt.Println(hello, ok) } ==== Paquetes ==== Los nombres solo pertenecen al paquete en el que son declarados: package main import "fmt" // La función *main* solo es visible a través del paquete *main* func main() { fmt.Println("Hola!") } Si tenemos este otro fichero dentro del paquete ''main'': package main import "fmt" func bye() fmt.Println("Bye!") } Podríamos llamar a la función ''bye()'' en el otro fichero porque ambos pertenecen al paquete ''main'': package main import "fmt" func main() { fmt.Println("Hola!") bye() } ===== Importación ===== La importación es incluir código externo en nuestro código. Es como si declarásemos lo que hay dentro de los ficheros del paquete en nuestro propio fichero de Go. El paquete [[https://golang.org/pkg/fmt/?m=all|fmt]] está formado por los ficheros: * ''doc.go'' * ''errors.go'' * ''format.go'' * ''print.go'' * ''scan.go'' Cuando importamos ''fmt'' estamos incluyendo todos esos ficheros en el nuestro: // mi-fichero.go import "fmt" CODIGO Para importar más de un paquete, podemos hacerlo de dos maneras: import "fmt" import "runtime" O en la misma sentencia: import ( "fmt", "runtime" ) Si queremos ahorrarnos escribir el nombre del paquete a la hora de usar alguna de sus funciones/métodos, lo importamos con el punto: import . "fmt" func main() { // fmt.Println Println("Hola") } Si queremos temporalmente que no se importe algún paquete (por pruebas, por ejemplo), utilizaremos el identificador en blanco (''_''): import ( "fmt" _ "log" ) La lista de los paquetes a importar debe estar ordenada alfabéticamente o Go lo considerará como un error ==== Renombrar ==== Podemos importar múltiples paquetes, pero todos deben ser únicos: import "fmt" import "fmt" Sería incorrecto, pero si hacemos: import "fmt" import f "fmt" Podríamos usar el paquete ''fmt'' mediante ''fmt'' o ''f'': package main import "fmt" import f "fmt" func main() { fmt.Println("Hello!") f.Println("There!") } ===== Conversión de tipos ===== El paquete ''strconv'' dispone de funciones para la conversión de diferentes tipos de datos Convertir de entero a string: miCadena = strconv.Itoa(123) Convertir de string a entero: miEntero, _ = strconv.Atoi("123") Convertir a bool: miBool, _ = strconv.ParseBool("true") Convertir a decimal: miDecimal, _ = strconv.ParseFloat(123) Convertir a entero: miEntero, _ = strconv.ParseInt("123") ===== Entrada por teclado ===== Para que el programa tome argumentos en línea de comandos usaremos el paquete ''os'' que se encarga de ofrecer funcionalidades de sistema operativo. Dentro de ese paquete hay una variable llamada ''Args'': var Args []string // trozos de strings Cuando ejecutamos un programa Go, los argumentos se meten en esa variable automáticamente. go run main.go hi yo Para acceder a cada parámetro lo hacemos mediante la posición que ocupa en la variable ''Args'': * ''Args[0]'': ruta al programa * ''Args[1]'': primer argumento * ''Args[n]'': argumento que ocupa la posición ''n''. package main import( "fmt" "os" ) func main() { fmt.Printf("%#v\n", os.Args) fmt.Println("Path: ", os.Args[0]) fmt.Println("Primer argumento: ", os.Args[1]) fmt.Println("Segundo argumento: ", os.Args[2]) fmt.Println("Path: ", os.Args[0]) fmt.Println("Número de elementos dentro de os.Args: ", len(os.Args)) } Programa que tome el nombre desde línea de comandos y salude: (...) func main() { var nombre string nombre = os.Args[1] nombre_dos, edad := "gandalf", 2019 fmt.Println("Hola, gran", nombre, "!") fmt.Println("Mi nombre es ", nombre_dos) fmt.Println("Mi edad es ", edad) fmt.Println("Por cierto, ¡puedes pasar!") } Lo compilamos: go build -o saludo Lo ejecutamos: ./saludo pepito La salida: Hola, gran pepito! Mi nombre es gandalf Mi edad es 2019 Por cierto, ¡puedes pasar! Los valores que se guardan en ''Args'' son strings, así que si queremos realizar operaciones aritméticas tendremos que convertirlos en números. Para ello echamos mano del paquete ''strconv'' y su función ''ParseFloat()'': // Programa que convierte pies a metros package main import ( "os" "fmt" "strconv" ) func main() { arg := os.Args[1] // La función strconv devuelve también un error, pero // no nos interesa, así que lo asignamos al identificador // vacío. pies, _ = strconv.ParseFloat(arg, 64) metros := pies * 0.3048 fmt.Print("%f pies ess %f metros.\n", pies, metros } ===== Salida por pantalla ===== ==== fmt.Printf ==== Imprime por pantalla con el formato establecido. (...) func main() { var marca = "Google" fmt.Printf("%q\n", marca) } El primer argumento de la función indica el tipo de dato que vamos a querer imprimir y donde, y el segundo argumento es el dato a imprimir * ''s'': string * ''c'': caracteres * ''b'': bits * ''f'': decimales * ''g'': decimales sin las partes innecesarias * ''q'': string * ''d'': enteros * ''t'': bool. * ''T'': tipo de variable * ''v'': imprime cualquier valor Por ejemplo, para imprimir el siguiente texto: total: 1234 éxito: 1200 / 34 Lo haríamos: (...) total := 1234 exito := 1200 error := 34 fmt.Printf("total: %d éxito: %d / %d\n", total, exito, error) En caso de los valores decimales, en ''Printf'' podemos indicar la **precisión** con la que se muestran: (...) pi := 3.1415 fmt.Printf("Pi vale: %f\n", pi) fmt.Printf("Pi vale: %.2f\n", pi) Salida: Pi vale: 3.1415 Pi vale: 3.14 ==== Secuencias de escape ==== * ''\n'': nueva línea * ''\\'': \ * ''\%%"%%'': " ==== Bytes, runas y cadenas ==== // Imprime una tabla de caracteres y sus valores en decimal package main import ( "fmt" "strings" ) func main() { start, stop := 'A', 'Z' fmt.Printf("%-10s %-10s\n%s\n", "literal", "dec", "hex", "encoded", strings.Repeat("-", 45)) for n := start; n <= stop; n++ { fmt.Printf("%-10c %-10[1]d %-10[1]x % -12x\n", n, string(n)) } } ===== Cadenas ===== ==== Tamaño ==== len(): devuelve el tamaño de una cadena. Los caracteres Unicode pueden tener de 1 a 4 bytes, así que la función ''len()'' devuelve el número de bytes, no de caracteres: (...) nombre := "çeszc" fmt.Println(len(nombre)) // 6 y no 5 porque 'ç' son 2 bytes Si queremos saber el número de caracteres de una cadena hay que usar el paquete ''utf8'': import ( "fmt" "unicode/utf8" ) func main() { nombre := "çeszc" fmt.Println(utf8.RuneCountInString(nombre)) // 5 } Una runa representa un caracter inglés y no inglés. Las runas también son llamados //codepoints// ==== Manipulación ==== * https://golang.org/pkg/strings/ Repetir cadenas: package main import ( "fmt" "os" "strings" ) func main() { msg := os.Args[1] l := len(msg) // 'Repeat' permite repetir una cadena X número de veces s := msg + strings.Repeat("!", l) // 'ToUpper' convierte en mayúsculas un string s = strings.ToUpper(s) fmt.Println(s) } ===== Arrays ===== Colección de elementos. Tiene un tamaño fijo. Los //slices// son como los arrays, pero su tamaño es variable. var libros [4]string Si queremos declarar e inicializar: var libros [4]string{1, 3, 10, 23} Para acceder a cada elemento del array lo hacemos por la posición que ocupa. Empezando por el 0: libros[0] libros[1] libros[2] for i := 0, i < len(libros); i++ { fmt.Println(libros[i]) } // Con un for range: for i:= range libros { fmt.Println(libros[i]) } ==== Arrays multidimensionales ==== Array que contiene otros arrays. Podemos imaginarlos como tablas o matrices. [3]int{5, 6, 1} [4]int{9, 8, 4} // Multidimensional [2][3]int{ [3]int{5, 6, 1} [4]int{9, 8, 4} } // Go permite omitir el tipo de los arrays internos: [2][3]int{ {5, 6, 1} {9, 8, 4} } El primer índice ''[2]'' indica el tamaño del array multidimensional Se pueden crear arrays indexados: notas := [3]float65{ 0: 1.5 1: 10.0 2: 3 } Go inicializa a 0 los elementos no inicializados: notas := [...]float65{ 5: 1.5 } Go creará un array de 6 elementos con valor 0. El sexto elemento valdrá 1.5 La notación ''[...]'' permite que no especifiquemos implicitamente la longitud del array. ===== Slices ===== Colección de elementos de un mismo tipo. El tamaño de los slices es variable. Las ''slices'' no contiene ningún elemento sino que son una vista de un array, es decir, por debajo tienen un array. Lo más habitual es trabajar con slices y no con arrays, salvo que de antemano se conozca el tamaño de los datos. En tiempo de ejecución, se pueden añadir o quitar elementos a un slice. var nums []int A la hora de declarar un slice no definimos su tamaño, así que no tiene un tamaño fijo en tiempo de compilación lo cual permite que modamos modificarlo. A diferencia de los arrays, el valor cero de un slice es ''nill'' var nums []int // nums es nil; len(nums) es 0 Para crear e inicializar un slice se utiliza la función interna **make**: // Creamos un slice de enteros y el tamaño que queremos que tenga listado := make([]string, 3) Internamente, esa instrucción con ''make'' se creará un array (espacio en memoria) y un slice que es una represetanción de ese array. ==== Añadir elementos ==== Se utiliza la función ''append()'' append(slice, newElement[,newElement2,...] ) Ejemplos: nums := []int{1, 2, 3} nums = append(nums, 4) Si queremos añadir un slice a otro, utilizamos el operador elipsis (''...''): nums := []int{1, 2, 3} tens := []int{12, 13} nums = append(nums, tens...) ==== Quitar elementos ==== Se utilizan expresiones msg := []byte{'h', 'o', 'l', 'a'} msg[0:2] // corta el slice desde la posición 0 hasta la 2 (no la incluye) -> hol msg[:3] // ho ==== Slices multidimensionales ==== Un slice que contiene otros: gastos := [][]int { {200, 100} {500} {50, 25, 75}, } A diferencia de los arrays multidimensionales, cada slice interno puede tener una longitud diferente. ==== Maps ==== Pares de clave-valor (como los diccionarios de Python). Si queremos crear estructuras más complejas con mapas (y otros tipos de datos), debemos usar **structs** Tenemos dos formas de crearlos: miMapa := map[string]int{} Utilizando ''make'': b := make(map[string]float32) b["mahou"] = 0.59 b["buckler"] = 0.60 Podemos inicializarlos: misMascotas := map[string]int{ "perro": 2, "gato": 1, "planta": 5, } === Acceder a los valores === Podemos acceder a los valores a través de sus claves: fmt.Println(misMascotas["perro"]) === Eliminar elementos === delete(b, "buckler") Si no queremos eliminar la clave sino modificar un valor: misMascotas["perro"] = misMascotas["perro"] - 1 // misMascotas["perro"] -= 1 ===== Control de flujo ===== ==== Condicionales ==== === If === if { } La condición siempre debe evaluarse (reducirse) a un valor lógico (true o false) Salvo que sea estrictamente necesario, en Go no hay que poner paréntesis en la condición del condicional if { } else if { } else { } === Switch === switch { case : case : default: } La cláusula ''default'' se ejecuta cuando la expresión del switch no está recogida en ninguno de los ''case''. En Go es posible añadir varias condiciones en los ''case'': switch ciudad { case "París", "Lyon": fmt.Println("Francia") case "Tokio": fmt.Println("Japón") } ==== Bucles ==== En Go solo hay una sentencia para bucles (no hay ''while'' por ejemplo) === for === for var := valor; ; var++ Ejemplo: (...) for i := ; i <= 1000; i++ { sum += i } fmt.Println(sum) Para tener el comportamiento de una sentencia while, usamos ''for'' de esta manera: for { } Para romper un bucle se usa la palabra reservada ''break'': for { if i > 5 { break } sum += i i++ } Si queremos continuar un bucle, usamos la palabra reservada ''continue'': for { if i > 10 { break } // Comprobar si el número es par o no if i % 2 != 0 { i++ continue } sum += i fmt.Println(sum) i++ } En el código anterior, cuando se llega a ''continue'', Go va al comienzo del bucle, sin ejecutar lo que viniese después de ''continue''. === for range === Permite iterar fácilmente en arrays/slices. import ( "fmt" "strings" ) func main() { palabras := strings. Fields("En un lugar de la Mancha") for i, v := range palabras { } * ''i'': índice * ''v'': valor ===== Structs ===== Tipo compuesto que agrupa diferentes tipos de datos en un único tipo. Structs nos permite representar conceptos declarando una especie de plantilla para un conjunto de **datos relacionados**. Los structs de Go son como las **clases** en la Programación Orientada a Objetos. Por ejemplo, podríamos crear un struct para personas, películas... Para el struct de personas, se compondría de los campos Nombre, Apellidos, Edad. ==== Creación ==== type NombreStruct struct { Campo1 string Campo2 string Campo3 bool } Ejemplo: type VideoGame struct { Title string Genre string Published bool } // Creamos un valor struct utilizando el creado anteriormente: pacman := VideoGame { Title: "Pac-Man", Genre: "Arcade Game", Published: true } Podemos acceder a los campos de un struct y/o modificar sus valores mediante ''nombreStruct.campoStruct'': (...) type person struct { name, lastname string age int } // Declaramos un nuevo struct // Recordemos que al no inicializarlo, Go lo // hace por nosotros. var picasso person // Modificamos los valores de sus campos picasso.name = "Pablo" picass.lastname = "Picasso" picasso.age = 91 fmt.Println("\nPicasso: %v\n", picasso) Un struct puede contener otro struct: type song struct { title, artist string } type playlist struct { genre string songs []song } songs := []song{ {title: "Wonderwall", artist: "Oasis"}, {title: "Super Sonic", artist: "Oasis"}, } rock := playlist{genre: "indie rock", songs: songs} Volviendo a la comparación con POO, en Go no existe la herencia sino más bien la **composición**: un struct puede componerse de otros structs. Es lo que en Go llaman //embedding//. Los **campos anónimos** nos ahorran código a la hora de usar structs dentro de structs. Por ejemplo: type text struct { title string words int } type book struct { text text isbn string } /* // Lo de arriba se podría escribir: type book struct { text isbn string } */ moby := book { text: text {title: "Moby Dick", words: 206052, isbn: "102030", } Con los campos anónimos, Go toma el nombre del tipo del struct al que hacen referencia, ''text'' en este caso. Otro ejemplo completo: package main import "fmt" type Mascota struct { Edad int, Tipo string } func main () { misMascotas := map[string]Mascota{ "Bobby": Pet{ Edad: 12, Tipo: "Perro" }, "Slow": Pet{ Edad: 20, Tipo: "Tortuga" }, } fmt.Println(misMascotas) } ==== Codificación JSON ==== Los motivos por los que se usa JSON para el intercambio de datos: * Legible (por los humanos) * Los ordenadores lo pueden entender fácilmente. * Ampliamente usado y soportado type permissions map[string]bool type user struct { Name string Password string Permissions permissions } func main() { users := []user{ {"god", "42", permissions{"admin", true}}, {"devil", "666", permissions{"write", }}, } out, err := json.MarshallIndent(users, "", "\t") if err != nil { fmt.Println(err) return } fmt.Println(string(out)) } Si queremos cambiar el nombre de las claves de JSON: type user struct { Name string `json: "username"` Password string `json: "-"` Permissions permissions `json: "permis"` } ===== Interface ===== Tipo de dato no concreto. Los tipos **structs** indican qué es algo mientras que los tipos de datos **interface** indican qué puede hacer. Completar la explicación con ejemplos claros ===== Goroutines y channels ===== Mucha de la programación que se hace es secuencial. Las rutinas de Go y los canales permiten realizar operaciones mientras otro código está en ejecución. Son como los hilos. Los canales se comunican entre las rutinas de go. Las rutinas necesitan comunicarse para saber cuándo finalizan y saber si deben detenerse o continuar. Un ejemplo de uso sería una rutina para leer varios ficheros. En lugar de abrir uno, leer todo el contenido, cerrar el fichero y proceder con el siguiente, se puede hacer todo esto al mismo tiempo. Los canales pueden avisar cuándo se ha terminado de leer todos los ficheros. Otro ejemplo sería contar la duración de la ejecución de una función. Se podría lanzar una goroutine con un contador de tiempo para que guarde la duración de la función. Estos dos procesos se ejecutan a la vez y uno de ellos está esperando a que el otro termine. func goRoutinesAndChannels() { files := []string{ "file1.csv", "file2.csv", "file3.csv", } rowChannel := make(chan int, len(files)) for _, file := range files { file := file // this is stupid, but we have to because closures. big go pitfall! go func() { numRows, err := countRows(file) if err != nil { panic("aaaaah!") } rowChannel <- numRows }() } var totalRows int for range files { numRows := <- rowChannel totalRows += numRows } return totalRows } ===== Punteros ===== ===== Manejo de errores ===== Es necesario un manejo de los posibles errores: * Problemas con la red * Problemas con el acceso a ficheros * Entrada del usuario ''nil'' es un valor que indica que algo no tiene valor, que aún no ha sido inicializado. La función strconv.Atoi se define como: func Atoi(s string) (int, error) Así que a veces devuelve un error. Podemos usar 'nil' para el manejo de errores: package main import ( "os" "fmt" "strconv" ) func main() { n, err := strconv.Atoi(os.Args[1]) fmt.Println("Número convertido: ", n) fmt.Println("Error: ", err) } Si lanzamos el programa con un valor correcto: go run main.go 42 Salida: Número convertido: 42 Error: Cuando lanzamos el programa con un valor incorrecto: go run main.go hola Salida: Número convertido: 0 Error: strconv.Atoi: parsing "hola": invalid syntax Modificación del programa para controlar errores: package main import ( "os" "fmt" "strconv" ) func main() { n, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Println("ERROR: ", err) return // finaliza la ejecución } fmt.Println("Número convertido: ", n) fmt.Println("Error: ", err) } ===== Bibliotecas ===== Las más usadas: * Testify (testing) * Logrus (log): como un estándar para logs. La estándar no es suficiente. * pkg/errors (errors) * cobra (cli): el cliente de Docker está hecho usando este framework * godog (testing) ===== Frameworks ===== ==== Utilidades para APIs ==== * **Gorilla mux**: muy usada para montar APIs. Resuelve todo el tema de routing * Negroni * google/jsonapi * grpc * echo ==== Frameworks para Web-Scraping ==== * Pholcus (Chino) * go_spider * ants-go * colly * Dataflow Kit ===== Aplicaciones hechas en Go ===== * [[https://www.docker.com/|Docker]] * [[https://github.com/muesli/beehive|Beehive]] * [[https://github.com/go-shiori/shiori|Shiori]] ===== Recursos ===== * [[https://golang.org/ref/spec|The Go Programming Language Specification]] * [[https://tour.golang.org/|A Tour of Go]] * [[https://golang.org/doc/effective_go|Effective Go]] * [[https://golang.org/pkg/|Documentación sobre paquetes]] * [[https://go.dev/blog/|The Go blog]] * [[https://gobyexample.com/|Go by Example]] * [[https://www.youtube.com/watch?v=Yo2xmii7TbA|Learn to Code Go in 17 minutes | Golang Programming Crash Course]] (YouTube) * [[https://play.golang.org/|The Go Playground]]: prueba o comparte código desde el navegador. * [[https://friendsofgo.tech/|Friends of Go]] (en español) * https://forum.golangbridge.org: foro oficial * [[https://github.com/inancgumus/learngo|1000+ Hand-Crafted Go Examples, Exercises, and Quizzes]] * [[https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/|Build Web Application with Golang]] * [[http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/|50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs]] * [[https://gophercises.com/|Gophercises: Coding exercises for budding gophers]] (requiere crearse una cuenta)