sábado, 4 de noviembre de 2023

FromIterator de Rust

 


FromIterator te permite crear una colección a partir de un Iterador.

fn main() {

    let primes = vec![2, 3, 5, 7];

    let prime_squares = primes

        .into_iter()

        .map(|prime| prime * prime)

        .collect::<Vec<_>>();

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

}

Iterator implementa fn collect<B>(self) -> B where B: FromIterator<Self::Item>, Self: Sized


También hay implementaciones que te permiten hacer cosas interesantes como convertir un Iterador<Item = Result<V, E>> en un Result<Vec<V>, E>.

viernes, 3 de noviembre de 2023

Asignación de memoria con make en Go


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


make([]int, 10, 100)


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

Estos ejemplos ilustran la diferencia entre nuevo y fabricado.


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

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


// Unnecessarily complex:

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

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


// Idiomatic:

v := make([]int, 100)


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

Iterators en Rust


Puede implementar el trait Iterador en nuestros tipos :

struct Fibonacci {

    curr: u32,

    next: u32,

}


impl Iterator for Fibonacci {

    type Item = u32;


    fn next(&mut self) -> Option<Self::Item> {

        let new_next = self.curr + self.next;

        self.curr = self.next;

        self.next = new_next;

        Some(self.curr)

    }

}


fn main() {

    let fib = Fibonacci { curr: 0, next: 1 };

    for (i, n) in fib.enumerate().take(5) {

        println!("fib({i}): {n}");

    }

}


El trait Iterador implementa muchas operaciones de programación funcional comunes sobre colecciones (por ejemplo, map, filter, reduce, etc). En Rust, estas funciones deberían producir un código tan eficiente como las implementaciones imperativas equivalentes.

IntoIterator es el trait que hace que los bucles funcionen. Se implementa mediante tipos de colección como Vec<T> y referencias a ellos como &Vec<T> y &[T]. Ranges también lo implementan. Es por eso que puedes iterar sobre un vector con for i in some_vec { .. } pero some_vec.next() no existe.


miércoles, 1 de noviembre de 2023

Test en Go


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

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

package greetings


import "fmt"


// Hello returns a greeting for the named person.

func Hello(name string) (message string) {

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

if name == "" {

message = "Hi!"

} else {

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

}

return message

}


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

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

package greetings


import (

"regexp"

"testing"

)


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

// for a valid return value.

func TestHelloName(t *testing.T) {

name := "Gladys"

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

msg := Hello("Gladys")

if !want.MatchString(msg) {

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

}

}


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

func TestHelloEmpty(t *testing.T) {

msg := Hello("")

if msg != "Hi!" {

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

}

}


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

Si ejecutamos go test en el directorio obtendremos : 

$ go test

PASS

ok      example.com/greetings   0.364s


$ go test -v

=== RUN   TestHelloName

--- PASS: TestHelloName (0.00s)

=== RUN   TestHelloEmpty

--- PASS: TestHelloEmpty (0.00s)

PASS

ok      example.com/greetings   0.372s


Y eso es todo! 

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




Arrays en Go


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

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

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

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


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

    for _, v := range *a {

        sum += v

    }

    return

}


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

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


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

martes, 31 de octubre de 2023

Constructores y literales compuestos


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


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

    if fd < 0 {

        return nil

    }

    f := new(File)

    f.fd = fd

    f.name = name

    f.dirinfo = nil

    f.nepipe = 0

    return f

}


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


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

    if fd < 0 {

        return nil

    }

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

    return &f

}


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


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


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


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


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

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


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

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

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




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)

    }