Translate

martes, 6 de febrero de 2024

Atoms en Erlang


Hay una razón por la cual los nombres de las variables no pueden comenzar en minuscula: los átomos. Los átomos son literales, constantes con su propio nombre para el valor. Lo que ves es lo que obtienes y no esperes más. El átomo gato significa "gato" y listo. No puedes jugar con ello, no puedes cambiarlo, no puedes hacerlo pedazos; es gato. Y listo!

Si bien las palabras individuales que comienzan con una letra minúscula son una forma de escribir un átomo, hay más de una manera de hacerlo:

1> atom.

atom

2> atoms_rule.

atoms_rule

3> atoms_rule@erlang.

atoms_rule@erlang

4> 'Atoms can be cheated!'.

'Atoms can be cheated!'

5> atom = 'atom'.

atom


Un átomo debe estar entre comillas simples (') si no comienza con una letra minúscula o si contiene otros caracteres además de alfanuméricos, guión bajo (_) o @.

La expresión 5 también muestra que un átomo con comillas simples es exactamente igual que un átomo similar sin ellas.

Los átomos  permiten olvidar de los valores subyacentes: por ejemplo los colores de los ojos pueden ser simplemente "azules", "marrones", "verdes" y "otros". No es necesario enumerarlos o que tengan otro valor. Estos colores se pueden usar en cualquier parte de cualquier código: los valores subyacentes nunca chocarán y es imposible que una constante de este tipo no esté definida. Si realmente necesitamos constantes con valores asociados, hay una manera de hacerlo que veremos más adelante.

Por lo tanto, un átomo es principalmente útil para expresar o calificar datos acoplados a él. Usado solo, es un poco más difícil encontrarle un buen uso. Por eso no pasaremos más tiempo jugando con ellos; su mejor uso se producirá cuando se combinen con otros tipos de datos.

Los átomos son realmente agradables y una excelente manera de enviar mensajes o representar constantes. Sin embargo, existen riesgos al usar átomos para demasiadas cosas: se hace referencia a un átomo en una "tabla de átomos" que consume memoria (4 bytes/átomo en un sistema de 32 bits, 8 bytes/átomo en un sistema de 64 bits). La tabla de átomos no se recolecta como basura, por lo que los átomos se acumularán hasta que el sistema se vuelque, ya sea por el uso de la memoria o porque se declararon 1048577 átomos.

Esto significa que los átomos no deberían generarse dinámicamente por ningún motivo; Si su sistema tiene que ser confiable y la entrada del usuario permite que alguien lo bloquee a voluntad diciéndole que cree átomos, está en serios problemas. Los átomos deben verse como herramientas para el desarrollador porque, sinceramente, es lo que son.

Algunos átomos son palabras reservadas y no se pueden usar excepto para lo que los diseñadores del lenguaje querían que fueran: nombres de funciones, operadores, expresiones, etc. Estos son: after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor

lunes, 5 de febrero de 2024

Threads en Rust


Los subprocesos de Rust funcionan de manera similar a otros lenguajes:

use std::thread;

use std::time::Duration;


fn main() {

    thread::spawn(|| {

        for i in 1..10 {

            println!("Count in thread: {i}!");

            thread::sleep(Duration::from_millis(5));

        }

    });


    for i in 1..5 {

        println!("Main thread: {i}");

        thread::sleep(Duration::from_millis(5));

    }

}


  • Los subprocesos son todos subprocesos de demonio, el subproceso principal no los espera.
  • Los pánicos en los hilos son independientes entre sí.
  • Los pánicos pueden llevar una carga útil, que se puede descomprimir con downcast_ref.


Invariables en Erlang


Hacer aritmética está bien, pero no llegarás muy lejos sin poder almacenar los resultados en algún lugar. Para eso, usaremos variables. Las variables no pueden variar en la programación funcional. El comportamiento básico de las variables se puede demostrar con estas 7 expresiones (las variables comienzan con una letra mayúscula):

1> One.

* 1: variable 'One' is unbound

2> One = 1.

1

3> Un = Uno = One = 1.

1

4> Two = One + One.

2

5> Two = 2.        

2

6> Two = Two + 1.

** exception error: no match of right hand side value 3

7> two = 2.

** exception error: no match of right hand side value 2


Lo primero que nos dicen estos comandos es que puedes asignar un valor a una variable exactamente una vez; entonces puedes 'fingir' asignar un valor a una variable si es el mismo valor que ya tiene. Si es diferente, Erlang se quejará. Es una observación correcta, pero la explicación es un poco más compleja y depende del operador =. El operador = (no las variables) tiene la función de comparar valores y quejarse si son diferentes. Si son iguales, devuelve el valor:


8> 47 = 45 + 2.

47

9> 47 = 45 + 3.

** exception error: no match of right hand side value 48

Lo que hace este operador cuando se mezcla con variables es que si el término del lado izquierdo es una variable y no está vinculado (no tiene ningún valor asociado), Erlang vinculará automáticamente el valor del lado derecho a la variable de la izquierda. lado. En consecuencia, la comparación tendrá éxito y la variable mantendrá el valor en la memoria.

Este comportamiento del operador = es la base de algo llamado 'coincidencia de patrones', que tienen muchos lenguajes de programación funcionales, aunque la forma de hacer las cosas de Erlang suele considerarse más flexible y completa que las alternativas. 

La otra cosa que nos dijeron los comandos 1-7 es que los nombres de las variables deben comenzar con una letra mayúscula. El comando 7 falló porque la palabra dos tenía una letra minúscula al principio. Técnicamente, las variables también pueden comenzar con un guión bajo ('_'), pero por convención su uso está restringido a valores que no te interesan, pero sentiste que era necesario documentar lo que contienen.

También puedes tener variables que sean solo un guión bajo:

10> _ = 14+3.

17

11> _.

* 1: variable '_' is unbound


A diferencia de cualquier otro tipo de variable, nunca almacenará ningún valor. Totalmente inútil por ahora, pero sabrás que existe cuando lo necesitemos.

Nota: Si está probando en el shell y guarda el valor incorrecto en una variable, es posible "borrar" esa variable usando la función f(Variable). Si desea borrar todos los nombres de variables, haga f( )..

Estas funciones están ahí sólo para ayudarle durante las pruebas y sólo funcionan en el shell. Al escribir programas reales, no podremos destruir valores de esa manera. Poder hacerlo solo en el shell tiene sentido si se reconoce que Erlang es utilizable en escenarios industriales: es completamente posible tener un shell activo durante años sin interrupción... Apostemos a que la variable X se usaría más de una vez. en ese período de tiempo.



Números en Erlang

 


En el shell de Erlang, las expresiones deben terminar con un punto seguido de un espacio en blanco (salto de línea, un espacio, etc.); de lo contrario, no se ejecutarán. Puedes separar expresiones con comas, pero solo se mostrará el resultado de la última (las demás aún se ejecutan). Esta es ciertamente una sintaxis inusual para la mayoría de las personas y proviene de los días en que Erlang se implementó directamente en Prolog, un lenguaje de programación lógica.


Abramos el shell Erlang y escribamos:


1> 2 + 15.

17

2> 49 * 100.

4900

3> 1892 - 1472.

420

4> 5 / 2.

2.5

5> 5 div 2.

2

6> 5 rem 2.

1


Deberías haber notado que a Erlang no le importa si ingresas números de punto flotante o enteros: ambos tipos son compatibles cuando se trata de aritmética. Una calculadora con el número '80085' escrito. Los números enteros y los valores flotantes son prácticamente los únicos tipos de datos que los operadores matemáticos de Erlang manejarán de forma transparente. Sin embargo, si desea tener la división de entero a entero, usemos div, y para tener el operador de módulo, usemos rem (resto).


Podemos utilizar varios operadores en una sola expresión y las operaciones matemáticas obedecen a las reglas de precedencia normales.


7> (50 * 100) - 4999.

1

8> -(50 * 100 - 4999).

-1

9> -50 * (100 - 4999).

244950


Si deseamos expresar números enteros en bases distintas a la base 10, simplemente ingresamos el número como Base#Valor (dada que la Base está en el rango 2..36):


10> 2#101010.

42

11> 8#0677.

447

12> 16#AE.

174



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.

Erlang shell




En Erlang, puedes probar la mayoría de tus cosas en un emulador; ejecutará sus scripts cuando los compile e implemente, pero también te permite ejecutar código en vivo. Para esto, iniciamos el shell en Linux y luego escribimos $ erl. Y si esta todo bien, deberíamos ver un texto como este:

Erlang R13B01 (erts-5.7.2) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.2  (abort with ^G)


Para los usuarios de Windows, aún pueden ejecutar el shell erl.exe, pero se recomienda que se utilice werl.exe, que se puede encontrar en el menú de inicio (programas > Erlang). Werl es una implementación solo para Windows del shell Erlang, que tiene su propia ventana con barras de desplazamiento y admite edición de línea de comandos (como copiar y pegar, lo que llegó a ser una molestia con el shell cmd.exe estándar en Windows). El shell erl aún es necesario si desea redirigir la entrada o salida estándar, o utilizar canalizaciones.

Podremos ingresar y ejecutar código en el emulador, pero primero, veamos cómo podemos movernos en él. El shell Erlang tiene un editor de líneas incorporado basado en un subconjunto de Emacs, un popular editor de texto que se utiliza desde los años 70. Si conoce Emacs, debería estar bien. Para los demás, te irá bien de todos modos.

En primer lugar, si escribe algo de texto y luego va ^A (Ctrl+A), debería ver que el cursor se mueve al principio de la línea. ^E (Ctrl+E) te lleva al final. Puede usar las teclas de flecha para avanzar, retroceder, mostrar líneas anteriores o siguientes para poder repetir el código.

Si escribe algo como li y luego presiona "tab", el shell habrá completado los términos y si presionamos tabulador nuevamente y el shell le sugerirá muchas funciones para usar después. Este es Erlang completando las listas de módulos y luego sugiriendo funciones a partir de ellos. 

Creo que hemos visto suficiente funcionalidad del shell para estar bien, excepto por una cosa: ¡no sabemos cómo salir! Hay una forma rápida de descubrir cómo hacerlo. Simplemente escriba help(). y debería obtener información sobre un montón de comandos que puede usar en el shell (no olvide el punto (.) ya que es necesario para que se ejecute el comando). Usaremos algunos de ellos más adelante, pero la única línea que nos preocupa para poder salir es

q() -- salir - abreviatura de init:stop()

Esta es una manera de hacerlo (de hecho, dos maneras). Si estabas prestando atención, cuando inició el shell, hubo un comentario sobre "abortar con ^G". ¡Hagámoslo y luego presione h para obtener ayuda!


User switch command

--> h

c [nn]            - connect to job

i [nn]            - interrupt job

k [nn]            - kill job

j                 - list all jobs

s [shell]         - start local shell

r [node [shell]]  - start remote shell

q        - quit erlang

? | h             - this message

-->


Si escribe i y luego c, Erlang debería detener el código que se está ejecutando actualmente y devolverlo a un shell responsivo. j le dará una lista de procesos en ejecución (una estrella después de un número indica que este es el trabajo que está ejecutando actualmente), que luego puede interrumpir con i seguido del número. Si usa k, matará el shell tal como está en lugar de simplemente interrumpirlo. Presione s para iniciar uno nuevo.


Eshell V5.7.2  (abort with ^G)

1> "OH NO THIS SHELL IS UNRESPONSIVE!!! *hits ctrl+G*"

User switch command

--> k

--> c

Unknown job

--> s

--> j

2* {shell,start,[]}

--> c 2

Eshell V5.7.2  (abort with ^G)

1> "YESS!"


Si vuelve a leer el texto de ayuda, notará que podemos iniciar shells remotos. 

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());



miércoles, 24 de enero de 2024

Curso Gratuito de Rust en español: Comprehensive Rust

 


Quería
 compartirles este curso gratuito y en español de Rust. En la pagina del curso, nos dan una pequeña descripción del mismo: 

"Este es un curso de Rust de tres días que ha desarrollado el equipo de Android de Google. El curso abarca todo lo relacionado con Rust, desde la sintaxis básica hasta temas avanzados como los genéricos y la gestión de errores. También incluye contenidos específicos de Android el último día."

Dejo link: https://google.github.io/comprehensive-rust/es/index.html

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, 22 de enero de 2024

Mensajes de error, lints con Clippy de rust.


El compilador de Rust produce fantásticos mensajes de error, así como útiles lints integradas. Clippy proporciona aún más ayuda, organizadas en grupos que se pueden habilitar por proyecto.


#[deny(clippy::cast_possible_truncation)]

fn main() {

    let x = 3;

    while (x < 70000) {

        x *= 2;

    }

    println!("X probably fits in a u16, right? {}", x as u16);

}

Si lo ejecutamos : 


   Compiling hello_cargo v0.1.0 

warning: unnecessary parentheses around `while` condition

 --> src/main.rs:7:11

  |

7 |     while (x < 70000) {

  |           ^         ^

  |

  = note: `#[warn(unused_parens)]` on by default

help: remove these parentheses

  |

7 -     while (x < 70000) {

7 +     while x < 70000 {

  |


error[E0384]: cannot assign twice to immutable variable `x`

 --> src/main.rs:9:9

  |

5 |     let x = 3;

  |         -

  |         |

  |         first assignment to `x`

  |         help: consider making this binding mutable: `mut x`

...

9 |         x *= 2;

  |         ^^^^^^ cannot assign twice to immutable variable


For more information about this error, try `rustc --explain E0384`.

warning: `hello_cargo` (bin "hello_cargo") generated 1 warning

error: could not compile `hello_cargo` (bin "hello_cargo") due to previous error; 1 warning emitted


 *  Error 


Clippy tiene una extensa documentación de sus lints: https://doc.rust-lang.org/nightly/clippy/

sábado, 20 de enero de 2024

Mocking en Rust con mockall


Rust se ha ganado rápidamente la reputación de ser un lenguaje de programación robusto y seguro. Sin embargo, cuando se trata de pruebas unitarias y de integración, la capacidad de simular comportamientos específicos se vuelve esencial. Ahí es donde entra en juego Mockall, una biblioteca de mocking para Rust que facilita la creación de simulaciones para tus pruebas.

Mockall es una biblioteca de mocking para Rust que te permite crear objetos simulados (mocks) para  pruebas unitarias. Estos mocks pueden ser configurados para emular el comportamiento de las dependencias del código que estás probando, permitiéndote aislar y probar componentes de manera más efectiva.

Agregar Mockall a tu proyecto Rust es sencillo. Simplemente agrega la siguiente línea a tu archivo Cargo.toml: 

[dev-dependencies]

mockall = "0.10"


Luego, ejecuta cargo build para instalar la dependencia.

Supongamos que tienes una función simple que realiza una operación matemática:

pub fn suma(a: i32, b: i32) -> i32 {
    a + b
}

Ahora, quieres probar una función que utiliza esta función suma, pero no deseas depender de su implementación real durante las pruebas. Entra Mockall:

use mockall::predicate;

trait Calculadora {
    fn suma(&self, a: i32, b: i32) -> i32;
}

struct MiCalculadora;

impl Calculadora for MiCalculadora {
    fn suma(&self, a: i32, b: i32) -> i32 {
        a + b
    }
}

En este ejemplo, hemos definido un trait Calculadora que contiene la función suma, y luego implementamos este trait para la estructura MiCalculadora. Ahora, veamos cómo podemos usar Mockall para simular esta función en nuestras pruebas:

use mockall::automock;

#[automock]
impl Calculadora for MiCalculadora {}

fn funcion_a_probar(calc: &dyn Calculadora, a: i32, b: i32) -> i32 {
    calc.suma(a, b)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn prueba_funcion_a_probar() {
        let mut mock_calculadora = MockCalculadora::new();
        mock_calculadora.expect_suma()
            .with(predicate::eq(2), predicate::eq(3))
            .times(1)
            .returning(|a, b| a + b);

        let resultado = funcion_a_probar(&mock_calculadora, 2, 3);

        assert_eq!(resultado, 5);
    }
}

En este caso, hemos creado un mock para el trait Calculadora utilizando la macro automock. Luego, en la prueba, configuramos el comportamiento esperado del mock para la función suma. Cuando llamamos a funcion_a_probar con el mock, este utilizará la implementación simulada en lugar de la implementación real.

Mockall proporciona una forma eficaz y fácil de simular comportamientos para las pruebas en Rust. Al adoptar esta biblioteca, puedes mejorar la calidad de tus pruebas y garantizar que tu código sea robusto y confiable.

jueves, 18 de enero de 2024

El paquete GoogleTest de Rust


El paquete GoogleTest permite hacer test más legibles: 


use googletest::prelude::*;


#[googletest::test]

fn test_elements_are() {

    let value = vec!["foo", "bar", "baz"];

    expect_that!(value, elements_are!(eq("foo"), lt("xyz"), starts_with("b")));

}

Podemos agregar la dependencia con este comando : 

$ cargo add googletest

    Updating crates.io index

      Adding googletest v0.11.0 to dependencies.

             Features:

             - anyhow

             - proptest

    Updating crates.io index


Y si corremos el test, tendremos este resultado: 


   Compiling hello_cargo v0.1.0 (/home/emanuel/Projects/rust/hello_cargo)

    Finished test [unoptimized + debuginfo] target(s) in 6.29s

     Running unittests src/main.rs (target/debug/deps/hello_cargo-4557c2a679e4325f)


running 1 test

test test_elements_are ... ok


test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s




Dejo link: https://github.com/google/googletest-rust

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

sábado, 13 de enero de 2024

Definición de un servicio gRPC

 


gRPC utiliza protocol buffers como IDL para definir la interfaz de servicio. Protocol buffers en un mecanismo extensible, independiente del lenguaje y neutral de la plataforma para serializar datos estructurados se pueden considerar como un mecanismo de serialización de datos . La definición de la interfaz de servicio se especifica en un archivo .proto: un archivo de texto normal con una extensión .proto. Los servicios gRPC se definen en formato Protocol buffers normal, con parámetros de método RPC y tipos de retorno especificados como mensajes de Protocol buffers. Dado que la definición del servicio es una extensión de la especificación de Protocol buffers, se utiliza un complemento gRPC especial para generar código a partir de su archivo proto.

En nuestro caso de uso de ejemplo, la interfaz del servicio ProductInfo se puede definir utilizando Protocol buffers como se muestra en el siguiente ejemplo: 


syntax = "proto3";

package ecommerce;


service ProductInfo {

rpc addProduct(Product) returns (ProductID);

rpc getProduct(ProductID) returns (Product);


}


message Product {

string id = 1;

string name = 2;

string description = 3;

}


message ProductID {

string value = 1;

}


La definición de servicio de ProductInfo se compone de una definición de interfaz de servicio donde especificamos los métodos remotos, sus parámetros de entrada y salida, y la definición de tipo (o formatos de mensaje) de esos parámetros.


Probar una funcionalidad con la documentación en Rust


Rust tiene soporte integrado para pruebas en la documentación, por ejemplo:

/// Shortens a string to the given length.

///

/// ```

/// # use playground::shorten_string;

/// assert_eq!(shorten_string("Hello World", 5), "Hello");

/// assert_eq!(shorten_string("Hello World", 20), "Hello World");

/// ```

pub fn shorten_string(s: &str, length: usize) -> &str {

    &s[..std::cmp::min(length, s.len())]

}


Los bloques de código en /// de los comentarios se ven automáticamente como código Rust.
El código se compilará y ejecutará como parte de la prueba.

Si agregamos # en el código lo ocultará de los documentos, pero aún así lo compilará/ejecutará.

Este código lo podemos probar en el playground