Translate

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"}




lunes, 23 de octubre de 2023

Asignación con new en Go


Go tiene dos primitivas de asignación de memoria, las funciones integradas new y make. Hacen cosas diferentes y se aplican a diferentes tipos, lo que puede resultar confuso, pero las reglas son simples. Hablemos primero de new. Es una función incorporada que asigna memoria, pero a diferencia de sus homónimos en otros lenguajes, no inicializa la memoria, solo la pone a cero. Es decir, new(T) asigna almacenamiento puesto a cero para un nuevo elemento de tipo T y devuelve su dirección, un valor de tipo *T. En la terminología de Go, devuelve un puntero a un valor cero recién asignado de tipo T.

Dado que la memoria devuelta por new se pone a cero, es útil disponer al diseñar las estructuras de datos que el valor cero de cada tipo se pueda usar sin inicialización adicional. Esto significa que un usuario de la estructura de datos puede crear una nueva y ponerse manos a la obra. Por ejemplo, la documentación de bytes.Buffer indica que "el valor cero de Buffer es un búfer vacío listo para usar". De manera similar, sync.Mutex no tiene un constructor explícito ni un método Init. En cambio, el valor cero para sync.Mutex se define como un mutex desbloqueado.

La propiedad del valor cero es útil funciona de forma transitiva. Considere este tipo de declaración.


type SyncedBuffer struct {

    lock    sync.Mutex

    buffer  bytes.Buffer

}


Los valores de tipo SyncedBuffer también están listos para usar inmediatamente después de la asignación o simplemente de la declaración. En el siguiente fragmento, tanto p como v funcionarán correctamente sin más arreglos.


p := new(SyncedBuffer)  // type *SyncedBuffer

var v SyncedBuffer      // type  SyncedBuffer


domingo, 22 de octubre de 2023

impl Trait en rust


De manera similar a trait bounds, se puede usar una sintaxis de trait implícita en argumentos de funciones y valores de retorno:

use std::fmt::Display;

fn get_x(name: impl Display) -> impl Display {

    format!("Hello {name}")

}


fn main() {

    let x = get_x("foo");

    println!("{x}");

}


El significado de impl Trait es un poco diferente en las diferentes posiciones.

Para un parámetro, impl Trait es como un parámetro genérico anónimo con un trait vinculado.

Para un tipo de retorno, significa que el tipo de retorno es algún tipo concreto que implementa el trait, sin nombrar el tipo. Esto puede resultar útil cuando no desea exponer el tipo concreto en una API pública.

La inferencia es difícil en la posición de retorno. Una función que devuelve impl Foo elige el tipo concreto que devuelve, sin escribirlo en el código fuente. Una función que devuelve un tipo genérico como recopilar<B>() -> B puede devolver cualquier tipo que satisfaga B, y es posible que la persona que llama deba elegir uno, como con let x: Vec<_> = foo.collect() o  foo.collect::<Vec<_>>().

Este ejemplo es fantástico porque utiliza impl Display dos veces. Es útil explicar que nada aquí exige que sea el mismo tipo de visualización implícito. Si usáramos un especificación de tipo T:, se impondría la restricción de que el tipo de entrada T y el de retorno T sean del mismo tipo. ¡No funcionaría para esta función en particular, ya que el tipo que esperamos como entrada probablemente no sea del mismo tipo  que format!. Si quisiéramos hacer lo mismo mediante la especificación del tipo, necesitaríamos dos parámetros de tipo genéricos independientes.

viernes, 20 de octubre de 2023

Trait Bounds en Rust


Cuando se trabaja con genéricos, a menudo se puede solicitar que los tipos implementen algún Trait, de modo que pueda llamar a los métodos de este Trait.

Puedes hacer esto con T: Trait o impl Trait:


fn duplicate<T: Clone>(a: T) -> (T, T) {

    (a.clone(), a.clone())

}


// Syntactic sugar for:

//   fn add_42_millions<T: Into<i32>>(x: T) -> i32 {

fn add_42_millions(x: impl Into<i32>) -> i32 {

    x.into() + 42_000_000

}


// struct NotClonable;

fn main() {

    let foo = String::from("foo");

    let pair = duplicate(foo);

    println!("{pair:?}");


    let many = add_42_millions(42_i8);

    println!("{many}");

    let many_more = add_42_millions(10_000_000);

    println!("{many_more}");

}


Se puede tambien usar la sentencia where


fn duplicate<T>(a: T) -> (T, T)

where

    T: Clone,

{

    (a.clone(), a.clone())

}


miércoles, 18 de octubre de 2023

Defer en Go


La declaración de Defer de Go programa una llamada a una función para que se ejecute inmediatamente antes de que regrese el flujo de control. Es una forma inusual pero efectiva de lidiar con situaciones como recursos que deben liberarse independientemente del camino que tome una función para regresar. Los ejemplos canónicos son desbloquear un mutex o cerrar un archivo.


// Contents returns the file's contents as a string.

func Contents(filename string) (string, error) {

    f, err := os.Open(filename)

    if err != nil {

        return "", err

    }

    defer f.Close()  // f.Close will run when we're finished.


    var result []byte

    buf := make([]byte, 100)

    for {

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

        result = append(result, buf[0:n]...) // append is discussed later.

        if err != nil {

            if err == io.EOF {

                break

            }

            return "", err  // f will be closed if we return here.

        }

    }

    return string(result), nil // f will be closed if we return here.

}

Diferir una llamada a una función como Close tiene dos ventajas. Primero, garantiza que nunca olvidará cerrar el archivo, un error que es fácil de cometer si luego edita la función para agregar una nueva ruta de retorno. En segundo lugar, significa que el cierre se sitúa cerca de la apertura, lo cual es mucho más claro que colocarlo al final de la función.

Los argumentos de la función diferida (que incluyen al receptor si la función es un método) se evalúan cuando se ejecuta el diferimiento, no cuando se ejecuta la llamada. Además de evitar preocupaciones acerca de que las variables cambien los valores a medida que se ejecuta la función, esto significa que un único sitio de llamada diferida puede diferir múltiples ejecuciones de funciones. He aquí un ejemplo:


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

    defer fmt.Printf("%d ", i)

}


Las funciones diferidas se ejecutan en orden LIFO, por lo que este código hará que se imprima 4 3 2 1 0 cuando la función regrese. Un ejemplo más plausible es una forma sencilla de rastrear la ejecución de funciones a través del programa. Podríamos escribir un par de rutinas de rastreo simples como esta:


func trace(s string)   { fmt.Println("entering:", s) }

func untrace(s string) { fmt.Println("leaving:", s) }


// Use them like this:

func a() {

    trace("a")

    defer untrace("a")

    // do something....

}

Podemos hacerlo mejor aprovechando el hecho de que los argumentos de las funciones diferidas se evalúan cuando se ejecuta el diferimiento. La rutina de rastreo puede configurar el argumento para la rutina de desrastreo. Este ejemplo:


func trace(s string) string {

    fmt.Println("entering:", s)

    return s

}


func un(s string) {

    fmt.Println("leaving:", s)

}


func a() {

    defer un(trace("a"))

    fmt.Println("in a")

}


func b() {

    defer un(trace("b"))

    fmt.Println("in b")

    a()

}


func main() {

    b()

}


Esto imprime: 


entering: b

in b

entering: a

in a

leaving: a

leaving: b


Para programadores acostumbrados a la gestión de recursos a nivel de bloques de otros lenguajes, defer puede parecer peculiar, pero sus aplicaciones más interesantes y potentes provienen precisamente de que no está basado en bloques sino en funciones.

lunes, 16 de octubre de 2023

Las funciones pueden nombrar sus variables de retorno en Go


A los "parámetros" de retorno o resultado de una función Go se les pueden dar nombres y usarse como variables regulares, al igual que los parámetros entrantes. Cuando se nombran, se inicializan con los valores cero para sus tipos cuando comienza la función; Si la función ejecuta una declaración de devolución sin argumentos, los valores actuales de los parámetros del resultado se utilizan como valores devueltos.

Los nombres no son obligatorios pero pueden hacer que el código sea más corto y claro: son documentación. Si nombramos los resultados de nextInt, resulta obvio cuál int devuelto es cuál.


func nextInt(b []byte, pos int) (value, nextPos int) {


Debido a que los resultados con nombre se inicializan y vinculan a un retorno sin adornos, pueden simplificar y aclarar. Aquí hay una versión de io.ReadFull que los usa bien:


func ReadFull(r Reader, buf []byte) (n int, err error) {

    for len(buf) > 0 && err == nil {

        var nr int

        nr, err = r.Read(buf)

        n += nr

        buf = buf[nr:]

    }

    return

}

Default Methods en Rust


Los traits pueden implementar el comportamiento en términos de otros métodos de traits:

trait Equals {

    fn equals(&self, other: &Self) -> bool;

    fn not_equals(&self, other: &Self) -> bool {

        !self.equals(other)

    }

}


#[derive(Debug)]

struct Centimeter(i16);


impl Equals for Centimeter {

    fn equals(&self, other: &Centimeter) -> bool {

        self.0 == other.0

    }

}


fn main() {

    let a = Centimeter(10);

    let b = Centimeter(20);

    println!("{a:?} equals {b:?}: {}", a.equals(&b));

    println!("{a:?} not_equals {b:?}: {}", a.not_equals(&b));

}


Los traits pueden especificar métodos preimplementados (predeterminados) y métodos que los usuarios deben implementar ellos mismos. Los métodos con implementaciones predeterminadas pueden depender de los métodos requeridos. Por ejemplo podemos implementar un trait NotEquals y mover el método not_equals a este y hacer de Equals un súper rasgo para NotEquals.


trait NotEquals: Equals {

    fn not_equals(&self, other: &Self) -> bool {

        !self.equals(other)

    }

}

O podemos proporcionar una implementación general de NotEquals, y especificar una implementación para todos los que implementen Equals.

trait NotEquals {

    fn not_equals(&self, other: &Self) -> bool;

}


impl<T> NotEquals for T where T: Equals {

    fn not_equals(&self, other: &Self) -> bool {

        !self.equals(other)

    }

}

Con la implementación general, ya no necesita a Equals como un trait general para NotEqual.

sábado, 14 de octubre de 2023

derive de Rust


Las macros derivadas o derive de Rust funcionan generando automáticamente código que implementa los rasgos especificados para una estructura de datos.

Puede dejar que el compilador obtenga una serie de características de la siguiente manera:

#[derive(Debug, Clone, PartialEq, Eq, Default)]

struct Player {

    name: String,

    strength: u8,

    hit_points: u8,

}


fn main() {

    let p1 = Player::default();

    let p2 = p1.clone();

    println!("Is {:?}\nequal to {:?}?\nThe answer is {}!", &p1, &p2,

             if p1 == p2 { "yes" } else { "no" });

}

La funciones retornan multiples valores en Go


Una de las características inusuales de Go es que las funciones y métodos pueden devolver múltiples valores. Estea tecnica se puede utilizar para mejorar un par de modismos torpes en programas C: retornos de error dentro de banda como -1 para EOF y modificación de un argumento pasado por dirección.

En C, un error de escritura se indica mediante un recuento negativo con el código de error escondido en un lugar volátil. En Go, Write puede devolver un recuento y un error: “Sí, escribiste algunos bytes pero no todos porque llenaste el dispositivo”. La firma del método Write en archivos del paquete os es:


func (file *File) Write(b []byte) (n int, err error)


y como dice la documentación, devuelve el número de bytes escritos y un error no nulo cuando n! = len(b). 

Un enfoque similar evita la necesidad de pasar un puntero a un valor de retorno para simular un parámetro de referencia. Aquí hay una función simple para tomar un número de una posición en un segmento de bytes, devolviendo el número y la siguiente posición.


func nextInt(b []byte, i int) (int, int) {

    for ; i < len(b) && !isDigit(b[i]); i++ {

    }

    x := 0

    for ; i < len(b) && isDigit(b[i]); i++ {

        x = x*10 + int(b[i]) - '0'

    }

    return x, i

}


Podrías usarlo para escanear los números en un segmento de entrada b como este:


    for i := 0; i < len(b); {

        x, i = nextInt(b, i)

        fmt.Println(x)

    }

miércoles, 11 de octubre de 2023

Estructuras de Control en Go


Las estructuras de control de Go están relacionadas con las de C pero difieren en aspectos importantes. No hay bucle do o while, sólo un for ligeramente generalizado; switch es más flexible; if y switch aceptan una declaración de inicialización opcional como la de for; las declaraciones  break y continue toman una etiqueta opcional para identificar qué interrumpir o continuar; y hay nuevas estructuras de control que incluyen un interruptor tipo y un multiplexor de comunicaciones multidireccional, select. La sintaxis también es ligeramente diferente: no hay paréntesis y los cuerpos siempre deben estar delimitados por llaves.


En Go, un if simple se ve así:


if x > 0 {

    return y

}


Las llaves obligatorias alientan a escribir declaraciones if simples en varias líneas. Es un buen estilo hacerlo de todos modos, especialmente cuando el cuerpo contiene una declaración de control como return o break.

Dado que if y switch aceptan una declaración de inicialización, es común ver una utilizada para configurar una variable local.


if err := file.Chmod(0664); err != nil {

    log.Print(err)

    return err

}


En las bibliotecas Go, encontrará que cuando una declaración if no fluye hacia la siguiente declaración (es decir, el cuerpo termina en break, continue, goto o return), se omite el else innecesario.


f, err := os.Open(name)

if err != nil {

    return err

}

codeUsing(f)


Este es un ejemplo de una situación común en la que el código debe protegerse contra una secuencia de condiciones de error. El código se lee bien si el flujo de control exitoso recorre la página, eliminando los casos de error a medida que surgen. Dado que los casos de error tienden a terminar en declaraciones de devolución, el código resultante no necesita else.


f, err := os.Open(name)

if err != nil {

    return err

}

d, err := f.Stat()

if err != nil {

    f.Close()

    return err

}

codeUsing(f, d)


El último ejemplo demuestra un detalle, cómo funciona las declaración corta :=. La declaración que llama a os.Open dice:


f, err := os.Open(name)


Esta declaración declara dos variables, f y err. Unas líneas más tarde, la llamada a f.Stat dice:


d, err := f.Stat()


que parece como si declarara d y err. Observe, sin embargo, que err aparece en ambas afirmaciones. Esta duplicación es legal: err se declara en la primera declaración, pero solo se reasigna en la segunda. Esto significa que la llamada a f.Stat utiliza la variable err existente declarada anteriormente y simplemente le da un nuevo valor.

En una declaración := puede aparecer una variable v incluso si ya ha sido declarada, siempre que:

  • esta declaración está en el mismo alcance que la declaración existente de v (si v ya está declarado en un alcance externo, la declaración creará una nueva variable),
  • el valor correspondiente en la inicialización es asignable a v, y
  • hay al menos otra variable creada por la declaración.

Esta propiedad inusual es puro pragmatismo, lo que facilita el uso de un único valor de error, por ejemplo, en una larga cadena if-else. Verás que se usa con frecuencia.

Vale la pena señalar aquí que en Go el alcance de los parámetros de la función y los valores de retorno es el mismo que el del cuerpo de la función, aunque aparecen léxicamente fuera de las llaves que encierran el cuerpo.

El bucle for es similar, pero no igual, al de C. Unifica for ,while y do-while. Hay tres formas, de las cuales sólo una tiene punto y coma.


// Like a C for

for init; condition; post { }


// Like a C while

for condition { }


// Like a C for(;;)

for { }


Las declaraciones breves facilitan la declaración de la variable de índice directamente en el bucle.


sum := 0

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

    sum += i

}


Si está realizando un bucle sobre una matriz, un segmento, una cadena o un mapa, o leyendo desde un canal, una cláusula de rango puede gestionar el bucle.


for key, value := range oldMap {

    newMap[key] = value

}


Si solo necesita el primer elemento del rango (la clave o índice), elimine el segundo:


for key := range m {

    if key.expired() {

        delete(m, key)

    }

}


Si solo necesita el segundo elemento del rango (el valor), utilice el identificador en blanco, un guión bajo, para descartar el primero:


sum := 0

for _, value := range array {

    sum += value

}


Para las cadenas, range , desglosa en caracteres código Unicode individuales analizando el UTF-8. Las codificaciones erróneas consumen un byte y producen la runa de reemplazo U+FFFD.


for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding

    fmt.Printf("character %#U starts at byte position %d\n", char, pos)

}


esto imprime: 


character U+65E5 '日' starts at byte position 0

character U+672C '本' starts at byte position 3

character U+FFFD '�' starts at byte position 6

character U+8A9E '語' starts at byte position 7


Finalmente, Go no tiene operador de coma y ++ y -- son declaraciones, no expresiones. Por lo tanto, si desea ejecutar múltiples variables en un for, debe usar la asignación paralela.


// Reverse a

for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {

    a[i], a[j] = a[j], a[i]

}



El switch de Go es más general que el de C. No es necesario que las expresiones sean constantes o incluso números enteros, los casos se evalúan de arriba a abajo hasta que se encuentra una coincidencia y, si el interruptor no tiene expresión, se activa como verdadero. Por lo tanto, es posible (e idiomático) escribir una cadena if-else-if-else como un switch.


func unhex(c byte) byte {

    switch {

    case '0' <= c && c <= '9':

        return c - '0'

    case 'a' <= c && c <= 'f':

        return c - 'a' + 10

    case 'A' <= c && c <= 'F':

        return c - 'A' + 10

    }

    return 0

}


No hay clasificación automática, pero los casos se pueden presentar en listas separadas por comas.


func shouldEscape(c byte) bool {

    switch c {

    case ' ', '?', '&', '=', '#', '+', '%':

        return true

    }

    return false

}


Aunque no son tan comunes en Go como en otros lenguajes similares a C, las declaraciones de break se pueden utilizar para finalizar un caso. A veces, sin embargo, es necesario salir de un bucle circundante, no del interruptor, y en Go eso se puede lograr colocando una etiqueta en el bucle y "rompiendo" esa etiqueta. Este ejemplo muestra ambos usos.


Loop:

    for n := 0; n < len(src); n += size {

        switch {

        case src[n] < sizeOne:

            if validateOnly {

                break

            }

            size = 1

            update(src[n])


        case src[n] < sizeTwo:

            if n+1 >= len(src) {

                err = errShortInput

                break Loop

            }

            if validateOnly {

                break

            }

            size = 2

            update(src[n] + src[n+1]<<shift)

        }

    }


Por supuesto, la instrucción continue también acepta una etiqueta opcional pero se aplica sólo a los bucles.


Aquí hay una rutina de comparación para segmentos de bytes que utiliza dos declaraciones de cambio:


// Comparar devuelve un número entero que compara los dos segmentos de bytes,

// lexicográficamente.

// El resultado será 0 si a == b, -1 si a < b, y +1 si a > b

func Compare(a, b []byte) int {

    for i := 0; i < len(a) && i < len(b); i++ {

        switch {

        case a[i] > b[i]:

            return 1

        case a[i] < b[i]:

            return -1

        }

    }

    switch {

    case len(a) > len(b):

        return 1

    case len(a) < len(b):

        return -1

    }

    return 0

}


También se puede utilizar un modificador para descubrir el tipo dinámico de una variable de interfaz. Un cambio de tipo de este tipo utiliza la sintaxis de una aserción de tipo con la palabra clave tipo entre paréntesis. Si el modificador declara una variable en la expresión, la variable tendrá el tipo correspondiente en cada cláusula. También es idiomático reutilizar el nombre en tales casos, declarando de hecho una nueva variable con el mismo nombre pero de un tipo diferente en cada caso.


var t interface{}

t = functionOfSomeType()

switch t := t.(type) {

default:

    fmt.Printf("unexpected type %T\n", t)     // %T prints whatever type t has

case bool:

    fmt.Printf("boolean %t\n", t)             // t has type bool

case int:

    fmt.Printf("integer %d\n", t)             // t has type int

case *bool:

    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool

case *int:

    fmt.Printf("pointer to integer %d\n", *t) // t has type *int

}

Traits en Rust


Rust te permite abstraer tipos con rasgos o traits. Son similares a las interfaces de java o c#:

struct Dog { name: String, age: i8 }

struct Cat { lives: i8 } // No name needed, cats won't respond anyway.


trait Pet {

    fn talk(&self) -> String;

}


impl Pet for Dog {

    fn talk(&self) -> String { format!("Woof, my name is {}!", self.name) }

}


impl Pet for Cat {

    fn talk(&self) -> String { String::from("Miau!") }

}


fn greet<P: Pet>(pet: &P) {

    println!("Oh you're a cutie! What's your name? {}", pet.talk());

}


fn main() {

    let captain_floof = Cat { lives: 9 };

    let fido = Dog { name: String::from("Fido"), age: 5 };


    greet(&captain_floof);

    greet(&fido);

}


Los objetos de rasgo permiten valores de diferentes tipos, por ejemplo en una colección:


fn main() {

    let pets: Vec<Box<dyn Pet>> = vec![

        Box::new(Cat { lives: 9 }),

        Box::new(Dog { name: String::from("Fido"), age: 5 }),

    ];

    for pet in pets {

        println!("Hello, who are you? {}", pet.talk());

    }

}


Disposición de la memoria después de asignar mascotas:



Los tipos que implementan un Trait determinado pueden ser de diferentes tamaños. Esto hace que sea imposible tener cosas como Vec<dyn Pet> en el ejemplo anterior.

dyn Pet es una forma de informarle al compilador acerca de un tipo de tamaño dinámico que implementa Pet.

En el ejemplo, las mascotas se asignan en la pila y los datos vectoriales están en el montón. Los dos elementos del vector son fat pointers o punteros gordos:

Un fat pointers es un puntero de doble ancho. Tiene dos componentes: un puntero al objeto real y un puntero a la tabla de métodos virtuales (vtable) para la implementación Pet de ese objeto en particular.

Los datos del perro llamado Fido son los campos de nombre y edad. El Gato tiene un campo de vida.

martes, 10 de octubre de 2023

Transformación digital en América Latina

 

Monomorfización en Rust


El código genérico se convierte en código no genérico según los sitios de llamadas:


fn main() {

    let integer = Some(5);

    let float = Some(5.0);

}

se comporta como si escribieras


enum Option_i32 {

    Some(i32),

    None,

}


enum Option_f64 {

    Some(f64),

    None,

}


fn main() {

    let integer = Option_i32::Some(5);

    let float = Option_f64::Some(5.0);

}


Esta es una abstracción de costo cero: obtienes exactamente el mismo resultado que si hubieras codificado manualmente las estructuras de datos sin la abstracción.


sábado, 7 de octubre de 2023

bbolt: Una Base de Datos Embebida para Go


Cuando se trata de almacenar y gestionar datos en una aplicación Go, una de las opciones más populares y eficientes es bbolt. bbolt es una base de datos embebida de código abierto diseñada específicamente para aplicaciones Go. 

Pero, ¿Qué es bbolt? bbolt, también conocida como "BoltDB", es una base de datos embebida escrita en Go que se centra en la simplicidad, el rendimiento y la eficiencia en la utilización de recursos. A diferencia de las bases de datos tradicionales que se ejecutan como procesos separados, bbolt se integra directamente en la aplicación Go, lo que la convierte en una excelente opción para aplicaciones que necesitan un almacenamiento local y ligero sin la necesidad de un servidor de base de datos independiente.

Características Clave de bbolt son: 

  • Transacciones ACID: bbolt garantiza la integridad de los datos mediante transacciones ACID (Atomicidad, Consistencia, Aislamiento y Durabilidad). Esto significa que las operaciones de lectura y escritura se realizan de manera segura y confiable.
  • Soporte para Índices:Puedes crear índices para acelerar las consultas de datos, lo que es especialmente útil cuando necesitas recuperar datos de manera eficiente en función de ciertos criterios.
  • Eficiencia en el Uso de la Memoria: bbolt está diseñada para utilizar la memoria de manera eficiente, lo que la hace adecuada para aplicaciones con recursos limitados.
  • Almacenamiento Duradero: Los datos almacenados en bbolt son persistentes y duraderos. Puedes confiar en que los datos sobrevivirán incluso después de reiniciar la aplicación.


Para comenzar a usar bbolt en tu proyecto Go, primero debemos agregar la dependencia:


go get go.etcd.io/bbolt@latest


Vamos a importar bbolt : 


import bolt "go.etcd.io/bbolt"


Luego, podemos abrir una base de datos o crear una nueva:


db, err := bolt.Open(path, 0600, nil)

if err != nil {

  return err

}

defer db.Close()


A partir de ahí, puedes crear buckets (similares a tablas en una base de datos relacional), almacenar y recuperar datos, y gestionar transacciones de manera similar a otras bases de datos. El enfoque es simple y eficiente, lo que facilita el trabajo con la base de datos.


// Abre una transacción de escritura.

tx, err := db.Begin(true)

if err != nil {

    log.Fatal(err)

}

defer tx.Rollback()


// Obtiene un bucket (o lo crea si no existe).

bucket, err := tx.CreateBucketIfNotExists([]byte("mi-bucket"))

if err != nil {

    log.Fatal(err)

}


// Almacena un valor en el bucket.

err = bucket.Put([]byte("clave"), []byte("valor"))

if err != nil {

    log.Fatal(err)

}


// Realiza commit de la transacción.

if err := tx.Commit(); err != nil {

    log.Fatal(err)

}


// Recupera un valor del bucket.

valor := bucket.Get([]byte("clave"))

fmt.Printf("Valor recuperado: %s\n", valor)


bbolt es una opción atractiva para aplicaciones Go que necesitan una base de datos embebida rápida, eficiente y fácil de usar. Su diseño simple, soporte para transacciones ACID y eficiencia en el uso de recursos la convierten en una elección sólida para muchas aplicaciones. Podemos considerar a bbolt para tus proyectos Go que requieran almacenamiento de datos local y duradero.


Dejo link: 

https://pkg.go.dev/go.etcd.io/bbolt

https://github.com/etcd-io/bbolt

viernes, 6 de octubre de 2023

Punto y coma en Go


Al igual que C, la gramática formal de Go usa punto y coma para terminar declaraciones, pero a diferencia de C, esos punto y coma no aparecen en la fuente. En cambio, Lexer utiliza una regla simple para insertar puntos y coma automáticamente mientras escanea, por lo que el texto de entrada está prácticamente libre de ellos.

La regla es esta. Si el último token antes de una nueva línea es un identificador (que incluye palabras como int y float64), un literal básico como un número o una cadena constante, o uno de los tokens


break continue fallthrough return ++ -- ) }


el lexer siempre inserta un punto y coma después del token. Esto podría resumirse como "si la nueva línea viene después de un token que podría finalizar una declaración, inserte un punto y coma".

También se puede omitir un punto y coma inmediatamente antes de una llave de cierre, por lo que una declaración como


     go func() { for { dst <- <-src } }()


no necesita punto y coma. Los programas idiomáticos de Go tienen punto y coma solo en lugares como cláusulas de bucle for, para separar los elementos inicializador, condición y continuación. También son necesarios para separar varias declaraciones en una línea, en caso de que escriba el código de esa manera.

Una consecuencia de las reglas de inserción de punto y coma es que no se puede colocar la llave de apertura de una estructura de control (if, for, switch o select) en la línea siguiente. Si lo hace, se insertará un punto y coma antes del aparato ortopédico, lo que podría provocar efectos no deseados. 


if i < f() {

    g()

}


así no


if i < f()  // wrong!

{           // wrong!

    g()

}

jueves, 5 de octubre de 2023

Generics en Rust


Rust soporta genéricos, lo que le permite abstraer algoritmos o estructuras de datos (como ordenamiento o un árbol binario) sobre los tipos definidos por nosotros o por el sdk.

Puede utilizar genéricos para abstraer el tipo de campo concreto:

#[derive(Debug)]

struct Point<T> {

    x: T,

    y: T,

}


fn main() {

    let integer = Point { x: 5, y: 10 };

    let float = Point { x: 1.0, y: 4.0 };

    println!("{integer:?} and {float:?}");

}


Lo hermoso es que infiere el tipo dependiendo de como lo usamos. Es decir, si hacemos : 


let x = Point { x: 5, y: 10.0 };


no va a compilar. 


Se puedes declarar un tipo genérico en tu bloque impl:


#[derive(Debug)]

struct Point<T>(T, T);


impl<T> Point<T> {

    fn x(&self) -> &T {

        &self.0  // + 10

    }


    // fn set_x(&mut self, x: T)

}


fn main() {

    let p = Point(5, 10);

    println!("p.x = {}", p.x());

}


¿Por qué T se especifica dos veces en impl<T> Point<T> {}? ¿No es eso redundante?

Esto se debe a que es una sección de implementación genérica para tipos genéricos. Son genéricos independientemente. Significa que estos métodos están definidos para cualquier T.

Es posible escribir impl Point<u32> { .. }.

Point sigue siendo genérico y puedes usar Point<f64>, pero los métodos de este bloque solo estarán disponibles para Point<u32>.

miércoles, 4 de octubre de 2023

Agregando Swagger a nuestro proyecto Echo


Luego de crear nuestro proyecto con echo, vamos a agregar swagger

Empezamos agregando la librería de swag 


go get -d github.com/swaggo/swag/cmd/swag

go install github.com/swaggo/swag/cmd/swag@latest


Luego ejecutamos Swag en la carpeta raíz del proyecto Go que contiene el archivo main.go, Swag analizará los comentarios y generará los archivos necesarios (carpeta docs y docs/doc.go).


swag init


y luego agregamos echo para swagger


go get -u github.com/swaggo/echo-swagger


Y ahora tenemos swagger en nustro proyecto echo : 


package main


import (

"github.com/labstack/echo/v4"

"github.com/swaggo/echo-swagger"


_ "github.com/swaggo/echo-swagger/example/docs" // docs is generated by Swag CLI, you have to import it.

)


// @title Swagger Example API

// @version 1.0

// @description This is a sample server Petstore server.

// @termsOfService http://swagger.io/terms/


// @contact.name API Support

// @contact.url http://www.swagger.io/support

// @contact.email support@swagger.io


// @license.name Apache 2.0

// @license.url http://www.apache.org/licenses/LICENSE-2.0.html


// @host petstore.swagger.io

// @BasePath /v2

func main() {

e := echo.New()


e.GET("/swagger/*", echoSwagger.WrapHandler)


e.Logger.Fatal(e.Start(":1323"))

}


En mi caso solo tengo que agregar : 

el import : 

import (_ "myapp/docs")

y el router : 

e.GET("/swagger/*", echoSwagger.WrapHandler)

y tengo estos 2 servicios con su documentación  : 

// HealthCheck godoc
// @Summary Show the status of server.
// @Description get the status of server.
// @Tags root
// @Accept */*
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /health/check [get]
func HealthCheck(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]interface{}{
"data": "Server is up and running",
})
}

// Info godoc
// @Summary Show the info of server.
// @Description get the info of server.
// @Tags root
// @Accept */*
// @Produce json
// @Success 200 {object} computer.SysInfo
// @Router /health/info [get]
func Info(c echo.Context) error {
return c.JSON(http.StatusOK, computer.Info())
}

Y se genero el siguiente swagger.json :

{
    "swagger": "2.0",
    "info": {
        "contact": {}
    },
    "paths": {
        "/health/check": {
            "get": {
                "description": "get the status of server.",
                "consumes": [
                    "*/*"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "root"
                ],
                "summary": "Show the status of server.",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "type": "object",
                            "additionalProperties": true
                        }
                    }
                }
            }
        },
        "/health/info": {
            "get": {
                "description": "get the info of server.",
                "consumes": [
                    "*/*"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "root"
                ],
                "summary": "Show the info of server.",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/computer.SysInfo"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "computer.SysInfo": {
            "type": "object",
            "properties": {
                "disk": {
                    "type": "integer"
                },
                "hostname": {
                    "type": "string"
                },
                "platform": {
                    "type": "string"
                },
                "ram": {
                    "type": "integer"
                },
                "ram available": {
                    "type": "integer"
                },
                "ram free": {
                    "type": "integer"
                },
                "uptime": {
                    "type": "integer"
                }
            }
        }
    }
}

Dejo link : https://github.com/swaggo/echo-swagger

martes, 3 de octubre de 2023

Módulos en Rust y su Ubicación


Rust es conocido por su sistema de módulos sólido y su capacidad para administrar y organizar el código de manera efectiva. Los módulos permiten dividir un proyecto en partes más pequeñas y lógicas, facilitando el mantenimiento y la colaboración.

Los módulos en Rust son unidades de organización para el código que ayudan a dividir una aplicación en partes más pequeñas y manejables. Estos módulos permiten agrupar funciones, tipos, estructuras y otros elementos relacionados de manera lógica.

Para definir un módulo en Rust, utilizamos la palabra clave mod. Por ejemplo:

// Definición de un módulo llamado 'my_module'

mod my_module {

    // Contenido del módulo aquí

}

En Rust, la ubicación de los módulos dentro de un proyecto es importante para su visibilidad y accesibilidad desde otros módulos. 

Omitir mod le indicará a Rust que lo busque en otro archivo, si escribimos:

mod garden;

Esto le indica a Rust que el contenido del módulo garden se encuentra en src/garden.rs. De manera similar, se puede encontrar un módulo garden::vegetables en src/garden/vegetables.rs.

La el directorio raíz está en:

  • src/lib.rs (para una libreria)
  • src/main.rs (para una aplicación binaria)
Los módulos definidos en archivos también se pueden documentar mediante "comentarios internos del documento". Estos documentan el elemento que los contiene (en este caso, un módulo).

//! This module implements the garden, including a highly performant germination
//! implementation.

// Re-export types from this module.
pub use seeds::SeedPacket;
pub use garden::Garden;

/// Sow the given seed packets.
pub fn sow(seeds: Vec<SeedPacket>) { todo!() }

/// Harvest the produce in the garden that is ready.
pub fn harvest(garden: &mut Garden) { todo!() }


Antes de Rust 2018, los módulos debían estar ubicados en module/mod.rs en lugar de module.rs, y esta sigue siendo una alternativa funcional para las ediciones posteriores a 2018.

La razón principal para introducir filename.rs como alternativa a filename/mod.rs fue porque muchos archivos llamados mod.rs pueden ser difíciles de distinguir en los IDE.

Un anidamiento más profundo puede utilizar carpetas, incluso si el módulo principal es un archivo:

src/
├── main.rs
├── top_module.rs
└── top_module/
    └── sub_module.rs

El lugar donde Rust buscará los módulos se puede cambiar con una directiva del compilador:

#[path = "some/path.rs"]
mod some_module;

Esto es útil, por ejemplo, si desea colocar pruebas para un módulo en un archivo llamado some_module_test.rs, similar a la convención en Go.


Paths en Rust




Los Paths se resuelven de la siguiente manera:

  • Como ruta relativa:
    • foo o self::foo se refiere a foo en el módulo actual,
    • super::foo se refiere a foo en el módulo principal.
  • Como Path absoluto:
    • crate::foo se refiere a foo en la raíz del modulo actual,
    • bar::foo se refiere a foo en el modulo de bar. 

Un módulo puede incorporar símbolos de otro módulo al alcance con su uso. Normalmente verá algo como esto en la parte superior de cada módulo:

use std::collections::HashSet;

use std::mem::transmute;

lunes, 2 de octubre de 2023

Crear un proyecto en Go y Echo


Vamos a crear un proyecto con echo y go. Echo es un framework web minimalista y de alto rendimiento para Go. Está diseñado para ser simple y eficiente, lo que lo hace adecuado para el desarrollo rápido de API REST. Echo es especialmente apreciado por su rendimiento y facilidad de uso.


$ mkdir myapp && cd myapp

$ go mod init myapp

$ go get github.com/labstack/echo/v4

$ go get github.com/labstack/echo/v4/middleware


Con esto, hemos importado las librerías necesarias si miramos el archivo go.mod : 


module myapp 


go 1.19


require (

github.com/golang-jwt/jwt v3.2.2+incompatible // indirect

github.com/labstack/echo/v4 v4.11.1 // indirect

github.com/labstack/gommon v0.4.0 // indirect

github.com/mattn/go-colorable v0.1.13 // indirect

github.com/mattn/go-isatty v0.0.19 // indirect

github.com/valyala/bytebufferpool v1.0.0 // indirect

github.com/valyala/fasttemplate v1.2.2 // indirect

golang.org/x/crypto v0.11.0 // indirect

golang.org/x/net v0.12.0 // indirect

golang.org/x/sys v0.10.0 // indirect

golang.org/x/text v0.11.0 // indirect

golang.org/x/time v0.3.0 // indirect

)


Ahora vamos a hacer un pequeño programa que tenga un hola mundo : 


import (

  "github.com/labstack/echo/v4"

  "github.com/labstack/echo/v4/middleware"

  "net/http"

)


func main() {

  // Echo instance

  e := echo.New()


  // Middleware

  e.Use(middleware.Logger())

  e.Use(middleware.Recover())


  // Routes

  e.GET("/", hello)


  // Start server

  e.Logger.Fatal(e.Start(":1323"))

}


// Handler

func hello(c echo.Context) error {

  return c.String(http.StatusOK, "Hola Mundo!")

}


Y Listo!!


Dejo links: 

https://echo.labstack.com/docs

https://github.com/labstack/echo