Translate

Mostrando las entradas con la etiqueta Go. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Go. Mostrar todas las entradas

martes, 22 de octubre de 2024

Borgo, lenguaje de programación que compila a Go.


Borgo es un lenguaje de programación relativamente nuevo que está diseñado para ser versátil y eficiente en varios entornos de desarrollo. Aunque aún está en sus primeras etapas, promete ofrecer un equilibrio entre alto rendimiento y facilidad de uso, haciendo énfasis en la simplicidad y la expresividad del código.

  1. Simplicidad y Legibilidad: Uno de los pilares de Borgo es la simplicidad en su sintaxis. Se enfoca en evitar la verbosidad innecesaria, permitiendo que los desarrolladores escriban código más limpio y comprensible.
  2. Eficiencia y Rendimiento: Borgo está optimizado para ejecutar código de manera eficiente, lo que lo hace ideal para aplicaciones de alto rendimiento. Su compilador se enfoca en generar binarios pequeños y rápidos.
  3. Soporte para Programación Funcional y Orientada a Objetos: Al igual que lenguajes como Scala y Kotlin, Borgo combina paradigmas funcionales y orientados a objetos, lo que permite a los desarrolladores elegir el enfoque más adecuado para su proyecto.
  4. Concurrencia Nativa: Al ser un lenguaje moderno, Borgo incluye soporte para concurrencia y paralelismo de manera nativa, facilitando la creación de aplicaciones altamente escalables sin tener que recurrir a bibliotecas externas.
  5. Ecosistema Modular: Borgo apuesta por un ecosistema modular, donde los desarrolladores pueden añadir funcionalidad mediante paquetes externos, similar a lo que ofrecen lenguajes como Python con `pip` o Node.js con `npm`.

Relindas las características pero veamos un poco de código: 


use fmt

enum NetworkState<T> {

    Loading,

    Failed(int),

    Success(T),

}


struct Response {

    title: string,

    duration: int,

}


fn main() {

    let res = Response {

        title: "Hello world",

        duration: 0,

    }


    let state = NetworkState.Success(res)


    let msg = match state {

        NetworkState.Loading => "still loading",

        NetworkState.Failed(code) => fmt.Sprintf("Got error code: %d", code),

        NetworkState.Success(res) => res.title,

    }


    fmt.Println(msg)

}


Como ven, es como que go y rust hubieran tenido un hijo... 

Borgo es un lenguaje emergente con mucho potencial, diseñado para combinar simplicidad con eficiencia. Aunque aún no es ampliamente adoptado, sus características prometen hacer que valga la pena seguirlo de cerca, especialmente para aquellos desarrolladores que buscan una alternativa moderna a lenguajes tradicionales. 

Dejo link: https://borgo-lang.github.io/

jueves, 11 de julio de 2024

Como podemos manejar las referencias nulas?


El error más frecuente en Java es NullPointerException y me imagino que en otros lenguajes alguno similar...  Para abordar esto, se han introducido estructuras y operadores que ayudan a manejar la ausencia de valores de manera más segura y explícita. 

Por ejemplo en Java se introdujo la clase `Optional` en la versión 8 para manejar valores potencialmente nulos de una manera más segura. `Optional` es un contenedor que puede o no contener un valor no nulo.

import java.util.Optional;


public class OptionalExample {

    public static void main(String[] args) {

        Optional<String> optional = Optional.of("Hello, World!");

        

        // Verificar si hay un valor presente

        if (optional.isPresent()) {

            System.out.println(optional.get());

        }

        

        // Uso del método ifPresent

        optional.ifPresent(System.out::println);

        

        // Proveer un valor predeterminado

        String value = optional.orElse("Default Value");

        System.out.println(value);

        

        // Proveer un valor predeterminado usando un Supplier

        value = optional.orElseGet(() -> "Default Value from Supplier");

        System.out.println(value);

    }

}


Scala utiliza la clase `Option` para representar un valor opcional. `Option` tiene dos subclases: `Some` y `None`, lo que proporciona una forma elegante y funcional de manejar valores que pueden estar ausentes. Esta idea es similar a la monada `Maybe` en Haskell.


object OptionExample extends App {

  val someValue: Option[String] = Some("Hello, World!")

  val noneValue: Option[String] = None


  // Uso de getOrElse

  println(someValue.getOrElse("Default Value"))

  println(noneValue.getOrElse("Default Value"))


  // Uso del patrón de coincidencia (Pattern Matching)

  someValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }


  noneValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }

}


Scala "copio" esta forma de Haskell. Haskell utiliza el tipo de datos `Maybe` para manejar valores opcionales `Maybe` puede ser `Just` un valor o `Nothing`.


main :: IO ()

main = do

    let someValue = Just "Hello, World!"

    let noneValue = Nothing


    -- Uso de fromMaybe

    putStrLn (fromMaybe "Default Value" someValue)

    putStrLn (fromMaybe "Default Value" noneValue)


    -- Uso del patrón de coincidencia (Pattern Matching)

    case someValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


    case noneValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


Kotlin es similar a Scala en muchos aspectos pero no en este. Kotlin introduce el operador `?` para facilitar la gestión de valores nulos. Este operador se utiliza para declarar tipos de datos que pueden ser nulos y para realizar operaciones seguras contra nulos.


fun main() {

    var nullableString: String? = "Hello, World!"


    // Uso del operador ?. para llamadas seguras

    println(nullableString?.length)


    // Uso del operador ?: para proporcionar un valor predeterminado

    val length = nullableString?.length ?: 0

    println(length)


    nullableString = null


    // Uso de let para ejecutar código solo si el valor no es nulo

    nullableString?.let {

        println(it)

    }

}


C# ha incluido varias características para manejar valores nulos, como el operador `?`, que facilita el manejo seguro de tipos que pueden ser nulos.


using System;


class Program

{

    static void Main()

    {

        string? nullableString = "Hello, World!";

        

        // Uso del operador ?. para llamadas seguras

        Console.WriteLine(nullableString?.Length);


        // Uso del operador ?? para proporcionar un valor predeterminado

        int length = nullableString?.Length ?? 0;

        Console.WriteLine(length);


        nullableString = null;


        // Uso de pattern matching para verificar nulos

        if (nullableString is string nonNullString)

        {

            Console.WriteLine(nonNullString);

        }

    }

}


Rust maneja la ausencia de valores y los errores de una manera robusta utilizando los tipos `Option` y `Result`. `Option` puede ser `Some` o `None`, mientras que `Result` puede ser `Ok` o `Err`.


fn main() {

    let some_value: Option<String> = Some("Hello, World!".to_string());

    let none_value: Option<String> = None;


    // Uso de unwrap_or

    println!("{}", some_value.unwrap_or("Default Value".to_string()));

    println!("{}", none_value.unwrap_or("Default Value".to_string()));


    // Uso del patrón de coincidencia (Pattern Matching)

    match some_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }


    match none_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }

}


Go no tiene un tipo de datos específico para manejar valores opcionales, pero utiliza la convención de retornar múltiples valores, incluyendo un valor y un `error`. Que la verdad no me gusta, te pasas preguntando todo el tiempo si hay error o si los valores son nulos. 


package main


import (

    "errors"

    "fmt"

)


func getValue() (string, error) {

    return "Hello, World!", nil

}


func getNullableValue() (string, error) {

    return "", errors.New("no value")

}


func main() {

    value, err := getValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", value)

    }


    nullableValue, err := getNullableValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", nullableValue)

    }

}


Python utiliza la palabra clave `None` para representar la ausencia de valor. Aunque no tiene una estructura específica como `Optional`, los desarrolladores pueden utilizar condicionales y manejo de excepciones.


def get_value():

    return "Hello, World!"


def get_nullable_value():

    return None


value = get_value()

nullable_value = get_nullable_value()


if value is not None:

    print(value)

else:

    print("Default Value")


if nullable_value is not None:

    print(nullable_value)

else:

    print("Default Value")


Ruby utiliza `nil` para representar la ausencia de valor. Al igual que en Python, no tiene una estructura específica para valores opcionales, pero proporciona métodos para manejar `nil`.


value = "Hello, World!"

nullable_value = nil


# Uso del operador ||

puts value || "Default Value"

puts nullable_value || "Default Value"


# Uso de condicionales

puts value.nil? ? "Default Value" : value

puts nullable_value.nil? ? "Default Value" : nullable_value


C++ utiliza punteros inteligentes (`smart pointers`) para gestionar la memoria y prevenir errores relacionados con punteros nulos. Los punteros inteligentes, como `std::unique_ptr` y `std::shared_ptr`, se encargan de la gestión automática de la memoria.


#include <iostream>

#include <memory>


int main() {

    std::unique_ptr<int> uniquePtr(new int(42));

    if (uniquePtr) {

        std::cout << *uniquePtr << std::endl;

    }


    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

    if (sharedPtr) {

        std::cout << *sharedPtr << std::endl;

    }


    // Uso de weak_ptr para evitar ciclos de referencia

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {

        std::cout << *lockedPtr << std::endl;

    }


    return 0;

}


TypeScript, un superconjunto de JavaScript, permite tipos opcionales y tiene un soporte robusto para manejar valores `null` y `undefined`.


let nullableString: string | null = "Hello, World!";


// Uso del operador ? para llamadas seguras

console.log(nullableString?.length ?? 0);


// Uso de if para asegurar valores no nulos

if (nullableString !== null) {

    console.log(nullableString);

}


TypeScript utiliza tipos opcionales para manejar valores que pueden ser `null` o `undefined`, proporcionando un enfoque seguro para evitar errores comunes relacionados con valores nulos. El operador `?.` permite realizar llamadas seguras, y el operador `??` proporciona valores predeterminados en caso de valores `null` o `undefined`.

En fin, aunque la gestión de valores nulos varía entre lenguajes, la idea subyacente es la misma: proporcionar mecanismos más seguros y expresivos para manejar la ausencia de valores. Ya sea mediante clases contenedoras como `Optional` en Java y `Option` en Scala, tipos de datos como `Maybe` en Haskell, operadores específicos como `?` en Kotlin y C#, punteros inteligentes en C++, o enfoques específicos en Rust, Go, Python y Ruby, estos enfoques ayudan a reducir los errores y a escribir un código más robusto y mantenible.


lunes, 20 de mayo de 2024

Lenguajes utilizados en los proyectos apache

Tal vez hace mucho que Apache publico este gráfico pero yo recién lo veo : 



Como se puede ver Java es el lenguaje más utilizado por los proyectos de apache, seguido de python, c++, c, javascript, scala, C#, go, perl, etc ... 




jueves, 16 de mayo de 2024

Hacer un servicio con gRPC y go-zero


Hagamos un hola mundo con go-zero utilizando gRPC. Para empezar vamos a hacer el archivo .proto : 

syntax = "proto3";


package hello;


option go_package = "./hello";


message Request {

}


message Response {

  string msg = 1;

}


service Hello {

  rpc Ping(Request) returns(Response);

}


$ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server

Done.

luego hacemos : 

cd server

go mod tidy 


Completamos el archivo server/internal/logic/pinglogic.go


func (l *PingLogic) Ping(in *hello.Request) (*hello.Response, error) {

return &hello.Response{ Msg: "pong" }, nil

}


y luego en el archivo server/etc/hello.yaml agregamos que estamos trabajando en modo dev: 


Name: hello.rpc
ListenOn: 0.0.0.0:8080
Mode: dev

y por ultimo corremos el proyecto: 

go run hello.go



miércoles, 6 de marzo de 2024

Primera API con Go-zero


Después de completar la instalación de goctl, podemos crear un servicio HTTP mínimo para obtener una descripción general del servicio API go-zero de goctl.


# Create workspaces and enter the directory

$ mkdir -p ~/workspace/api && cd ~/workspace/api

# Execute instructions generated demo service

$ goctl api new demo

Done.


Después de ejecutar la instrucción, se generará un directorio de demostración en el directorio actual que contiene un servicio HTTP minimizado, verificaremos la estructura del directorio del servicio.


$ cd ~/workspace/api/demo

$ ls

demo.api demo.go  etc      go.mod   internal

$ tree

.

├── demo.api

├── demo.go

├── etc

│   └── demo-api.yaml

├── go.mod

└── internal

    ├── config

    │   └── config.go

    ├── handler

    │   ├── demohandler.go

    │   └── routes.go

    ├── logic

    │   └── demologic.go

    ├── svc

    │   └── servicecontext.go

    └── types

        └── types.go


Después de completar la generación del código anterior, podemos encontrar los archivos ~/workspace/api/demo/internal/logic/demologic.go, y podemos completar el códigos entre las líneas 27 y 28:

resp = new(types.Response)

resp.Message = req.Name


Después de escribir el código anterior, podemos iniciar el servicio con las siguientes instrucciones:


# Enter service directory

$ cd ~/workspace/api/demo

# to organize dependencies

$ go mod tidy

# Run go program

$ go run demo.go


Cuando vea el siguiente resultado "Starting server at 0.0.0.0.0:888.." indica que el servicio se ha iniciado correctamente, entonces visitamos el servicio HTTP.


$ curl --request GET 'http://127.0.0.0.1:8888/from/me'


Cuando vea el resultado en la terminal {"message":"me"} en nombre de su servicio se inició con éxito.


Y listo!!

Dejo link: https://go-zero.dev/en/docs/tasks/cli/api-demo


domingo, 25 de febrero de 2024

Instalando go zero

 


Ya hemos hablado de zero el framework de go para hacer microservicios. Ahora vamos a bajar las herramientas para trabajar con él. 

Primero, tenemos que tener go instalado. Yo tengo la versión 1.22.0

$ go version

go version go1.22.0 linux/amd64


Después de la versión 1.11, se recomienda que el valor GO111MODULE se establezca en on para evitar errores innecesarios.

$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct

$ go env GO111MODULE
on
$ go env GOPROXY
https://goproxy.cn,direct

goctl es una herramienta incorporada de go-zero que es importante para aumentar la eficiencia del desarrollo, generar código, documentos, implementar k8s yaml, dockerfile, etc.

$ go install github.com/zeromicro/go-zero/tools/goctl@latest
go: downloading github.com/gookit/color v1.5.4
go: downloading github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
go: downloading github.com/spf13/cobra v1.8.0
...

$ goctl -version
goctl version 1.6.2 linux/amd64

Ahora tenemos que installar docker. Yo ya tengo docker instalado por lo tanto siguo: 

$ sudo docker pull kevinwan/goctl
$ sudo docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help

Y si todo anda bien hacemos : 

$ sudo docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest goctl --version
goctl version 1.3.5 linux/amd64

protoc es una herramienta para generar código basado en archivos proto, este genern código en múltiples lenguajes como C++, Java, Python, Go, PHP, etc. para nuestros servicios gRPC. Lo tenemos que instalar, en mi caso yo lo tenia instalado y lo sé porque hice : 

$ goctl env check --install --verbose --force
[goctl-env]: preparing to check env

[goctl-env]: looking up "protoc"
[goctl-env]: "protoc" is installed

[goctl-env]: looking up "protoc-gen-go"
[goctl-env]: "protoc-gen-go" is installed

[goctl-env]: looking up "protoc-gen-go-grpc"
[goctl-env]: "protoc-gen-go-grpc" is installed

[goctl-env]: congratulations! your goctl environment is ready!

Y ahora por fin, vamos a crear nuestro proyecto go-zero!! 

Creamos el proyecto: 
$ mkdir myapp && cd myapp
$ go mod init myapp
$ go get github.com/zeromicro/go-zero@latest
go: added github.com/zeromicro/go-zero v1.6.2


Y Listo!! 


sábado, 3 de febrero de 2024

Printing en Go


La impresión formateada en Go utiliza un estilo similar a la familia printf de C, pero es más rica y más general. Las funciones se encuentran en el paquete fmt y tienen nombres en mayúscula: fmt.Printf, fmt.Fprintf, fmt.Sprintf, etc. Las funciones de cadena (Sprintf, etc.) devuelven una cadena en lugar de enviar la cadena a un buffer determinado.

No es necesario proporcionar un formato. Para cada uno de Printf, Fprintf y Sprintf hay otro par de funciones, por ejemplo Print y Println. Estas funciones no toman una cadena de formato sino que generan un formato predeterminado para cada argumento. Las versiones Println también insertan un espacio en blanco entre los argumentos y agregan una nueva línea a la salida, mientras que las versiones Print agregan espacios en blanco solo si el operando en ninguno de los lados es una cadena. En este ejemplo, cada línea produce el mismo resultado.


fmt.Printf("Hello %d\n", 23)

fmt.Fprint(os.Stdout, "Hello ", 23, "\n")

fmt.Println("Hello", 23)

fmt.Println(fmt.Sprint("Hello ", 23))


Las funciones de impresión formateadas fmt.Fprint y las demás toman como primer argumento cualquier objeto que implemente la interfaz io.Writer; por ejemplo os.Stdout y os.Stderr.

Aquí las cosas empiezan a diferir de C. Primero, los formatos numéricos como %d no aceptan indicadores de signo o tamaño; en cambio, las rutinas de impresión utilizan el tipo de argumento para decidir estas propiedades.


var x uint64 = 1<<64 - 1

fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))


Esto imprime: 


18446744073709551615 ffffffffffffffff; -1 -1


Si solo desea la conversión predeterminada, como decimal para números enteros, puede usar el formato general %v (para “valor”); el resultado es exactamente lo que producirían Print y Println. Además, ese formato puede imprimir cualquier valor, incluso matrices, sectores, estructuras y mapas. Aquí hay una declaración impresa para el mapa de zona horaria.


fmt.Printf("%v\n", timeZone)  // or just fmt.Println(timeZone)


lo que da salida:


map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]


Para los mapas, Printf y las demás clasifican la salida lexicográficamente por clave.


Al imprimir una estructura, el formato modificado %+v anota los campos de la estructura con sus nombres y, para cualquier valor, el formato alternativo %#v imprime el valor en la sintaxis Go completa.


type T struct {

    a int

    b float64

    c string

}

t := &T{ 7, -2.35, "abc\tdef" }

fmt.Printf("%v\n", t)

fmt.Printf("%+v\n", t)

fmt.Printf("%#v\n", t)

fmt.Printf("%#v\n", timeZone)


imprime : 



&{7 -2.35 abc   def}

&{a:7 b:-2.35 c:abc     def}

&main.T{a:7, b:-2.35, c:"abc\tdef"}

map[string]int{"CST":-21600, "EST":-18000, "MST":-25200, "PST":-28800, "UTC":0}


(Tenga en cuenta los símbolos). Ese formato de cadena entre comillas también está disponible a través de %q cuando se aplica a un valor de tipo cadena o []byte. El formato alternativo %#q utilizará comillas inversas si es posible. (El formato %q también se aplica a números enteros y runas, lo que produce una constante rúnica entre comillas simples). Además, %x funciona en cadenas, matrices de bytes y porciones de bytes, así como en números enteros, generando una cadena hexadecimal larga y con un espacio. en el formato (% x) pone espacios entre los bytes.


Otro formato útil es %T, que imprime el tipo de un valor.


fmt.Printf("%T\n", timeZone)


imprime: 


map[string]int


Si desea controlar el formato predeterminado para un tipo personalizado, todo lo que se requiere es definir un método con la cadena de firma String() en el tipo. Para nuestro tipo T simple, podría verse así.


func (t *T) String() string {

    return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)

}

fmt.Printf("%v\n", t)


para imprimir en el formato


7/-2.35/"abc\tdef"


(Si necesita imprimir valores de tipo T así como punteros a T, el receptor de String debe ser de tipo valor; este ejemplo usó un puntero porque es más eficiente e idiomático para tipos de estructuras).

Nuestro método String puede llamar a Sprintf porque las rutinas de impresión son completamente reentrantes y se pueden empaquetar de esta manera. Sin embargo, hay un detalle importante que debe comprender acerca de este enfoque: no construya un método String llamando a Sprintf de una manera que se repetirá en su método String indefinidamente. Esto puede suceder si la llamada de Sprintf intenta imprimir el receptor directamente como una cadena, lo que a su vez invocará el método nuevamente. Es un error común y fácil de cometer, como muestra este ejemplo.


type MyString string


func (m MyString) String() string {

    return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.

}


También es fácil de solucionar: convierta el argumento al tipo de cadena básica, que no tiene el método.


type MyString string

func (m MyString) String() string {

    return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.

}


Otra técnica de impresión consiste en pasar los argumentos de una rutina de impresión directamente a otra rutina similar. La firma de Printf usa el tipo ...interfaz{} como argumento final para especificar que un número arbitrario de parámetros (de tipo arbitrario) puede aparecer después del formato.


func Printf(format string, v ...interface{}) (n int, err error) {


Dentro de la función Printf, v actúa como una variable de tipo []interfaz{} pero si se pasa a otra función variable, actúa como una lista normal de argumentos. Aquí está la implementación de la función log.Println que usamos anteriormente. Pasa sus argumentos directamente a fmt.Sprintln para el formato real.


// Println prints to the standard logger in the manner of fmt.Println.

func Println(v ...interface{}) {

    std.Output(2, fmt.Sprintln(v...))  // Output takes parameters (int, string)

}


Escribimos ... después de v en la llamada anidada a Sprintln para decirle al compilador que trate a v como una lista de argumentos; de lo contrario, simplemente pasaría v como argumento de un solo segmento.

gRPC Client


De manera similar al lado del servidor, podemos generar el código del lado del cliente utilizando la definición del servicio. El código del cliente proporciona los mismos métodos que el servidor, que su código de cliente puede invocar; el código del cliente los traduce en llamadas de red de invocación de funciones remotas que van al lado del servidor. Dado que las definiciones de servicios gRPC son independientes del lenguaje, puede generar clientes y servidores para cualquier lenguaje admitido (a través de implementaciones de terceros) de su elección. 

Entonces, veamos un ejemplo en Java. A pesar del lenguaje de programación que utilizamos, los pasos simples involucrados en una implementación del lado del cliente implican configurar una conexión con el servidor remoto, adjuntar el código auxiliar del cliente a esa conexión e invocar el método remoto usando el código auxiliar del cliente.


ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)

.usePlaintext(true)

 .build(); 


// Initialize blocking stub using the channel 

ProductInfoGrpc.ProductInfoBlockingStub stub = ProductInfoGrpc.newBlockingStub(channel); 


// Call remote method using the blocking stub 

StringValue productID = stub.addProduct( 

     Product.newBuilder() 

            .setName("Apple iPhone 11") 

            .setDescription("Meet Apple iPhone 11." + "All-new dual-camera system with " + "Ultra Wide and Night mode.") 

   .build());



martes, 23 de enero de 2024

gRPC Server


Una vez que tengamos una definición de servicio (un archivo .proto), podemos usarla para generar el código del lado del servidor o del cliente usando el protocolo del compilador del búfer de protocolo. Con el complemento gRPC para búferes de protocolo, puede generar código del lado del servidor y del lado del cliente de gRPC, así como el código del búfer de protocolo normal para completar, serializar y recuperar sus tipos de mensajes.

En el lado del servidor, el servidor implementa esa definición de servicio y ejecuta un servidor gRPC para manejar las llamadas de los clientes. Por lo tanto, para generar un servicio gRPC necesitamos:

1. Implementar la lógica del servicio, en el esqueleto del la clase generada. 

2. Ejecute un servidor gRPC para escuchar las solicitudes de los clientes y devolver las respuestas del servicio.

Al implementar la lógica del servicio, lo primero que debe hacer es generar el esqueleto del servicio a partir de la definición del servicio. Por ejemplo:


import (

...

"context"

pb "github.com/grpc-up-and-running/samples/ch02/productinfo/go/proto"

"google.golang.org/grpc"

...

)

// ProductInfo implementation with Go

// Add product remote method

func (s *server) AddProduct(ctx context.Context, in *pb.Product) (

*pb.ProductID, error) {

// business logic

}

// Get product remote method

func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (

*pb.Product, error) {

// business logic

}


Dentro del cuerpo de estas funciones remotas puedes implementar la lógica de cada función.

Una vez que tengamos lista la implementación del servicio, debe ejecutar un servidor gRPC para escuchar las solicitudes de los clientes, enviar esas solicitudes a la implementación del servicio y devolver las respuestas del servicio al cliente. Por ejemplo:  

func main() {
    lis, _ := net.Listen("tcp", port)
    s := grpc.NewServer()
    pb.RegisterProductInfoServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

En el ejemplo anterior se muestra una implementación de servidor gRPC con Go para el caso de uso del servicio ProductInfo. Aquí abrimos un puerto TCP, iniciamos el servidor gRPC y registramos el servicio ProductInfo con ese servidor. Y eso es todo lo que tienes que hacer en el lado del servidor. 


lunes, 15 de enero de 2024

Go Patterns


Estas estudiando Golang y queres aprender patrones de diseño? bueno, te dejo este recurso que te va a venir super bien. 


Dejo link: https://github.com/tmrts/go-patterns

martes, 9 de enero de 2024

Primer servicio GRpc con Go


Vamos a hacer un servicio que salude en Go y GRpc. Lo primero que tenemos que hacer es instalar las dependencias: 

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

go: downloading google.golang.org/protobuf v1.28.1

$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

go: downloading google.golang.org/grpc v1.2.1


Luego escribir el .proto : 

syntax = "proto3";


option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";

option java_multiple_files = true;

option java_package = "io.grpc.examples.helloworld";

option java_outer_classname = "HelloWorldProto";


package helloworld;


// The greeting service definition.

service Greeter {

  // Sends a greeting

  rpc SayHello (HelloRequest) returns (HelloReply) {}

}


// The request message containing the user's name.

message HelloRequest {

  string name = 1;

}


// The response message containing the greetings

message HelloReply {

  string message = 1;

}

Este archivo, es un descriptor del servicio y nos permite generar código con el comando protoc, por ejemplo: 

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./greetings/greetings.proto


Y luego podemos escribir el cliente y el servidor. Por ejemplo: 

Server: 

package main


import (

"context"

"flag"

"fmt"

"log"

"net"


"google.golang.org/grpc"

pb "google.golang.org/grpc/examples/helloworld/helloworld"

)


var (

port = flag.Int("port", 50051, "The server port")

)


// server is used to implement helloworld.GreeterServer.

type server struct {

pb.UnimplementedGreeterServer

}


// SayHello implements helloworld.GreeterServer

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {

log.Printf("Received: %v", in.GetName())

return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil

}


func main() {

flag.Parse()

lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))

if err != nil {

log.Fatalf("failed to listen: %v", err)

}

s := grpc.NewServer()

pb.RegisterGreeterServer(s, &server{})

log.Printf("server listening at %v", lis.Addr())

if err := s.Serve(lis); err != nil {

log.Fatalf("failed to serve: %v", err)

}

}


y el cliente: 


package main


import (

"context"

"flag"

"log"

"time"


"google.golang.org/grpc"

"google.golang.org/grpc/credentials/insecure"

pb "google.golang.org/grpc/examples/helloworld/helloworld"

)


const (

defaultName = "world"

)


var (

addr = flag.String("addr", "localhost:50051", "the address to connect to")

name = flag.String("name", defaultName, "Name to greet")

)


func main() {

flag.Parse()

// Set up a connection to the server.

conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))

if err != nil {

log.Fatalf("did not connect: %v", err)

}

defer conn.Close()

c := pb.NewGreeterClient(conn)


// Contact the server and print out its response.

ctx, cancel := context.WithTimeout(context.Background(), time.Second)

defer cancel()

r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})

if err != nil {

log.Fatalf("could not greet: %v", err)

}

log.Printf("Greeting: %s", r.GetMessage())

}


Y listo!

Para probarlo, ejecutamos el server : 

$ go run greeter_server/main.go


Y luego el cliente : 

$ go run greeter_client/main.go


Dejo link: https://grpc.io/docs/languages/go/quickstart/


jueves, 21 de diciembre de 2023

Maps en Go


Los maps son una estructura de datos que asocia valores de un tipo (la clave) con valores de otro tipo (el elemento o valor). La clave puede ser de cualquier tipo para el que esté definido el operador de igualdad, como números enteros, números de punto flotante y complejos, cadenas, punteros, interfaces (siempre que el tipo dinámico admita la igualdad), estructuras y matrices. Los slices no se pueden utilizar como claves de map porque la igualdad no está definida en ellos. Al igual que los slices, los map contienen referencias a una estructura de datos subyacente. Si pasa un map a una función que cambia el contenido del map, los cambios serán visibles en la persona que llama.

Los map se pueden construir utilizando la sintaxis literal compuesta habitual con pares clave-valor separados por dos puntos, por lo que es fácil construirlos durante la inicialización.0


var timeZone = map[string]int{

    "UTC":  0*60*60,

    "EST": -5*60*60,

    "CST": -6*60*60,

    "MST": -7*60*60,

    "PST": -8*60*60,

}


Asignar y recuperar valores de maps parece sintácticamente igual que hacer lo mismo con matrices y slices, excepto que no es necesario que el índice sea un número entero.


offset := timeZone["EST"]


Un intento de recuperar un valor de mapa con una clave que no está presente en el mapa devolverá el valor cero para el tipo de entradas en el mapa. Por ejemplo, si el mapa contiene números enteros, buscar una clave inexistente devolverá 0. Un conjunto se puede implementar como un mapa con valor de tipo bool. 

attended := map[string]bool{

    "Ann": true,

    "Joe": true,

    ...

}


if attended[person] { // will be false if person is not in the map

    fmt.Println(person, "was at the meeting")

}


A veces es necesario distinguir una entrada faltante de un valor cero. ¿Hay una entrada para "UTC" o es 0 porque no está en el mapa? Se puede discriminar con una forma de asignación múltiple.


var seconds int

var ok bool

seconds, ok = timeZone[tz]


Por razones obvias, esto se denomina modismo "coma ok". En este ejemplo, si tz está presente, los segundos se configurarán apropiadamente y ok será verdadero; de lo contrario, los segundos se establecerán en cero y ok será falso. Aquí hay una función que lo combina con un bonito informe de errores:


func offset(tz string) int {

    if seconds, ok := timeZone[tz]; ok {

        return seconds

    }

    log.Println("unknown time zone:", tz)

    return 0

}


Para probar la presencia en el mapa sin preocuparse por el valor real, puede utilizar el identificador en blanco (_) en lugar de la variable habitual para el valor.


_, present := timeZone[tz]


Para eliminar una entrada de mapa, utilice la función incorporada de eliminación, cuyos argumentos son el mapa y la clave que se va a eliminar. Es seguro hacer esto incluso si la clave ya no está en el mapa.


delete(timeZone, "PDT")  // Now on Standard Time


miércoles, 13 de diciembre de 2023

¿Qué es Go-Zero?

 


En el mundo del desarrollo de software, la velocidad, la eficiencia y la simplicidad son pilares fundamentales. El framework Go-Zero emerge como una solución poderosa para aquellos que buscan crear aplicaciones escalables y de alto rendimiento utilizando el lenguaje de programación Go (Golang).


Pero ¿Qué es Go-Zero? Go-Zero es un framework moderno y de código abierto diseñado para acelerar el proceso de desarrollo de aplicaciones en Go. Ofrece una arquitectura robusta y flexible, proporcionando herramientas y patrones que permiten construir aplicaciones web, API y microservicios de manera eficiente.


Características Principales:

  • Alto rendimiento: Go-Zero se destaca por su capacidad para manejar cargas de trabajo intensivas y mantener un alto rendimiento incluso en entornos de gran escala.
  • Productividad mejorada: Proporciona abstracciones y utilidades que reducen la complejidad del código, permitiendo a los desarrolladores enfocarse en la lógica de negocio en lugar de detalles de implementación.
  • Facilidad de uso: Con una curva de aprendizaje amigable, su estructura modular y su amplia documentación, Go-Zero facilita a los desarrolladores tanto principiantes como experimentados.
  • Soporte para microservicios: Ofrece herramientas específicas para la construcción de arquitecturas de microservicios, simplificando la comunicación entre ellos y la gestión de datos distribuidos.
Veamos un ejemplo básico de cómo se podría crear un servidor HTTP simple utilizando el framework Go-Zero:

package main

import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rest/httpx"
)

type Request struct {
Name string `form:"name"`
}

type Response struct {
Message string `json:"message"`
}

func helloHandler(w httpx.ResponseWriter, r *httpx.Request) {
var req Request
if err := r.ParseForm(&req); err != nil {
w.Error(httpx.BadRequest(err.Error()))
return
}

resp := Response{
Message: "Hello, " + req.Name + "!",
}
w.WriteJson(resp)
}

func main() {
var c rest.RestConf
conf.MustLoad("path/to/config.yaml", &c)

server := rest.MustNewServer(c)
server.AddRoute(rest.Route{
Method:  "GET",
Path:    "/hello",
Handler: helloHandler,
})
defer server.Stop()

server.Start()
}


Go-Zero representa una opción valiosa para aquellos que buscan desarrollar aplicaciones en Go de manera rápida, eficiente y escalable. Su enfoque en el rendimiento y la productividad lo convierten en una herramienta atractiva para proyectos de diversos tamaños y complejidades.

Dejo link: https://github.com/zeromicro/go-zero

martes, 5 de diciembre de 2023

Slices bidimensionales en Go


Las matrices y Slices de Go son unidimensionales. Para crear el equivalente de una matriz o un Slice 2D, es necesario definir un conjunto de matrices o un Slice de Slices, como este:


type Transform [3][3]float64  // A 3x3 array, really an array of arrays.

type LinesOfText [][]byte     // A slice of byte slices.


Debido a que los Slices tienen una longitud variable, es posible que cada Slice interno tenga una longitud diferente. Esa puede ser una situación común, como en nuestro ejemplo de LinesOfText: cada línea tiene una longitud independiente.


text := LinesOfText{

    []byte("Now is the time"),

    []byte("for all good gophers"),

    []byte("to bring some fun to the party."),

}

A veces es necesario asignar un Slice 2D, una situación que puede surgir al procesar líneas de escaneo de píxeles, por ejemplo. Hay dos formas de lograrlo. Una es asignar cada porción de forma independiente; la otra es asignar una única matriz y apuntar los sectores individuales hacia ella. Cuál usar depende de su aplicación. Si los sectores pueden crecer o reducirse, deben asignarse de forma independiente para evitar sobrescribir la siguiente línea; de lo contrario, puede ser más eficiente construir el objeto con una única asignación. Como referencia, aquí hay bocetos de los dos métodos. Primero, una línea a la vez:


// Allocate the top-level slice.

picture := make([][]uint8, YSize) // One row per unit of y.

// Loop over the rows, allocating the slice for each row.

for i := range picture {

    picture[i] = make([]uint8, XSize)

}


Y ahora como una asignación, dividida en líneas:


// Allocate the top-level slice, the same as before.

picture := make([][]uint8, YSize) // One row per unit of y.

// Allocate one large slice to hold all the pixels.

pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.

// Loop over the rows, slicing each row from the front of the remaining pixels slice.

for i := range picture {

    picture[i], pixels = pixels[:XSize], pixels[XSize:]

}

jueves, 30 de noviembre de 2023

Testear endpoints en go con echo

Supongamos que tenemos unos endpoints hechos con echo:

package handler


import (

    "net/http"

    "github.com/labstack/echo/v4"

)


type (

    User struct {

        Name  string `json:"name" form:"name"`

        Email string `json:"email" form:"email"`

    }

    handler struct {

        db map[string]*User

    }

)


func (h *handler) createUser(c echo.Context) error {

    u := new(User)

    if err := c.Bind(u); err != nil {

        return err

    }

    return c.JSON(http.StatusCreated, u)

}


func (h *handler) getUser(c echo.Context) error {

    email := c.Param("email")

    user := h.db[email]

    if user == nil {

        return echo.NewHTTPError(http.StatusNotFound, "user not found")

    }

    return c.JSON(http.StatusOK, user)

}


Lo que queremos hacer es un test de unidad que testee este comportamiento, y podemos hacerlo así: 


package handler


import (

    "net/http"

    "net/http/httptest"

    "strings"

    "testing"


    "github.com/labstack/echo/v4"

    "github.com/stretchr/testify/assert"

)


var (

    mockDB = map[string]*User{

        "jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},

    }

    userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`

)


func TestCreateUser(t *testing.T) {

    // Setup

    e := echo.New()

    req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))

    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)

    rec := httptest.NewRecorder()

    c := e.NewContext(req, rec)

    h := &handler{mockDB}


    // Assertions

    if assert.NoError(t, h.createUser(c)) {

        assert.Equal(t, http.StatusCreated, rec.Code)

        assert.Equal(t, userJSON, rec.Body.String())

    }

}


func TestGetUser(t *testing.T) {

    // Setup

    e := echo.New()

    req := httptest.NewRequest(http.MethodGet, "/", nil)

    rec := httptest.NewRecorder()

    c := e.NewContext(req, rec)

    c.SetPath("/users/:email")

    c.SetParamNames("email")

    c.SetParamValues("jon@labstack.com")

    h := &handler{mockDB}


    // Assertions

    if assert.NoError(t, h.getUser(c)) {

        assert.Equal(t, http.StatusOK, rec.Code)

        assert.Equal(t, userJSON, rec.Body.String())

    }

}


Y listo!! 


Dejo link: https://echo.labstack.com/docs/testing

lunes, 13 de noviembre de 2023

Slices en golang


Los Slices envuelven matrices para brindar una interfaz más general, poderosa y conveniente para secuencias de datos. Excepto por elementos con dimensiones explícitas, como matrices de transformación, la mayor parte de la programación de matrices en Go se realiza con Slices en lugar de simples arrays.

Los Slices contienen referencias a una matriz subyacente y, si asigna un Slice a otro, ambos se refieren a la misma matriz. Si una función toma un argumento de tipo Slice, los cambios que realice en los elementos del segmento serán visibles para quien llama, de forma análoga a pasar un puntero a la matriz subyacente. Por lo tanto, una función de lectura puede aceptar un argumento de tipo Slice en lugar de un puntero y una dimensión; la longitud dentro del segmento establece un límite superior de la cantidad de datos que se leerán. Aquí está la firma del método de lectura del tipo de archivo en el paquete os:


func (f *File) Read(buf []byte) (n int, err error)


El método devuelve el número de bytes leídos y un valor de error, si lo hubiera. Para leer los primeros 32 bytes hacemos: 


  n, err := f.Read(buf[0:32])


Este Slice es común y eficiente. De hecho, dejando de lado la eficiencia por el momento, el siguiente fragmento también leería los primeros 32 bytes del búfer.


    var n int

    var err error

    for i := 0; i < 32; i++ {

        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.

        n += nbytes

        if nbytes == 0 || e != nil {

            err = e

            break

        }

    }


La longitud de un segmento se puede cambiar siempre que todavía se ajuste dentro de los límites de la matriz subyacente; simplemente asígnalo a una Slice de sí mismo. La capacidad de un Slice es accesible mediante una función incorporada, informa la longitud máxima que puede asumir el segmento. Aquí hay una función para agregar datos a un slice. Si los datos exceden la capacidad, se reasigna el slice y se devuelve el slice resultante. La función utiliza el hecho de que len y cap son legales cuando se aplican a segmentos nulos y devuelven 0.


func Append(slice, data []byte) []byte {

    l := len(slice)

    if l + len(data) > cap(slice) {  // reallocate

        // Allocate double what's needed, for future growth.

        newSlice := make([]byte, (l+len(data))*2)

        // The copy function is predeclared and works for any slice type.

        copy(newSlice, slice)

        slice = newSlice

    }

    slice = slice[0:l+len(data)]

    copy(slice[l:], data)

    return slice

}


Debemos devolver el slice después porque, aunque Append puede modificar los elementos del slice, el slice en sí (la estructura de datos en tiempo de ejecución que contiene el puntero, la longitud y la capacidad) se pasa por valor.

La idea de agregar un elemento a un slice es tan util que tenemos una función para hacerlo:  append. Sin embargo, para comprender el diseño de esa función necesitamos un poco más de información, por lo que volveremos a ello más adelante.

viernes, 3 de noviembre de 2023

Asignación de memoria con make en Go


La función make(T, args) tiene un propósito diferente al de new(T). Crea únicamente slices, maps, y channels y devuelve un valor inicializado (no puesto a cero) de tipo T (no *T). El motivo de la distinción es que estos tres tipos representan, encubiertamente, referencias a estructuras de datos que deben inicializarse antes de su uso. Un slice, por ejemplo, es un descriptor de tres elementos que contiene un puntero a los datos (dentro de un arreglo o vector), la longitud y la capacidad, y hasta que esos elementos se inicialicen, el slice es nulo. Para slices, map y chanels, make inicializa la estructura de datos interna y prepara el valor para su uso. Por ejemplo,


make([]int, 10, 100)


asigna una arreglo de entero de 100 elementos y luego crea una estructura slice con una longitud de 10 y una capacidad de 100 que apunta a los primeros 10 elementos de la matriz. (Al crear un slice, se puede omitir la capacidad). Por el contrario, new([]int) devuelve un puntero a una estructura de slice puesta a cero recién asignada, es decir, un puntero a un slice nulo.

Estos ejemplos ilustran la diferencia entre nuevo y fabricado.


var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful

var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints


// Unnecessarily complex:

var p *[]int = new([]int)

*p = make([]int, 100, 100)


// Idiomatic:

v := make([]int, 100)


Recuerde que make se aplica solo a maps, slices y channels y no devuelve un puntero. Para obtener un puntero explícito, asigne nuevo o tome la dirección de una variable explícitamente.

miércoles, 1 de noviembre de 2023

Test en Go


Go ofrece soporte integrado para pruebas unitarias que hace que sea más fácil realizar pruebas sobre la marcha. Específicamente, utilizando las convenciones de nomenclatura, el paquete de pruebas de Go y el comando go test, puede escribir y ejecutar pruebas rápidamente.

Veamos un ejemplo, vamos a hacer un archivo greetings con el siguiente contenido : 

package greetings


import "fmt"


// Hello returns a greeting for the named person.

func Hello(name string) (message string) {

// Return a greeting that embeds the name in a message.

if name == "" {

message = "Hi!"

} else {

message = fmt.Sprintf("Hi, %v. Welcome!", name)

}

return message

}


En el directorio greetings, crearemos un archivo llamado greetings_test.go, que son las pruebas. Terminar el nombre de un archivo con _test.go le indica al comando go test que este archivo contiene test.

En Greetings_test.go, vamos a pegar el siguiente código y guardar el archivo.

package greetings


import (

"regexp"

"testing"

)


// TestHelloName calls greetings.Hello with a name, checking

// for a valid return value.

func TestHelloName(t *testing.T) {

name := "Gladys"

want := regexp.MustCompile(`\b` + name + `\b`)

msg := Hello("Gladys")

if !want.MatchString(msg) {

t.Fatalf(`Hello("Gladys") = %q, %v`, msg, want)

}

}


// TestHelloEmpty calls greetings.Hello with an empty string.

func TestHelloEmpty(t *testing.T) {

msg := Hello("")

if msg != "Hi!" {

t.Fatalf(`Hello("") = %q, want "Hi!"`, msg)

}

}


En este código, se puede ver que utilizamos el paquete "test" para hacer uso de función "Fatalf"  que indica que la función no pasa el test. 

Si ejecutamos go test en el directorio obtendremos : 

$ go test

PASS

ok      example.com/greetings   0.364s


$ go test -v

=== RUN   TestHelloName

--- PASS: TestHelloName (0.00s)

=== RUN   TestHelloEmpty

--- PASS: TestHelloEmpty (0.00s)

PASS

ok      example.com/greetings   0.372s


Y eso es todo! 

Dejo link : https://go.dev/doc/tutorial/add-a-test




Arrays en Go


Los arrays son útiles al planificar el diseño detallado de la memoria y, a veces, pueden ayudar a evitar la asignación, pero principalmente son un bloque de construcción para los slice.

Existen grandes diferencias entre la forma en que funcionan los arrys en Go y C. En Go,

  • Los arrys son valores. Asignar un array a otra copia todos los elementos.
  • En particular, si pasa una matriz a una función, recibirá una copia de la matriz, no un puntero a ella.
  • El tamaño de una matriz es parte de su tipo. Los tipos [10]int y [20]int son distintos.

Estas propiedades puede ser útiles pero también costosa; Si desea un comportamiento y eficiencia similares a los de C, puede pasar un puntero a la matriz.


func Sum(a *[3]float64) (sum float64) {

    for _, v := range *a {

        sum += v

    }

    return

}


array := [...]float64{7.0, 8.5, 9.1}

x := Sum(&array)  // Note the explicit address-of operator


Pero este código no sigue el estilo Go, podemos hacer algo similar con Slice que veremos más adelante. 

martes, 31 de octubre de 2023

Constructores y literales compuestos


A veces el valor cero no es suficiente y es necesario un constructor de inicialización, como en este ejemplo derivado del paquete os.


func NewFile(fd int, name string) *File {

    if fd < 0 {

        return nil

    }

    f := new(File)

    f.fd = fd

    f.name = name

    f.dirinfo = nil

    f.nepipe = 0

    return f

}


Hay mucho texto en este codigo que podemos simplificarlo usando un literal compuesto, que es una expresión que crea una nueva instancia:


func NewFile(fd int, name string) *File {

    if fd < 0 {

        return nil

    }

    f := File{fd, name, nil, 0}

    return &f

}


Tenemos que tener en cuenta que, a diferencia de C, está perfectamente bien devolver la dirección de una variable local; el almacenamiento asociado con la variable sobrevive después de que regresa la función. De hecho, al tomar la dirección de un literal compuesto se asigna una instancia nueva cada vez que se evalúa, por lo que podemos combinar estas dos últimas líneas: 


return &File{fd, name, nil, 0}


Los campos de un literal compuesto están ordenados y todos deben estar presentes. Sin embargo, al etiquetar los elementos explícitamente como pares campo:valor, los inicializadores pueden aparecer en cualquier orden, dejando los que faltan con sus respectivos valores cero. Así podríamos decir


return &File{fd: fd, name: name}


Como caso límite, si un literal compuesto no contiene ningún campo, crea un valor cero para el tipo. Las expresiones new(File) y &File{} son equivalentes.

También se pueden crear literales compuestos para matrices, slices y mapas, siendo las etiquetas de campo índices o claves de mapa, según corresponda. En estos ejemplos, las inicializaciones funcionan independientemente de los valores de Enone, Eio y Einval, siempre que sean distintos.


a := [...]string   {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

s := []string      {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}