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




viernes, 12 de enero de 2024

¡C# es el lenguaje de programación del año 2023!

Como cada año se publica el indice tiobe que indica el uso de los lenguajes. Y este año la novedad fue el amplio crecimiento de C#. 

"Por primera vez en la historia del índice TIOBE, C# ha ganado el premio al lenguaje de programación del año. ¡Felicidades! C# ha estado entre los 10 mejores durante más de 2 décadas y ahora que se está poniendo al día con los 4 grandes lenguajes, ganó el merecido premio al ser el lenguaje con el mayor repunte en un año (+1,43%). Los segundos puestos son Scratch (+0,83%) y Fortran (+0,64%). C# le está quitando cuota de mercado a Java y se está volviendo cada vez más popular en dominios como backends de aplicaciones web y juegos (gracias a Unity). C# se puede utilizar de forma gratuita y evoluciona a un ritmo constante, lo que hace que el lenguaje sea más expresivo con cada nueva versión. C# llegó para quedarse y pronto podría incluso superar a Java.

Aparte de C#, el año pasado hubo muchos cambios interesantes en el índice TIOBE. Fortran y Kotlin se convirtieron permanentemente en los 20 mejores jugadores reemplazando a los antiguos favoritos R y Perl. Fortran está muy preparado para hacer cálculos con buenas bibliotecas y sigue siendo uno de los favoritos de las universidades en muchos ámbitos. Kotlin es el competidor de Java fácil de aprender y escribir. Pregunta interesante: ¿qué lenguajes entrarán en el top 20 del índice TIOBE en 2024? Esto es muy difícil de predecir. Julia tocó brevemente el índice TIOBE en 2023, pero no pudo mantener esa posición. Se necesita la madurez del idioma y la comunidad de Julia para tener una segunda oportunidad. Apostaría por Dart (con Flutter) y TypeScript. Este último ya se utiliza mucho en la industria, pero por alguna razón todavía no se destaca en el índice TIOBE. Veamos qué nos depara el 2024." -- Paul Jansen, director ejecutivo de TIOBE Software (Esto nos dice la pagina oficial de TIOBE.)

Como lenguje más utilizado lo tenemos a python, seguido de c, c++ y java. 


Jan 2024Jan 2023ChangeProgramming LanguageRatingsChange
11Python pagePython13.97%-2.39%
22C pageC11.44%-4.81%
33C++ pageC++9.96%-2.95%
44Java pageJava7.87%-4.34%
55C# pageC#7.16%+1.43%
67changeJavaScript pageJavaScript2.77%-0.11%
710changePHP pagePHP1.79%+0.40%
86changeVisual Basic pageVisual Basic1.60%-3.04%
98changeSQL pageSQL1.46%-1.04%
1020changeScratch pageScratch1.44%+0.86%
1112changeGo pageGo1.38%+0.23%
1227changeFortran pageFortran1.09%+0.64%
1317changeDelphi/Object Pascal pageDelphi/Object Pascal1.09%+0.36%
1415changeMATLAB pageMATLAB0.97%+0.06%
159changeAssembly language pageAssembly language0.92%-0.68%
1611changeSwift pageSwift0.89%-0.31%
1725changeKotlin pageKotlin0.85%+0.37%
1816changeRuby pageRuby0.80%+0.01%
1918changeRust pageRust0.79%+0.18%
2031changeCOBOL pageCOBOL0.78%+0.45%



La verdad es que descreo un poco de este indice, por ejemplo me llama la atención que Rust bajo, cuando en general veo que va creciendo mucho. O por ejemplo que sea más utilizado Scratch que Go o Rust. Son cositas que no me cierran :( 


Dejo link: https://www.tiobe.com/tiobe-index/

Que es gRPC?

 


gRPC es una tecnología de comunicación entre procesos que nos permite conectar, invocar, operar y depurar aplicaciones heterogéneas distribuidas tan fácilmente como realizar una llamada a una función local.

Cuando desarrollamos una aplicación gRPC, lo primero que hacemos es definir una interfaz de servicio. La definición de la interfaz de servicio contiene información sobre cómo los consumidores pueden consumir el servicio, qué métodos permite que los consumidores llamen de forma remota, qué parámetros de método y formatos de mensaje usar al invocar esos métodos, etc. El lenguaje que especificamos en la definición del servicio se conoce como lenguaje de definición de interfaz (IDL).

Con esa definición de servicio, se puede generar el código del lado del servidor conocido como esqueleto del servidor, que simplifica la lógica del lado del servidor al proporcionar abstracciones de comunicación de bajo nivel. Además, puede generar el código del lado del cliente, conocido como código auxiliar del cliente, que simplifica la comunicación del lado del cliente con abstracciones para ocultar la comunicación de bajo nivel para diferentes lenguajes de programación. Los métodos que especifique en la definición de la interfaz de servicio pueden ser invocados remotamente por el lado del cliente tan fácilmente como realizar una invocación de función local. El framework gRPC subyacente maneja todas las complejidades que normalmente se asocian con el cumplimiento de contratos de servicio estrictos, serialización de datos, comunicación de red, autenticación, control de acceso, observabilidad, etc.

Para comprender los conceptos fundamentales de gRPC, echemos un vistazo a un caso de uso del mundo real de un microservicio implementado con gRPC. Supongamos que estamos creando una aplicación minorista en línea compuesta por múltiples microservicios.


La definición del servicio se especifica en el archivo ProductInfo.proto, que utilizan tanto el lado del servidor como el del cliente para generar el código. En este ejemplo, hemos asumido que el servicio se implementa utilizando el lenguaje Go y que el consumidor se implementa utilizando Java. La comunicación de red entre el servicio y el consumidor se realiza a través de HTTP/2.


miércoles, 10 de enero de 2024

Test de integración en Rust


Si necesitamos probar alguna librería o integración con base de datos o otros componentes podemos utilizar pruebas de integración.

Para hacerlas en Rust, debemos crear un archivo .rs en una carpeta tests/ :


// tests/my_library.rs

use my_library::init;


#[test]

fn test_init() {

    assert!(init().is_ok());

}

Y listo. 

Unit Tests en Rust


Rust y Cargo vienen con un framework de prueba unitario simple:

  • Las pruebas unitarias son compatibles con todo el código.
  • Las pruebas de integración se admiten a través del directorio tests/.

Las pruebas están anotadas con el atributo #[prueba]. Las pruebas unitarias a menudo se colocan en un módulo de pruebas anidadas, usando #[cfg(test)] para compilarlas condicionalmente. Veamos un ejemplo: 


fn first_word(text: &str) -> &str {

    match text.find(' ') {

        Some(idx) => &text[..idx],

        None => &text,

    }

}


#[cfg(test)]

mod test {

    use super::*;


    #[test]

    fn test_empty() {

        assert_eq!(first_word(""), "");

    }


    #[test]

    fn test_single_word() {

        assert_eq!(first_word("Hello"), "Hello");

    }


    #[test]

    fn test_multiple_words() {

        assert_eq!(first_word("Hello World"), "Hello");

    }

}

Podemos correr los test con cargo test: 


$ cargo test 

   Compiling hello_cargo v0.1.0 

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

     Running unittests src/main.rs 


running 3 tests

test test::test_multiple_words ... ok

test test::test_empty ... ok

test test::test_single_word ... ok


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



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/


lunes, 8 de enero de 2024

use, super, self en Rust


Un módulo puede incorporar símbolos de otros módulos con use. Normalmente verá algo como esto en la parte superior de cada módulo:

use std::collections::HashSet;

use std::process::abort;


Paths:

Los paths se pueden resolver de la siguiente manera:


  • Como ruta relativa:

foo o self::foo se refiere a foo del módulo actual,

super::foo se refiere a foo en el módulo principal.


  • Como camino absoluto:

crate::foo se refiere a foo de la raíz del proyecto actual,

bar::foo se refiere a foo en la caja de bar.


Es común “reexportar” símbolos en una ruta más corta. Por ejemplo, las bibliotecas de nivel superior en una caja podrían tener

mod storage;


pub use storage::disk::DiskStorage;

pub use storage::network::NetworkStorage;


haciendo que DiskStorage y NetworkStorage estén disponibles para otras cajas con una ruta corta y conveniente.

En su mayor parte, sólo es necesario utilizar los elementos que aparecen en un módulo. Sin embargo, un rasgo debe estar dentro del alcance para llamar a cualquier método en ese rasgo, incluso si un tipo que implementa ese rasgo ya está dentro del alcance. Por ejemplo, para usar el método read_to_string en un tipo que implementa el rasgo de lectura, debe usar std::io::Read.

La declaración de uso puede tener un comodín: use std::io::*. Se desaconseja esto porque no está claro qué artículos se importan y estos pueden cambiar con el tiempo.