You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1784 lines
49 KiB
1784 lines
49 KiB
10 months ago
|
---
|
||
|
weight: 4
|
||
|
title: "Apuntes del lenguaje de programación Go"
|
||
|
date: 2021-05-18T09:55:41+0200
|
||
|
draft: false
|
||
|
summary: "Apuntes de Go"
|
||
|
categories:
|
||
|
- notes
|
||
|
tags:
|
||
|
- golang
|
||
|
- programacion
|
||
|
---
|
||
|
|
||
|
{{< admonition type=warning title="Work in progress" open=true >}}
|
||
|
|
||
|
Apuntes incompletos del lenguaje de programación Go
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
|
||
|
|
||
|
## Referencias
|
||
|
|
||
|
- [Documentación Oficial](https://godoc.org/)
|
||
|
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments)
|
||
|
- [Golang 101 hacks](https://nanxiao.gitbooks.io/golang-101-hacks/content/)
|
||
|
- [Un video curso de Traversy Media](http://www.tonvid.com/info.php?video_id=SqrbIlUwR0U)
|
||
|
- [Un video curso de 7 horas de freeCodeCamp](http://www.tonvid.com/info.php?video_id=YS4e4q9oBaU)
|
||
|
- [Go Workshop Book](https://subscription.packtpub.com/book/ppprogramming/9781838647940)
|
||
|
- [Awesome Go](https://awesome-go.com/)
|
||
|
- [Golang by example: All design patterns in Go](https://golangbyexample.com/all-design-patterns-golang/)
|
||
|
- [Design Patters](https://refactoring.guru/design-patterns/go) (lo mismo pero con más dibujitos)
|
||
|
- [Go Modules](https://go.dev/blog/using-go-modules)
|
||
|
- [Build a web app in Golang](https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/01.1.html)
|
||
|
|
||
|
### Books
|
||
|
- Esencia del lenguaje Go: ["The Go Programming Language" Addison-Wesley](https://www.amazon.es/dp/0134190440/ref=cm_sw_r_cp_apa_fabc_7-7WFbSZRP6WG)
|
||
|
- Panorámica del go-stdlib: ["Mastering Go" Packt Publishing; 2nd Revised edition](https://www.amazon.es/dp/1838559337/ref=cm_sw_r_u_apa_i_DB8VXX0DNT6NPRMRAMHJ)
|
||
|
|
||
|
## Instalación
|
||
|
|
||
|
- Bajamos el paquete con la última versión desde la [página oficial](https://go.dev/dl/).
|
||
|
- Y lo descomprimimos como root en `/usr/local/`
|
||
|
|
||
|
```bash
|
||
|
cd ~/tmp/go
|
||
|
wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz
|
||
|
sudo cp /usr/local/go /usr/local/go.bkp
|
||
|
sudo tar -C /usr/local -xvzf go1.17.4.linux-amd64.tar.gz
|
||
|
```
|
||
|
|
||
|
Después de instalar es importante ajustar las rutas en `.profile` (ver la explicación en el siguiente punto):
|
||
|
|
||
|
```bash
|
||
|
# golang
|
||
|
if [ -d "/usr/local/go" ] ; then
|
||
|
# export GOROOT="/usr/local/go"
|
||
|
# PATH="$PATH:$GOROOT/bin"
|
||
|
fi
|
||
|
if [ -d "$HOME/go" ] ; then
|
||
|
export GOPATH="$HOME/go"
|
||
|
PATH="$PATH:$GOPATH/bin"
|
||
|
fi
|
||
|
|
||
|
export PATH
|
||
|
```
|
||
|
|
||
|
{{< admonition type=danger title="GOROOT" open=true >}}
|
||
|
|
||
|
Varios autores ([un ejemplo](https://dave.cheney.net/2013/06/14/you-dont-need-to-set-goroot-really)) recomiendan **NO** establecer el valor de `GOROOT` cuando usamos la localización estándar. Así que he comentado esa parte en mi fichero `~/.profile`
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
|
||
|
|
||
|
### Configuración del entorno de Go
|
||
|
|
||
|
{{< admonition type=info title="Referencias" open=false >}}
|
||
|
- [Golang Doc: gopath_code](https://golang.org/doc/gopath_code)
|
||
|
- [Golang Enviroment Configuration](https://www.metaltoad.com/blog/golang-environment-configuration)
|
||
|
- [Build a Web Application with Golang](https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/01.0.html)
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
Para dejar configurado el entorno de trabajo en _Go_ conviene conocer las siguientes variables de entorno (aunque hay [más](https://golang.org/doc/)):
|
||
|
|
||
|
**GOROOT**
|
||
|
|
||
|
: Es el directorio donde se localiza la biblioteca estándar de _Go_. Por defecto _Go_ asume la ruta `/usr/local/go`, así que estrictamente hablando **NO** es necesario establecer esta variable si has instalado en `/usr/local`. (Con [retoques de esta variable](https://golang.org/doc/manage-install) es posible instalar varias versiones de _Go_)
|
||
|
|
||
|
**GOBIN**
|
||
|
|
||
|
: Es el directorio donde quedan instalados los binarios de _Go_ cuando ejecutamos un comando `go install ...`. Solo deberías tocar esta variable si no quieres instalar los binarios en la localización por defecto: `$GOPATH/bin`.
|
||
|
|
||
|
**GOOS**
|
||
|
|
||
|
: especifica el sistema operativo, es opcional. En mi caso tiene valor `linux`
|
||
|
|
||
|
**GOARCH**
|
||
|
|
||
|
: especifica la arquitectura del procesador de nuestra máquina, también es opcional, en mi caso es `amd64`
|
||
|
|
||
|
**GOPATH**
|
||
|
|
||
|
: La ruta a nuestro directorio de trabajo (nuestro _workspace_). Esta variable es **obligatoria** y **no tiene valor por defecto**, podeis ponerla en donde tenga sentido para vosotros, por ejemplo `/home/salvari/code/go`
|
||
|
|
||
|
Evidentemente tenemos que dejar las variables de entorno exportadas y añadir al `PATH` de nuestro usuario las que correspondan (ver punto anterior)
|
||
|
|
||
|
{{< admonition type=tip title="Ver el entorno" open=true >}}
|
||
|
|
||
|
Puedes ver el entorno completo de Go con el comando `go env`
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
|
||
|
#### Más de GOPATH
|
||
|
|
||
|
{{< admonition type=warning title="Abandonando GOPATH" open=true >}}
|
||
|
|
||
|
Estamos usando ya go 1.17. Hay planes firmes para abandonar el tratamiento de dependencias via `GOPATH` y centrarse exclusivamente en gestionarlas a través de módulos.
|
||
|
|
||
|
Todo lo que comento aquí de `GOPATH` hay que tratarlo con pinzas.
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
|
||
|
_Go_ espera que en el directorio `$GOPATH` haya tres subdirectorios:
|
||
|
|
||
|
```bash
|
||
|
bin/
|
||
|
pkg/
|
||
|
src/
|
||
|
```
|
||
|
|
||
|
- `bin/` contiene los ejecutables que se generan cuando ejecutamos `go install ...`
|
||
|
- `pkg/` contiene los paquetes instalados en nuestro sistema con `go get ...`
|
||
|
- `src/` **aquí es donde deberíamos alojar nuestro código fuente** (no es obligatorio)
|
||
|
|
||
|
Como hemos dicho en `$GOPATH/src/` es donde tenemos que programar, ahí es donde tiene que estar nuestro código fuente, pero ojo la ruta exacta de nuestros proyectos es función de nuestra plataforma de control de código, y tiene que tener la forma `Source-Control-Platform/User/Repository`.
|
||
|
|
||
|
Por ejemplo podríamos tener los siguientes proyectos en el directorio `src/`:
|
||
|
```bash
|
||
|
src/github.com/salvari/miAppGo
|
||
|
src/gilab.com/salvari/mach5
|
||
|
```
|
||
|
|
||
|
### Instalación de herramientas
|
||
|
|
||
|
#### _golint_ y _godoc_
|
||
|
|
||
|
**Ya no** instalamos con los comandos:
|
||
|
|
||
|
```bash
|
||
|
go get -u golang.org/x/lint/golint
|
||
|
go get -u golang.org/x/tools/cmd/godoc
|
||
|
```
|
||
|
|
||
|
El comando `go get` se usa exclusivamente para añadir dependencias al módulo que estemos creando.
|
||
|
|
||
|
Los binarios que queremos usar en nuestro sistema se instalan con el nuevo estilo de instalación [recomendado](https://go.dev/doc/go-get-install-deprecation) desde go v1.17:
|
||
|
|
||
|
```bash
|
||
|
go install golang.org/x/lint/golint@latest
|
||
|
go install golang.org/x/tools/cmd/godoc@latest
|
||
|
```
|
||
|
|
||
|
Alternativamente podemos instalar **todas** las herramientas de _Go_ con:
|
||
|
|
||
|
```bash
|
||
|
go get -u golang.org/x/tools/...
|
||
|
```
|
||
|
|
||
|
Pero con esta opción me falla la instalación de `gopls`, una herramienta que necesito en mi configuración de _Emacs_
|
||
|
|
||
|
#### _gopls_ para protocolos LSP en editores
|
||
|
|
||
|
Desde un directorio **que no sea el GOPATH**
|
||
|
|
||
|
```bash
|
||
|
# GO111MODULE=on # not needed with 'go install'
|
||
|
go install golang.org/x/tools/gopls@latest
|
||
|
```
|
||
|
|
||
|
#### Herramientas adicionales
|
||
|
|
||
|
- gopkgs
|
||
|
- go-outline
|
||
|
- dlv
|
||
|
- dlv-dap
|
||
|
- staticcheck
|
||
|
|
||
|
```bash
|
||
|
go install github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest
|
||
|
go install github.com/ramya-rao-a/go-outline@latest
|
||
|
go install github.com/go-delve/delve/cmd/dlv@latest
|
||
|
go install github.com/go-delve/delve/cmd/dlv@master
|
||
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||
|
```
|
||
|
|
||
|
### Alias para zsh
|
||
|
|
||
|
```zsh
|
||
|
# golang aliases
|
||
|
alias gob='go build' # Build your code
|
||
|
alias goc='go clean' # Removes object files from package source directories
|
||
|
alias god='go doc' # Prints documentation comments
|
||
|
alias gof='go fmt' # Gofmt formats (aligns and indents) Go programs.
|
||
|
alias gofa='go fmt ./...' # Run go fmt for all packages in current directory, recursively
|
||
|
alias gog='go get' # Downloads packages and then installs them to $GOPATH
|
||
|
alias goi='go install' # Compiles and installs packages to $GOPATH
|
||
|
alias gol='go list' # Lists Go packages
|
||
|
alias gom='go mod' # Access to operations on modules
|
||
|
alias gop='cd $GOPATH' # Takes you to $GOPATH
|
||
|
alias gopb='cd $GOPATH/bin' # Takes you to $GOPATH/bin
|
||
|
alias gops='cd $GOPATH/src' # Takes you to $GOPATH/src
|
||
|
alias gor='go run' # Compiles and runs your code
|
||
|
alias got='go test' # Runs tests
|
||
|
alias gov='go vet' # Vet examines Go source code and reports suspicious constructs
|
||
|
```
|
||
|
|
||
|
## Go Modules
|
||
|
|
||
|
Básicamente un módulo (_Go Module_) es una colección de paquetes (_packages_) almacenados en un arbol de directorios que tiene un fichero `go.mod` en su raiz. El fichero `go.mod` especifica el _module path_ que es la ruta canónica al módulo que se usará para importarlo, y todas las depencias requeridas por el módulo, es decir el conjunto de módulos (otros módulos) que serán necesarios para compilar con éxito este módulo. Cada dependencia se especifica con su correspondiente _module path_ y un número de versión semántica (_semantic version_)
|
||
|
|
||
|
### Crear un módulo
|
||
|
|
||
|
El comando para crear un módulo es: `go mod init`
|
||
|
|
||
|
Antiguamente el compilador distinguía dos formas de funcionamiento, el _go module mode_ y el _gopath mode_. A estas alturas tenemos que usar el _go module mode_ el otro está en desuso. De hecho el compilador ahora funciona en _go module mode_ por defecto.
|
||
|
|
||
|
Si estamos en un directorio por debajo del `$GOPATH` nuestro directorio de proyecto debería tener esta forma:
|
||
|
|
||
|
```bash
|
||
|
$GOPATH/src/example.com/$USER/moduleName
|
||
|
```
|
||
|
|
||
|
En este caso el comando `go mod init` no necesita parámetros, genera un _module path_ basado en la estructura de directorios.
|
||
|
|
||
|
Si estamos trabajando en un directorio fuera del `$GOPATH` tendremos que especificar el _module path_ en el comando:
|
||
|
|
||
|
```bash
|
||
|
go mod init example.com/modulename
|
||
|
```
|
||
|
|
||
|
### Actualizar el fichero `.mod`
|
||
|
|
||
|
Ejecutamos: `go mod tidy`
|
||
|
|
||
|
|
||
|
## TDD (_Test Drived Development_) en Go
|
||
|
|
||
|
{{< admonition type=info title="Referencias" open=true >}}
|
||
|
|
||
|
- [Learn Go with Tests](https://quii.gitbook.io/learn-go-with-tests/) Un buen sitio para empezar
|
||
|
- [An introduction to Testing in Go](https://tube.cthd.icu/watch?v=GlA57dHa5Rg) Un video muy ilustrativo
|
||
|
- [Advanced Testing in Go](https://tube.cthd.icu/watch?v=S1O0XI0scOM) Continuación del anterior
|
||
|
|
||
|
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
TDD es una buena práctica:
|
||
|
- Mejora la calidad del código
|
||
|
- En general hace que escribamos un código más desacoplado (tiendes a aislar más las cosas para facilitar los test)
|
||
|
- Tiene la ventaja obvia de tener todo controlado con test fáciles de repetir
|
||
|
- Bien escritos valen como documentación de bajo nivel
|
||
|
- Previene regresiones (fallos que reaparecen al avanzar en el desarrollo)
|
||
|
- Potencia la arquitectura y diseño modulares
|
||
|
|
||
|
### Tres reglas ###
|
||
|
|
||
|
Se suele hablar del ciclo: ""rojo, verde y refactorizar" (_red, green and refactor_)
|
||
|
|
||
|
- Primero hay que escribir un test que falla
|
||
|
- Hay que programar lo justo para que el test no falle. No se puede escribir más código que el necesario para que el test no falle.
|
||
|
- Se revisa el código para refinarlo (tanto el código producto como el código de los test)
|
||
|
|
||
|
|
||
|
Los pasos que nos proponen en la página de _Learn Go with Test_:
|
||
|
- Escribe un test
|
||
|
- Haz lo necesario para que el compilador pase sin errores
|
||
|
- Ejecuta el test, comprueba que falla y que el mensaje de fallo es significativo
|
||
|
- Escribe el código suficiente para que el test no falle
|
||
|
- Refina el código (_Refactor_)
|
||
|
|
||
|
Nos dicen literalmente:
|
||
|
|
||
|
Este ciclo puede parecer tedioso pero es importante mantenerlo.
|
||
|
|
||
|
Al hacerlo no solo te aseguras de tener test significativos, sino que aseguras un buen diseño del software respaldado por la seguridad que dan los test.
|
||
|
|
||
|
Ver fallar el test es un paso importante por qué permite comprobar el mensaje de error (que debe ser significativo) Como desarrollador puede ser muy difícil trabajar con código cuando los mensajes de fallo de los test no dan una idea clara de que está pasando.
|
||
|
|
||
|
Asegurándote de que los test se ejecutan rápido y estableciendo un entorno de trabajo que facilite escribir los test puedes "entrar en sintonía" y programar de la forma más eficiente posible.
|
||
|
|
||
|
Si no escribes los test, te verás obligado a comprobar el funcionamiento del código ejecutándolo manualmente, eso rompera tu concentracion y a la larga te hara perder bastante mas tiempo que escribir correctamente los test.
|
||
|
|
||
|
### Sintáxis para test
|
||
|
|
||
|
Escribir un test es como escribir una función pero:
|
||
|
- Tiene que estar en un fichero de la forma `xxx_test.go`
|
||
|
- El nombre de la función que implementa el test tiene que empezar por `Test`
|
||
|
- La función que implementa el test tiene que tener un único argumento: `t *testing.T`
|
||
|
- Para poder usar ese tipo de argumento y otras facilidades de testeo es necesario hacer `import "testing"`
|
||
|
|
||
|
|
||
|
### Código independiente es más fácil de testear ###
|
||
|
|
||
|
En el ejemplo _Hello World_ es mejor escribir una funcion que devuelve la cadena con el saludo que una función que escribe el saludo. Por que así somos independientes de la salida que puede ser por pantalla, por una página web, por un mensaje, escrita, etc. etc. Y de paso es más simple de testear.
|
||
|
|
||
|
|
||
|
## Go
|
||
|
|
||
|
`_`
|
||
|
|
||
|
: Es el _black identifier_ podemos usarlo para pasar de variables que
|
||
|
no vamos a usar.
|
||
|
|
||
|
```golang
|
||
|
a = "Una cadena"
|
||
|
for _, r := range a {
|
||
|
fmt.Println(r)
|
||
|
}
|
||
|
|
||
|
```
|
||
|
|
||
|
`make`
|
||
|
|
||
|
: Parece que vale para hacer un _alloc_ de memoria.
|
||
|
|
||
|
```golang
|
||
|
counts := make(map[string]int)
|
||
|
```
|
||
|
|
||
|
`time`
|
||
|
|
||
|
: La especificación de formatos tiene su gracia ¬_¬ [ver referencia](https://golang.org/src/time/format.go)
|
||
|
|
||
|
### Arrays
|
||
|
|
||
|
```go
|
||
|
var myArray1 = [3]int // will be filled with so called
|
||
|
// zero values, for integers: 0
|
||
|
|
||
|
var myArray2 = [5]int{1,2,3,4,5} // number of values between { } can
|
||
|
// not be larger than size (ofc)
|
||
|
|
||
|
var myArray3 = […]int{1,2,3,4} // the compiler will count the
|
||
|
// array elements for you
|
||
|
```
|
||
|
|
||
|
- [Diferencias entre array y slice](https://gist.github.com/josephspurrier/b5713f9a534afe3cfdd2)
|
||
|
- [The Geek Stuff: Golang Array vs Slice](https://www.thegeekstuff.com/2019/03/golang-slice-examples/)
|
||
|
- [A comprehensive guide on array and slices in Go](https://www.sohamkamani.com/golang/arrays-vs-slices/)
|
||
|
- [Go: Array vs Slices Bonanza](https://medium.com/@marty.stepien/arrays-vs-slices-bonanza-in-golang-fa8d32cd2b7c)
|
||
|
- [Slice Tricks by Ian Lance Taylor](https://github.com/golang/go/wiki/SliceTricks)
|
||
|
- [DigitalOcean: Understanding array and slices in Go](https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go)
|
||
|
|
||
|
|
||
|
## Go Cheat Sheet
|
||
|
|
||
|
Copia descarada de [esto](https://github.com/a8m/golang-cheat-sheet)
|
||
|
|
||
|
|
||
|
### Crédito
|
||
|
|
||
|
La mayor parte de lo que se cuenta en esta sección está copiado de [golang-cheat-sheet](https://github.com/a8m/golang-cheat-sheet)
|
||
|
|
||
|
Los créditos originales avisan de que la mayor parte de los ejemplos tomados de [A Tour of Go](http://tour.golang.org/), una excelente introducción al lenguage __Go__
|
||
|
|
||
|
|
||
|
### Go en resumen
|
||
|
|
||
|
* Lenguaje Imperativo
|
||
|
* Tipado estático
|
||
|
* Sintáxis muy similar a la de C (pero con menos paréntesis y sin punto y coma obligatorio) la estructura es parecida a la de Oberon-2
|
||
|
* Compila a código nativo (nada de vm)
|
||
|
* No hay clases, pero tiene _structs_ con métodos
|
||
|
* Interfaces
|
||
|
* No implementa herencia. Hay algo llamado [type embedding](http://golang.org/doc/effective%5Fgo.html#embedding)
|
||
|
* Las Funciones son ciudadanos de primera clase
|
||
|
* Las Funciones pueden devolver múltiples valores
|
||
|
* Tiene _closures_
|
||
|
* Hay Punteros pero sin aritmética de punteros
|
||
|
* La concurrencia es _"Built-in"_ mediante las primitivas: _Goroutines_ y _Channels_
|
||
|
|
||
|
### Sintáxis básica
|
||
|
|
||
|
#### Hello World
|
||
|
|
||
|
Fichero `hello.go`:
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import "fmt"
|
||
|
|
||
|
func main() {
|
||
|
fmt.Println("Hello Go")
|
||
|
}
|
||
|
```
|
||
|
|
||
|
`$ go run hello.go`
|
||
|
|
||
|
### Operadores
|
||
|
|
||
|
#### Aritméticos
|
||
|
|Operador|Descripción|
|
||
|
|--------|-----------|
|
||
|
|`+`|addition|
|
||
|
|`-`|subtraction|
|
||
|
|`*`|multiplication|
|
||
|
|`/`|quotient|
|
||
|
|`%`|remainder|
|
||
|
|`&`|bitwise and|
|
||
|
|`\|`|bitwise or|
|
||
|
|`^`|bitwise xor|
|
||
|
|`&^`|bit clear (and not)|
|
||
|
|`<<`|left shift|
|
||
|
|`>>`|right shift|
|
||
|
|
||
|
#### Comparación
|
||
|
|Operador|Descripción|
|
||
|
|--------|-----------|
|
||
|
|`==`|equal|
|
||
|
|`!=`|not equal|
|
||
|
|`<`|less than|
|
||
|
|`<=`|less than or equal|
|
||
|
|`>`|greater than|
|
||
|
|`>=`|greater than or equal|
|
||
|
|
||
|
#### Lógicos
|
||
|
|Operador|Descripción|
|
||
|
|--------|-----------|
|
||
|
|`&&`|logical and|
|
||
|
|`\|\|`|logical or|
|
||
|
|`!`|logical not|
|
||
|
|
||
|
#### Otros
|
||
|
|Operador|Descripción|
|
||
|
|--------|-----------|
|
||
|
|`&`|address of / create pointer|
|
||
|
|`*`|dereference pointer|
|
||
|
|`<-`|send / receive operator (see 'Channels' below)|
|
||
|
|
||
|
### Declaraciones
|
||
|
__El tipo va después del indentificador de variable__
|
||
|
```go
|
||
|
var foo int // declaración sin inicialización
|
||
|
var foo int = 42 // declaración con inicialización
|
||
|
var foo, bar int = 42, 1302 // declaración e inicialización múltiples
|
||
|
var foo = 42 // se omite el tipo, será inferido
|
||
|
foo := 42 // abreviado, sólo es válido en el cuerpo de las funciones,
|
||
|
// el tipo siempre es implícito
|
||
|
const constant = "Esto es una constante"
|
||
|
|
||
|
// iota se puede usar para números que se incrementan, empezando por cero
|
||
|
const (
|
||
|
_ = iota
|
||
|
a
|
||
|
b
|
||
|
c = 1 << iota
|
||
|
d
|
||
|
)
|
||
|
fmt.Println(a, b) // 1 2 (0 is skipped)
|
||
|
fmt.Println(c, d) // 8 16 (2^3, 2^4)
|
||
|
```
|
||
|
|
||
|
### Funciones
|
||
|
|
||
|
```go
|
||
|
// una función simple
|
||
|
func functionName() {}
|
||
|
|
||
|
// función con parámetros (aquí también va el tipo después de la variable)
|
||
|
func functionName(param1 string, param2 int) {}
|
||
|
|
||
|
// multiples parámetros del mismo tipo
|
||
|
func functionName(param1, param2 int) {}
|
||
|
|
||
|
// podemos especificar el tipo devuelto por la función
|
||
|
func functionName() int {
|
||
|
return 42
|
||
|
}
|
||
|
|
||
|
// Pueden devolver multiples valores
|
||
|
func returnMulti() (int, string) {
|
||
|
return 42, "foobar"
|
||
|
}
|
||
|
var x, str = returnMulti()
|
||
|
|
||
|
// si los valores devueltos tienen nombre no hace falta especificarlos en el Return
|
||
|
func returnMulti2() (n int, s string) {
|
||
|
n = 42
|
||
|
s = "foobar"
|
||
|
// n and s will be returned
|
||
|
return
|
||
|
}
|
||
|
var x, str = returnMulti2()
|
||
|
```
|
||
|
|
||
|
#### Funciones como valores y _closures_
|
||
|
|
||
|
```go
|
||
|
func main() {
|
||
|
// asignar una función a una variable
|
||
|
add := func(a, b int) int {
|
||
|
return a + b
|
||
|
}
|
||
|
// usar el nombre para llamar a la función
|
||
|
fmt.Println(add(3, 4))
|
||
|
}
|
||
|
|
||
|
// 'Closures', lexically scoped: Las funciones pueden acceder valores que estában dentro del alcance (scope)
|
||
|
// cuando se definió la función
|
||
|
|
||
|
func scope() func() int{
|
||
|
outer_var := 2
|
||
|
foo := func() int { return outer_var}
|
||
|
return foo
|
||
|
}
|
||
|
|
||
|
func another_scope() func() int{
|
||
|
// won't compile because outer_var and foo not defined in this scope
|
||
|
outer_var = 444
|
||
|
return foo
|
||
|
}
|
||
|
|
||
|
|
||
|
// Closures
|
||
|
func outer() (func() int, int) {
|
||
|
outer_var := 2
|
||
|
inner := func() int {
|
||
|
outer_var += 99 // outer_var from outer scope is mutated.
|
||
|
return outer_var
|
||
|
}
|
||
|
inner()
|
||
|
return inner, outer_var // return inner func and mutated outer_var 101
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Variadic Functions
|
||
|
|
||
|
```go
|
||
|
func main() {
|
||
|
fmt.Println(adder(1, 2, 3)) // 6
|
||
|
fmt.Println(adder(9, 9)) // 18
|
||
|
|
||
|
nums := []int{10, 20, 30}
|
||
|
fmt.Println(adder(nums...)) // 60
|
||
|
}
|
||
|
|
||
|
// Usando ... antes del nombre del tipo del último parámetro indicamos que la función acepta cero o mas parámetros de ese tipo
|
||
|
// La función se usa como cualquier otra, excepto que podemos pasar tantos parámetros como queramos del último parámetro definido
|
||
|
func adder(args ...int) int {
|
||
|
total := 0
|
||
|
for _, v := range args { // Iterates over the arguments whatever the number.
|
||
|
total += v
|
||
|
}
|
||
|
return total
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Tipos _Built-in_
|
||
|
|
||
|
```
|
||
|
bool
|
||
|
|
||
|
string
|
||
|
|
||
|
int int8 int16 int32 int64
|
||
|
uint uint8 uint16 uint32 uint64 uintptr
|
||
|
|
||
|
byte // alias for uint8
|
||
|
|
||
|
rune // alias for int32 ~= a character (Unicode code point) - very Viking
|
||
|
|
||
|
float32 float64
|
||
|
|
||
|
complex64 complex128
|
||
|
```
|
||
|
|
||
|
### Conversión de tipos
|
||
|
|
||
|
```go
|
||
|
var i int = 42
|
||
|
var f float64 = float64(i)
|
||
|
var u uint = uint(f)
|
||
|
|
||
|
// alternative syntax
|
||
|
i := 42
|
||
|
f := float64(i)
|
||
|
u := uint(f)
|
||
|
```
|
||
|
|
||
|
### Paquetes (_Packages_)
|
||
|
|
||
|
{{< admonition type=info title="Referencias" open=false >}}
|
||
|
* [Golangbot: Go Packages](https://golangbot.com/go-packages/)
|
||
|
* [The Go blog: Using Go Modules](https://blog.golang.org/using-go-modules)
|
||
|
* [How to write Go code with GOPATH](https://golang.org/doc/gopath_code)
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
Las unidades de encapsulado en _Go_ de menor a mayor nivel serían:
|
||
|
- Funciones
|
||
|
- Paquetes
|
||
|
- Módulos
|
||
|
|
||
|
Los paquetes nos permiten organizar los ficheros de código para hacerlos modulares y reutilizables además de facilitar el mantenimiento del software.
|
||
|
|
||
|
* Cada fichero de código _Go_ debe pertenecer a un paquete. La pertenencia se declara con `package` al principio de cada uno de los ficheros fuente, evidentemente un paquete puede "poseer" varios ficheros.
|
||
|
* Los ejecutables están en el paquete `main`. Es un paquete especial.
|
||
|
* Por convención los programas ejecutables (los del paquete `main`) se llaman comandos (_commands_). El resto se llaman simplemente paquetes (_packages_)
|
||
|
* Por convención: <nombre_del_paquete> == último nombre del _import path_ (import path `math/rand` => package `rand`) Lo normal es que todos los ficheros del paquete `rand` se guarden en el directorio `rand/`
|
||
|
* Si el identificador empieza con mayúscula: se exporta el símbolo (será visible desde otros paquetes)
|
||
|
* Si el identificador empieza con minúscula: privado (no será visible desde otros paquetes)
|
||
|
|
||
|
Los **módulos** son coleciones de paquetes. Son imprescindibles para crear nuestros propios paquetes por qué la ruta de nuestros paquetes viene dada por el **módulo**. En cuanto quieras empezar a organizar tu código en paquetes inevitablemente tendrás que crear también **módulos**.
|
||
|
|
||
|
El flujo típico de trabajo para un proyecto estructurado con _Packages_ sería el siguiente:
|
||
|
|
||
|
- Supongamos que nuestro proyecto será un "pomodoro" y que va a gestionar _Timers_ y _Notifications_ (por decir algo)
|
||
|
- Creamos el directorio del proyecto que vamos a llamar `pomodoro`, dentro de ese directorio tendremos el _package main_ con los "comandos".
|
||
|
```bash
|
||
|
mkdir pomodoro
|
||
|
cd pomodoro
|
||
|
touch main.go
|
||
|
```
|
||
|
- Iniciamos el modulo en la raiz del proyecto
|
||
|
```bash
|
||
|
go mod init pomodoro
|
||
|
|
||
|
# Aunque lo mas correcto sería iniciarlo como
|
||
|
go mod init gitlab/salvari/pomodoro
|
||
|
```
|
||
|
- Creamos los subdirectorios para los _packages_: `timer` y `notification`. Dentro de los subdirectorios creamos un fichero de código (pueden ser tantos ficheros como queramos)
|
||
|
```bash
|
||
|
mkdir timer
|
||
|
touch timer/timer.go
|
||
|
mkdir notification
|
||
|
touch notification/notification.go
|
||
|
```
|
||
|
|
||
|
Tendremos una estructura de directorios:
|
||
|
|
||
|
```bash
|
||
|
pomodoro
|
||
|
├── go.mod
|
||
|
├── main.go
|
||
|
├── notification
|
||
|
│ └── notification.go
|
||
|
└── timer
|
||
|
└── timer.go
|
||
|
```
|
||
|
|
||
|
El contenido del fichero `go.mod` será (la versión de go depende de lo que tengas instalado):
|
||
|
|
||
|
```go
|
||
|
module pomodoro
|
||
|
|
||
|
go 1.17
|
||
|
```
|
||
|
|
||
|
Cada fichero con extensión `.go` debe empezar **siempre** con la declaración del _package_. En nuestro caso `main.go` declarará `package main` y por ejemplo `notification.go` declarará `package notification`.
|
||
|
|
||
|
Ya tenemos todo estructurado, ahora en nuestros ficheros de código podremos hacer _imports_ de este estilo
|
||
|
|
||
|
```go
|
||
|
import "pomodoro/timer"
|
||
|
import "pomodoro/notification"
|
||
|
```
|
||
|
|
||
|
#### Gestión de dependencias con módulos en Go
|
||
|
|
||
|
<https://go.dev/blog/go116-module-changes>
|
||
|
<https://stackoverflow.com/questions/53368187/go-modules-installing-go-tools/57317864#57317864>
|
||
|
|
||
|
|
||
|
### Control del flujo de programa
|
||
|
|
||
|
#### If
|
||
|
|
||
|
```go
|
||
|
func main() {
|
||
|
// Basic one
|
||
|
if x > 10 {
|
||
|
return x
|
||
|
} else if x == 10 {
|
||
|
return 10
|
||
|
} else {
|
||
|
return -x
|
||
|
}
|
||
|
|
||
|
// You can put one statement before the condition
|
||
|
if a := b + c; a < 42 {
|
||
|
return a
|
||
|
} else {
|
||
|
return a - 42
|
||
|
}
|
||
|
|
||
|
// Type assertion inside if
|
||
|
var val interface{}
|
||
|
val = "foo"
|
||
|
if str, ok := val.(string); ok {
|
||
|
fmt.Println(str)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Bucles
|
||
|
```go
|
||
|
// Solo hay bucle `for`, no hay `while`, ni `until`
|
||
|
for i := 1; i < 10; i++ {
|
||
|
}
|
||
|
for ; i < 10; { // bucle while
|
||
|
}
|
||
|
for i < 10 { // se pueden omitir los ; si solo hay una condición
|
||
|
}
|
||
|
for { // Si omitimos la condición tenemos un while (true)
|
||
|
}
|
||
|
|
||
|
// Podemos usar use break/continue en el bucle activo
|
||
|
// o usar break/continue con etiquetas (para bucles más externos)
|
||
|
here:
|
||
|
for i := 0; i < 2; i++ {
|
||
|
for j := i + 1; j < 3; j++ {
|
||
|
if i == 0 {
|
||
|
continue here
|
||
|
}
|
||
|
fmt.Println(j)
|
||
|
if j == 2 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
there:
|
||
|
for i := 0; i < 2; i++ {
|
||
|
for j := i + 1; j < 3; j++ {
|
||
|
if j == 1 {
|
||
|
continue
|
||
|
}
|
||
|
fmt.Println(j)
|
||
|
if j == 2 {
|
||
|
break there
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Switch
|
||
|
|
||
|
```go
|
||
|
// switch statement
|
||
|
switch operatingSystem {
|
||
|
case "darwin":
|
||
|
fmt.Println("Mac OS Hipster")
|
||
|
// cases break automatically, no fallthrough by default
|
||
|
case "linux":
|
||
|
fmt.Println("Linux Geek")
|
||
|
default:
|
||
|
// Windows, BSD, ...
|
||
|
fmt.Println("Other")
|
||
|
}
|
||
|
|
||
|
// al igual que con el 'for' y el 'if' podemos tener una sentencia de asignación justo antes de la variable del switch
|
||
|
switch os := runtime.GOOS; os {
|
||
|
case "darwin": ...
|
||
|
}
|
||
|
|
||
|
// se pueden hacer comparaciones en los casos del switch
|
||
|
number := 42
|
||
|
switch {
|
||
|
case number < 42:
|
||
|
fmt.Println("Smaller")
|
||
|
case number == 42:
|
||
|
fmt.Println("Equal")
|
||
|
case number > 42:
|
||
|
fmt.Println("Greater")
|
||
|
}
|
||
|
|
||
|
// los casos pueden ser listas de valores separados por comas
|
||
|
var char byte = '?'
|
||
|
switch char {
|
||
|
case ' ', '?', '&', '=', '#', '+', '%':
|
||
|
fmt.Println("Should escape")
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Arrays, Slices, Ranges
|
||
|
|
||
|
#### Arrays
|
||
|
|
||
|
- Tienen longitud fija. Los _arrays_ de longitudes **son tipos diferentes**
|
||
|
- No pueden redimensionarse
|
||
|
- El índice empieza en cero
|
||
|
- Un array **no es un puntero** en Go
|
||
|
|
||
|
```go
|
||
|
var a [10]int // declara un array de enteros (int) con longitud 10. ¡La longitud determina el tipo!
|
||
|
a[3] = 42 // establece el valor de un elemento
|
||
|
i := a[3] // lee el valor de un elemento
|
||
|
|
||
|
// array literals
|
||
|
var a = [2]int{1, 2}
|
||
|
a := [2]int{1, 2} //shorthand
|
||
|
a := [...]int{1, 2} // elipsis -> El compilador infiere la longitud del array
|
||
|
|
||
|
// no hay paso por referencia
|
||
|
arr1 := arr2 // Se hace una copia
|
||
|
arr1 := &arr2 // Se copia la referencia
|
||
|
|
||
|
// multidimensionales
|
||
|
// var variable_name [SIZE1][SIZE2]…[SIZEN] variable_type
|
||
|
|
||
|
a := [3][4]int{
|
||
|
{0, 1, 2, 3} , // initializers for row indexed by 0
|
||
|
{4, 5, 6, 7} , // initializers for row indexed by 1
|
||
|
{8, 9, 10, 11} // initializers for row indexed by 2}
|
||
|
```
|
||
|
|
||
|
#### Slices
|
||
|
|
||
|
- Un _slice_ es una abstracción apuntando a un array.
|
||
|
- Se pueden crear a partir de un array existente, en caso contrario Go creará el array detrás de las bambalinas
|
||
|
- No se especifica la dimensión
|
||
|
- Realmente un _slice_ son tres datos:
|
||
|
- Un puntero a la secuencia de datos en memoria
|
||
|
- Una longitud (_lenght_, `len(a)`) que almacena el número de elementos
|
||
|
- Una capacidad (_capacity_, `cap(a)`) que es el total de posiciones reservadas en memoria
|
||
|
- Cuando se asigna un _slice_ en realidad se copian esos valores, así que **se copia la referencia**
|
||
|
- Si un _slice_ tiene que crecer el compilador normalmente es conservador y **duplica** la capacidad. Eso implicará re-localizaciones del array subyacente en memoria
|
||
|
|
||
|
```go
|
||
|
var a []int // declare a slice - similar to an array, but length is unspecified
|
||
|
var a = []int {1, 2, 3, 4} // declare and initialize a slice (backed by the array given implicitly)
|
||
|
a := []int{1, 2, 3, 4} // shorthand
|
||
|
chars := []string{0:"a", 2:"c", 1: "b"} // ["a", "b", "c"]
|
||
|
|
||
|
|
||
|
var b = a[lo:hi] // creates a slice (view of the array) from index lo to hi-1
|
||
|
var b = a[1:4] // slice from index 1 to 3
|
||
|
var b = a[:3] // missing low index implies 0
|
||
|
var b = a[3:] // missing high index implies len(a)
|
||
|
a = append(a,17,3) // append items to slice a
|
||
|
c := append(a,b...) // concatenate slices a and b
|
||
|
|
||
|
// create a slice with make
|
||
|
a = make([]byte, 5, 5) // first arg length, second capacity
|
||
|
a = make([]byte, 5) // capacity is optional
|
||
|
|
||
|
// create a slice from an array
|
||
|
x := [3]string{"Лайка", "Белка", "Стрелка"}
|
||
|
s := x[:] // a slice referencing the storage of x
|
||
|
```
|
||
|
|
||
|
#### Operaciones sobre _Arrays_ y _Slices_
|
||
|
|
||
|
`len(a)` devuelve la longitud de un _array_ o _slice_ `a`. Es un _built-in_ no un atributo o método del *array*.
|
||
|
|
||
|
```go
|
||
|
// iterar sobre un array o slice
|
||
|
for i, e := range a {
|
||
|
// i es el index, e es el elemento
|
||
|
}
|
||
|
|
||
|
// si no vamos a usar el índice:
|
||
|
for _, e := range a {
|
||
|
// e es el elemento
|
||
|
}
|
||
|
|
||
|
// si solo queremos usar el índice
|
||
|
for i := range a {
|
||
|
}
|
||
|
|
||
|
// Desde Go 1.4 se puede iterar sin variables:
|
||
|
for range time.Tick(time.Second) {
|
||
|
// hacer algo cada segundo
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Maps
|
||
|
|
||
|
- Los mapas (_maps_) son inmutables
|
||
|
- No se garantiza ningún orden
|
||
|
|
||
|
```go
|
||
|
var m map[string]int
|
||
|
m = make(map[string]int)
|
||
|
m["key"] = 42
|
||
|
fmt.Println(m["key"])
|
||
|
|
||
|
delete(m, "key")
|
||
|
|
||
|
elem, ok := m["key"] // test if key "key" is present and retrieve it, if so
|
||
|
|
||
|
// map literal
|
||
|
var m = map[string]Vertex{
|
||
|
"Bell Labs": {40.68433, -74.39967},
|
||
|
"Google": {37.42202, -122.08408},
|
||
|
}
|
||
|
|
||
|
// iterate over map content
|
||
|
for key, value := range m {
|
||
|
}
|
||
|
|
||
|
```
|
||
|
|
||
|
### Structs
|
||
|
|
||
|
No hay clases en __Go__, solo _structs_. Las _structs_ pueden tener métodos.
|
||
|
|
||
|
```go
|
||
|
// Una struct es un tipo. También es una colección de campos
|
||
|
|
||
|
// Declaración
|
||
|
type Vertex struct {
|
||
|
X, Y int
|
||
|
}
|
||
|
|
||
|
// Creación
|
||
|
var v = Vertex{1, 2}
|
||
|
var v = Vertex{X: 1, Y: 2} // Creación de la struct definiendo sus valores con clave
|
||
|
var v = []Vertex{{1,2},{5,2},{5,5}} // Creación de un slice de structs
|
||
|
|
||
|
// Acceso a miembros de la struct
|
||
|
v.X = 4
|
||
|
|
||
|
// Se pueden declarar métodos sobre structs. La referencia a la struct que recibe el método
|
||
|
// tiene que ir entre la palabra clave 'func' y el nombre del método
|
||
|
// LA ESTRUCTURA SE COPIA EN CADA LLAMADA AL MÉTODO
|
||
|
func (v Vertex) Abs() float64 {
|
||
|
return math.Sqrt(v.X*v.X + v.Y*v.Y)
|
||
|
}
|
||
|
|
||
|
// Invocando el método
|
||
|
v.Abs()
|
||
|
|
||
|
// Para los métodos que mutan la estructura usamos punteros a esa struct
|
||
|
// como tipos. De esta forma EVITAMOS LA COPIA de la estructura al invocar el método
|
||
|
func (v *Vertex) add(n float64) {
|
||
|
v.X += n
|
||
|
v.Y += n
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Tenemos un caso especial con los contructores. Si queremos evitar que se construyan objetos sin usar el constructor:
|
||
|
|
||
|
```go
|
||
|
package matrix
|
||
|
|
||
|
type matrix struct { // NO EXPORTADO
|
||
|
....
|
||
|
}
|
||
|
func NewMatrix(rows, cols int) *matrix {
|
||
|
m := new(matrix)
|
||
|
m.rows = rows
|
||
|
m.cols = cols
|
||
|
m.elems = make([]float, rows*cols)
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
```
|
||
|
|
||
|
Como no exportamos la estructura base, solo se pueden construir nuevos objetos a través del constructor.
|
||
|
|
||
|
La función `NewMatrix` puede simplificarse fácilmente:
|
||
|
|
||
|
```go
|
||
|
func NewMatrix(rows, cols, int) *matrix {
|
||
|
return &matrix{rows, cols, make([]float, rows*cols)}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
{{< admonition type=warning title="structs y exportacion" open=true >}}
|
||
|
Si el nombre de la `struct` o de alguno de sus campos empieza por minúscula **no serán visibles** fuera de la propia `struct`
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
**Anonymous structs:**
|
||
|
|
||
|
Más económicas y seguras que usar: `map[string]interface{}`.
|
||
|
|
||
|
```go
|
||
|
point := struct {
|
||
|
X, Y int
|
||
|
}{1, 2}
|
||
|
```
|
||
|
|
||
|
### Punteros
|
||
|
|
||
|
```go
|
||
|
p := Vertex{1, 2} // p es de tipo Vertex
|
||
|
q := &p // q es un puntero a un Vertex
|
||
|
r := &Vertex{1, 2} // r también es un puntero a un Vertex
|
||
|
|
||
|
// El tipo de un puntero a un Vertex es *Vertex
|
||
|
|
||
|
var s *Vertex = new(Vertex) // new crea un puntero a una nueva instancia de Vertex
|
||
|
```
|
||
|
|
||
|
### Interfaces
|
||
|
|
||
|
- Un _interface_ es una especie de "contrato".
|
||
|
- Dentro del _interface_ especificamos las funciones que tiene que soportar un tipo para satisfacer el _interface_
|
||
|
- El _interface_ nos da una capa extra de abstracción, podemos definir una función que procese o devuelva el "tipo" definido por el _interface_
|
||
|
|
||
|
|
||
|
```go
|
||
|
// declaración
|
||
|
type Awesomizer interface {
|
||
|
Awesomize() string
|
||
|
}
|
||
|
|
||
|
// los tipos no declaran en ningún sitio implementar un interface
|
||
|
type Foo struct {}
|
||
|
|
||
|
// los tipos que implementan todos los métodos de un interface, satifacen ese interface implicitamente
|
||
|
func (foo Foo) Awesomize() string {
|
||
|
return "Awesome!"
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Embedding
|
||
|
|
||
|
There is no subclassing in Go. Instead, there is interface and struct embedding.
|
||
|
|
||
|
```go
|
||
|
// ReadWriter implementations must satisfy both Reader and Writer
|
||
|
type ReadWriter interface {
|
||
|
Reader
|
||
|
Writer
|
||
|
}
|
||
|
|
||
|
// Server exposes all the methods that Logger has
|
||
|
type Server struct {
|
||
|
Host string
|
||
|
Port int
|
||
|
*log.Logger
|
||
|
}
|
||
|
|
||
|
// initialize the embedded type the usual way
|
||
|
server := &Server{"localhost", 80, log.New(...)}
|
||
|
|
||
|
// methods implemented on the embedded struct are passed through
|
||
|
server.Log(...) // calls server.Logger.Log(...)
|
||
|
|
||
|
// the field name of the embedded type is its type name (in this case Logger)
|
||
|
var logger *log.Logger = server.Logger
|
||
|
```
|
||
|
|
||
|
### Errores
|
||
|
|
||
|
No hay gestión de excepciones en __Go__. Las funciones que pueden lanzar un _Error_ simplemente devuelven un valor adicional de tipo `Error`.
|
||
|
|
||
|
El _interface_ `Error` tiene esta pinta:
|
||
|
```go
|
||
|
type error interface {
|
||
|
Error() string
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Una función que puede devolver un error:
|
||
|
|
||
|
```go
|
||
|
func doStuff() (int, error) {
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
result, err := doStuff()
|
||
|
if err != nil {
|
||
|
// handle error
|
||
|
} else {
|
||
|
// all is good, use result
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Concurrency
|
||
|
|
||
|
#### Goroutines
|
||
|
|
||
|
Goroutines are lightweight threads (managed by Go, not OS threads). `go f(a, b)` starts a new goroutine which runs `f` (given `f` is a function).
|
||
|
|
||
|
```go
|
||
|
// just a function (which can be later started as a goroutine)
|
||
|
func doStuff(s string) {
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
// using a named function in a goroutine
|
||
|
go doStuff("foobar")
|
||
|
|
||
|
// using an anonymous inner function in a goroutine
|
||
|
go func (x int) {
|
||
|
// function body goes here
|
||
|
}(42)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Channels
|
||
|
|
||
|
```go
|
||
|
ch := make(chan int) // create a channel of type int
|
||
|
ch <- 42 // Send a value to the channel ch.
|
||
|
v := <-ch // Receive a value from ch
|
||
|
|
||
|
// Non-buffered channels block. Read blocks when no value is available, write blocks until there is a read.
|
||
|
|
||
|
// Create a buffered channel. Writing to a buffered channels does not block if less than <buffer size> unread values have been written.
|
||
|
ch := make(chan int, 100)
|
||
|
|
||
|
close(ch) // closes the channel (only sender should close)
|
||
|
|
||
|
// read from channel and test if it has been closed
|
||
|
v, ok := <-ch
|
||
|
|
||
|
// if ok is false, channel has been closed
|
||
|
|
||
|
// Read from channel until it is closed
|
||
|
for i := range ch {
|
||
|
fmt.Println(i)
|
||
|
}
|
||
|
|
||
|
// select blocks on multiple channel operations, if one unblocks, the corresponding case is executed
|
||
|
func doStuff(channelOut, channelIn chan int) {
|
||
|
select {
|
||
|
case channelOut <- 42:
|
||
|
fmt.Println("We could write to channelOut!")
|
||
|
case x := <- channelIn:
|
||
|
fmt.Println("We could read from channelIn")
|
||
|
case <-time.After(time.Second * 1):
|
||
|
fmt.Println("timeout")
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
##### Channel Axioms
|
||
|
|
||
|
- A send to a nil channel blocks forever
|
||
|
|
||
|
```go
|
||
|
var c chan string
|
||
|
c <- "Hello, World!"
|
||
|
// fatal error: all goroutines are asleep - deadlock!
|
||
|
```
|
||
|
- A receive from a nil channel blocks forever
|
||
|
|
||
|
```go
|
||
|
var c chan string
|
||
|
fmt.Println(<-c)
|
||
|
// fatal error: all goroutines are asleep - deadlock!
|
||
|
```
|
||
|
- A send to a closed channel panics
|
||
|
|
||
|
```go
|
||
|
var c = make(chan string, 1)
|
||
|
c <- "Hello, World!"
|
||
|
close(c)
|
||
|
c <- "Hello, Panic!"
|
||
|
// panic: send on closed channel
|
||
|
```
|
||
|
- A receive from a closed channel returns the zero value immediately
|
||
|
|
||
|
```go
|
||
|
var c = make(chan int, 2)
|
||
|
c <- 1
|
||
|
c <- 2
|
||
|
close(c)
|
||
|
for i := 0; i < 3; i++ {
|
||
|
fmt.Printf("%d ", <-c)
|
||
|
}
|
||
|
// 1 2 0
|
||
|
```
|
||
|
|
||
|
### Printing
|
||
|
|
||
|
```go
|
||
|
fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline
|
||
|
p := struct { X, Y int }{ 17, 2 }
|
||
|
fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc
|
||
|
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable
|
||
|
|
||
|
fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format
|
||
|
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable
|
||
|
|
||
|
hellomsg := `
|
||
|
"Hello" in Chinese is 你好 ('Ni Hao')
|
||
|
"Hello" in Hindi is नमस्ते ('Namaste')
|
||
|
` // multi-line string literal, using back-tick at beginning and end
|
||
|
```
|
||
|
|
||
|
### Reflection
|
||
|
|
||
|
#### Type Switch
|
||
|
|
||
|
A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
|
||
|
|
||
|
```go
|
||
|
func do(i interface{}) {
|
||
|
switch v := i.(type) {
|
||
|
case int:
|
||
|
fmt.Printf("Twice %v is %v\n", v, v*2)
|
||
|
case string:
|
||
|
fmt.Printf("%q is %v bytes long\n", v, len(v))
|
||
|
default:
|
||
|
fmt.Printf("I don't know about type %T!\n", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
do(21)
|
||
|
do("hello")
|
||
|
do(true)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Snippets
|
||
|
|
||
|
#### HTTP Server
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
)
|
||
|
|
||
|
// define a type for the response
|
||
|
type Hello struct{}
|
||
|
|
||
|
// let that type implement the ServeHTTP method (defined in interface http.Handler)
|
||
|
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
fmt.Fprint(w, "Hello!")
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
var h Hello
|
||
|
http.ListenAndServe("localhost:4000", h)
|
||
|
}
|
||
|
|
||
|
// Here's the method signature of http.ServeHTTP:
|
||
|
// type Handler interface {
|
||
|
// ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||
|
// }
|
||
|
```
|
||
|
|
||
|
|
||
|
## Misc
|
||
|
|
||
|
* Para ver el entorno usamos `go env`
|
||
|
|
||
|
|
||
|
## TDD con Go
|
||
|
|
||
|
* [TDD with Go](https://leanpub.com/golang-tdd/read)
|
||
|
|
||
|
Para que funcionen correctamente los comandos `go build`, `go clean` y `go test` parece que es necesario haber definido correctamente `go.mod` (con `go mod init`)
|
||
|
|
||
|
`go test` buscará ficheros nombrados como `*_test.go` para ejecutarlos.
|
||
|
|
||
|
Dentro de esos de test habrá bloques funcionales con la firma: `func TestXxxx(t *testing.T)`
|
||
|
|
||
|
Los test fallidos se informan con `t.Errorf`
|
||
|
|
||
|
### Ciclo de test
|
||
|
|
||
|
El ciclo de trabajo se resume en _**Red, Green, Refactor**_.
|
||
|
|
||
|
* Para implementar una nueva funcionalidad el primer paso debe ser escribir los test que capturan los requisitos de esa nueva funcionalidad. Estos test obviamente fallarán (_Red_ o rojo de fallo).
|
||
|
* La segunda fase consiste en implementar el código necesario para cumplir con los test y queden todos en verde (_Green_). El código debe minimizarse para cumplir los test sin "sobreimplementar" nada.
|
||
|
* El último paso es el _refactoring_. **No** implementamos nueva funcionalidad en este paso, se trata simplemente de hacer un código de calidad re-escribiendo el código de la segunda fase. **No** es un paso opcional, es imprescindible revisar el código y garantizar la coherencia y calidad del mismo.
|
||
|
|
||
|
### Vicios en TDD
|
||
|
1. Escribir demasiados test a la vez
|
||
|
2. Concentrarse exclusivamente en los _happy path_ y la cobertura del código
|
||
|
3. No considerar los diferentes escenarios
|
||
|
4. Demasiadas comprobaciones (_asserts_) en un sólo test
|
||
|
5. Probar cosas diferentes en el mismo caso de prueba
|
||
|
6. Escribir test triviales para mantener el código cubierto
|
||
|
7. No ejecutar los test con frecuencia
|
||
|
8. No seguir siempre las tres fases. Especialmente implementando código sin escribir antes el test
|
||
|
9. Hacer _asserts_ que no prueban nada
|
||
|
10. Crear test difíciles de mantener
|
||
|
|
||
|
### Dobles de prueba (_test doubles_)
|
||
|
Una implementación simplificada de algún tipo para facilitar las pruebas
|
||
|
|
||
|
Casos posibles:
|
||
|
|
||
|
_**Dummies**_
|
||
|
|
||
|
: Tipos sin ningún comportamiento, se implementan únicamente para cumplir con la firma de alguna función que queremos probar
|
||
|
|
||
|
_**Stubs**_ ("breves")
|
||
|
|
||
|
: Tipos que implementan el comportamiento mínimo para pasar un test
|
||
|
|
||
|
***Mocks*** (parodias)
|
||
|
|
||
|
: Implementaciones parciales que permiten definir como suponemos que serán los métodos sobre el tipo
|
||
|
|
||
|
***Spies*** (espías)
|
||
|
|
||
|
: Implementaciones parciales que nos permiten comprobar que métodos han sido invocados
|
||
|
|
||
|
***Fakes*** (falsificaciones)
|
||
|
|
||
|
: Implementaciones ligeras pero completas, por ejemplo implementar una base de datos en memoria a efectos de pruebas
|
||
|
|
||
|
|
||
|
|
||
|
## Paquetes útiles
|
||
|
|
||
|
### `flag`
|
||
|
|
||
|
Este paquete nos permite leer parámetros pasados en la llamada al programa por linea de comandos.
|
||
|
|
||
|
### `encoding/json`
|
||
|
|
||
|
Este paquete nos permite salvar o recuperar estructuras de datos desde ficheros _json_
|
||
|
|
||
|
{{< admonition type=warning title="Referencias" open=false >}}
|
||
|
* [json con golang](https://golangdocs.com/json-with-golang)
|
||
|
* [read a json file](https://golangdocs.com/golang-read-json-file)
|
||
|
* [Parse json to a nested struct](https://penthaa.medium.com/golang-how-to-parse-json-data-into-a-nested-struct-29be89ce2ae8)
|
||
|
{{< /admonition >}}
|
||
|
|
||
|
|
||
|
**_marshalling_**
|
||
|
|
||
|
: El proceso de codificar nuestra estructura de datos en _json_.
|
||
|
|
||
|
**_unmarshalling_**
|
||
|
|
||
|
: El proceso inverso al anterior para pasar de json a una estructura de datos
|
||
|
|
||
|
|
||
|
#### Arrays y Slices
|
||
|
|
||
|
Podemos codificar en _json_ arrays o slices de _Go_
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
type Book struct {
|
||
|
Name string
|
||
|
Author string
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
|
||
|
//--------------------------------------------------
|
||
|
// Marshalling
|
||
|
book := Book{"C++ programming language", "Bjarne Stroutsrup"}
|
||
|
my_json, err := json.Marshal(book)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Json for book is: %s\n", string(my_json)) // {"Name":"C++ programming language","Author":"Bjarne Stroutsrup"}
|
||
|
|
||
|
//--------------------------------------------------
|
||
|
// Unmarshalling
|
||
|
codString := `{"Name":"The Name of the Wind","Author":"Patrick Rothfuss"}`
|
||
|
|
||
|
var cod Book
|
||
|
|
||
|
err = json.Unmarshal([]byte(codString), &cod)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Book for json is: %v\n", cod)
|
||
|
|
||
|
//--------------------------------------------------
|
||
|
// Arrays and Slices
|
||
|
// You can marshall/unmarshall to array or slice
|
||
|
|
||
|
var books []Book
|
||
|
|
||
|
booksJson := `[{"Name": "Hacking for Dummies", "Author": "Kevin Beaver"},
|
||
|
{"Name": "Kerberos", "Author": "Jason Garman"}]`
|
||
|
|
||
|
err = json.Unmarshal([]byte(booksJson), &books)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
}
|
||
|
fmt.Printf("Slice for json is: %v\n", books)
|
||
|
|
||
|
some_books := []Book{
|
||
|
{Name: "Libro Uno", Author: "Author Uno"},
|
||
|
{Name: "Libro Dos", Author: "Author Dos"},
|
||
|
}
|
||
|
|
||
|
some_books_json, err := json.Marshal(some_books)
|
||
|
|
||
|
fmt.Printf("json for slice is: %v\n", string(some_books_json))
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Atributos "a medida" para _json_
|
||
|
|
||
|
Podemos añadir atributos _json_ a una `struct` que vamos a leer o a salvar con _json_.
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
type Book struct {
|
||
|
Name string `json:"title"` // IMPORTANTE: Nada de espacios al definir los atributos
|
||
|
Author string `json:"artist"`
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
|
||
|
//--------------------------------------------------
|
||
|
// Marshalling
|
||
|
book := Book{"C++ programming language", "Bjarne Stroutsrup"}
|
||
|
my_json, err := json.Marshal(book)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Json for book is: %s\n", string(my_json)) // {"title":"C++ programming language","artist":"Bjarne Stroutsrup"}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Además de cambiar los nombres de los campos podemos especificar un par de atributos `json` más:
|
||
|
|
||
|
- `.omitempty`: nos permite saltarnos los datos si el campo está vacio
|
||
|
- `"-"` : nos permite saltarnos este campo por completo
|
||
|
|
||
|
```go
|
||
|
type userdata struct {
|
||
|
login string `json:"username"`
|
||
|
real_name string `json:"name, .omitempty"`
|
||
|
password string `json:"-"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### _json_ y maps
|
||
|
|
||
|
Siempre podemos cargar el _json_ en un `map`, especialmente si no están muy bien estructurados:
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"encoding/json"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
unstructuredJson := `{"os": {"Windows": "Windows OS","Mac": "OSX","Linux": "Ubuntu"},"compilers": "gcc"}`
|
||
|
|
||
|
var result map[string]interface{}
|
||
|
|
||
|
json.Unmarshal([]byte(unstructuredJson), &result)
|
||
|
|
||
|
fmt.Println(result["os"]) // map[Linux:Ubuntu Mac:OSX Windows:Windows OS]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Por supuesto podemos también salvarlos en _json_:
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"encoding/json"
|
||
|
)
|
||
|
|
||
|
type Address struct {
|
||
|
Street string
|
||
|
City string
|
||
|
}
|
||
|
|
||
|
type Person struct {
|
||
|
Name string
|
||
|
Address Address
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
p := Person{
|
||
|
Name: "Sherlock Holmes",
|
||
|
Address: Address{
|
||
|
"22/b Baker street",
|
||
|
"London",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
str, err := json.Marshal(p)
|
||
|
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
}
|
||
|
|
||
|
fmt.Println(string(str)) // {"Name":"Sherlock Holmes","Address":{"Street":"22/b Baker street","City":"London"}}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### `os/exec`
|
||
|
|
||
|
<https://www.darrencoxall.com/golang/executing-commands-in-go/>
|
||
|
|
||
|
Este paquete es parte de la biblioteca estándar de _Go_. Nos permite ejecutar comandos de sistema.
|
||
|
|
||
|
Tres casos típicos:
|
||
|
|
||
|
- Lanzar un comando y capturar su salida
|
||
|
- Lanzar un comando y comprobar el _exit code_
|
||
|
- Lanzar comandos de larga duración
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"strings"
|
||
|
"syscall"
|
||
|
)
|
||
|
|
||
|
// printCommand prints command
|
||
|
func printCommand(cmd *exec.Cmd) {
|
||
|
fmt.Printf("==> Executing: %s\n", strings.Join(cmd.Args, " "))
|
||
|
}
|
||
|
|
||
|
// printError print human friendly error objects
|
||
|
func printError(err error) {
|
||
|
if err != nil {
|
||
|
os.Stderr.WriteString(fmt.Sprintf("==> Error: %s\n", err.Error()))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// printOutput prints command output
|
||
|
func printOutput(outs []byte) {
|
||
|
if len(outs) > 0 {
|
||
|
fmt.Printf("==> Output: %s\n", string(outs))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// main el cuerpo ppal del programa
|
||
|
func main() {
|
||
|
// Create an *exec.Cmd
|
||
|
// Capture Stdout and Stderr together
|
||
|
cmd := exec.Command("echo", "Called from Go!")
|
||
|
printCommand(cmd)
|
||
|
output, err := cmd.CombinedOutput() // runs cmd and return combined output
|
||
|
printError(err)
|
||
|
printOutput(output) // => go version
|
||
|
|
||
|
// Create an *exec.Cmd
|
||
|
cmd = exec.Command("go", "version")
|
||
|
|
||
|
// Stdout buffer
|
||
|
cmdOutput := &bytes.Buffer{}
|
||
|
// Attach buffer to command
|
||
|
cmd.Stdout = cmdOutput
|
||
|
|
||
|
// Execute command
|
||
|
printCommand(cmd)
|
||
|
err = cmd.Run() // will wait for command to return
|
||
|
printError(err)
|
||
|
// Only output the commands stdout
|
||
|
printOutput(cmdOutput.Bytes())
|
||
|
|
||
|
// create a command that will fail
|
||
|
cmd = exec.Command("ls", "/imaginary/dir")
|
||
|
var waitStatus syscall.WaitStatus
|
||
|
if err := cmd.Run(); err != nil {
|
||
|
printError(err)
|
||
|
// Did the command fail because of an unsuccessful exit code
|
||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||
|
waitStatus = exitError.Sys().(syscall.WaitStatus)
|
||
|
printOutput([]byte(fmt.Sprintf("%d", waitStatus.ExitStatus())))
|
||
|
}
|
||
|
} else {
|
||
|
// Command was successful
|
||
|
waitStatus = cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||
|
printOutput([]byte(fmt.Sprintf("%d", waitStatus.ExitStatus())))
|
||
|
printOutput(cmdOutput.Bytes())
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Para el último caso _comandos de larga duración_, no queremos que nuestro programa espere al resultado del comando. Es mejor hacerlo de forma asíncrona.
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// printError print human friendly error objects
|
||
|
func printError(err error) {
|
||
|
if err != nil {
|
||
|
os.Stderr.WriteString(fmt.Sprintf("==> Error: %s\n", err.Error()))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// printOutput prints command output
|
||
|
func printOutput(outs []byte) {
|
||
|
if len(outs) > 0 {
|
||
|
fmt.Printf("==> Output: %s\n", string(outs))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// main() programa ppal
|
||
|
func main() {
|
||
|
cmd := exec.Command("cat", "/dev/random") // creamos un comando
|
||
|
randomBytes := &bytes.Buffer{} // creamos un buffer
|
||
|
cmd.Stdout = randomBytes // asociamos la salida estándar al buffer
|
||
|
|
||
|
err := cmd.Start() // Lanzamos el comando de forma asíncrona
|
||
|
printError(err)
|
||
|
|
||
|
// Create a ticker that outputs elapsed time
|
||
|
ticker := time.NewTicker(time.Second)
|
||
|
go func(ticker *time.Ticker) {
|
||
|
now := time.Now()
|
||
|
for _ = range ticker.C {
|
||
|
printOutput(
|
||
|
[]byte(fmt.Sprintf("%s", time.Since(now))),
|
||
|
)
|
||
|
}
|
||
|
}(ticker)
|
||
|
|
||
|
// Create a timer that will kill the process
|
||
|
timer := time.NewTimer(time.Second * 4)
|
||
|
go func(timer *time.Timer, ticker *time.Ticker, cmd *exec.Cmd) {
|
||
|
for _ = range timer.C {
|
||
|
err := cmd.Process.Signal(os.Kill)
|
||
|
printError(err)
|
||
|
ticker.Stop()
|
||
|
}
|
||
|
}(timer, ticker, cmd)
|
||
|
|
||
|
// Only proceed once the process has finished
|
||
|
cmd.Wait()
|
||
|
printOutput(
|
||
|
[]byte(fmt.Sprintf("%d bytes generated!", len(randomBytes.Bytes()))),
|
||
|
)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
|
||
|
|
||
|
### `tcell`
|
||
|
|
||
|
Este paquete nos permite implementar interfaces de usuario basados en texto.
|
||
|
|
||
|
Instalamos:
|
||
|
|
||
|
```bash
|
||
|
go get -u github.com/gdamore/tcell
|
||
|
```
|
||
|
|
||
|
Recomiendan mirar primero el ejemplo incluido `mouse`
|
||
|
|
||
|
|
||
|
## Casos prácticos
|
||
|
|
||
|
### Leer y escribir json
|
||
|
|
||
|
Básico:
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
// main ...
|
||
|
func main() {
|
||
|
x := map[string]string{
|
||
|
"foo": "bar",
|
||
|
}
|
||
|
data, _ := json.Marshal(x)
|
||
|
fmt.Printf("Data contains:\n%s\n", (data))
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Result:
|
||
|
|
||
|
```bash
|
||
|
Data contains:
|
||
|
{"foo":"bar"}
|
||
|
```
|
||
|
|
||
|
Un poco más complicado:
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
// main ...
|
||
|
func main() {
|
||
|
|
||
|
type person struct {
|
||
|
Name string `json:"name"`
|
||
|
Age int `json:"age"`
|
||
|
Description string `json:"descr,omitempty"` // DON'T use spaces in tags
|
||
|
secret string // Unexported fields are Unmarshaled
|
||
|
}
|
||
|
|
||
|
x := person{
|
||
|
Name: "Bob",
|
||
|
Age: 32,
|
||
|
secret: "Shhh!",
|
||
|
}
|
||
|
|
||
|
data, _ := json.Marshal(x)
|
||
|
fmt.Printf("Data contains:\n%s\n", (data))
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Un ejemplo de *unmarshalling*, aunque siempre es preferible usar una `struc` si conocemos de antemano la estructura de los datos:
|
||
|
|
||
|
```go
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
// main test json
|
||
|
func main() {
|
||
|
data := []byte(`{"foo":"bar"}`)
|
||
|
|
||
|
var x interface{}
|
||
|
_ = json.Unmarshal(data, &x)
|
||
|
fmt.Printf("x contains:\n%s\n", x)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Un ejemplo de :
|
||
|
|
||
|
```go
|
||
|
|
||
|
```
|
||
|
|
||
|
|
||
|
## Best Practices
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Para estudiar
|
||
|
|
||
|
- <https://stackoverflow.com/questions/27178635/cast-a-struct-pointer-to-interface-pointer-in-golang>
|
||
|
- [Advanced Command Execution in Go](https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html)
|
||
|
- [Executing shell commands in Go](https://medium.com/rungo/executing-shell-commands-script-files-and-executables-in-go-894814f1c0f7)
|