martes, 11 de julio de 2023

Ámbitos y sombreado


En la programación informática, el sombreado de variables o  variable shadowing se produce cuando una variable declarada dentro de un determinado ámbito (bloque de decisión, método o clase interna) tiene el mismo nombre que una variable declarada en un ámbito externo.

veamos esto en rust :


fn main() {

    let a = 10;

    println!("before: {a}");


    {

        let a = "hello";

        println!("inner scope: {a}");


        let a = true;

        println!("shadowed in inner scope: {a}");

    }


    println!("after: {a}");

}


$ cargo run

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 1.32s

     Running `target/debug/hello_cargo`

before: 10

inner scope: hello

shadowed in inner scope: true

after: 10


Definición: El sombreado es diferente de la mutación, porque después del sombreado, las ubicaciones de memoria de ambas variables existen al mismo tiempo. Ambos están disponibles con el mismo nombre, dependiendo de dónde lo use en el código.

Una variable de sombreado puede tener un tipo diferente.

El sombreado parece oscuro al principio, pero es conveniente para mantener los valores después de .unwrap().

El siguiente código demuestra por qué el compilador no puede simplemente reutilizar ubicaciones de memoria cuando oculta una variable inmutable en un ámbito, incluso si el tipo no cambia.


fn main() {

    let a = 1;

    let b = &a;

    let a = a + 1;

    println!("{a} {b}");

}

lunes, 10 de julio de 2023

Variables estáticas y constantes en Rust


Se puede declarar constantes en tiempo de compilación:


const DIGEST_SIZE: usize = 3;

const ZERO: Option<u8> = Some(42);


fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] {

    let mut digest = [ZERO.unwrap_or(0); DIGEST_SIZE];

    for (idx, &b) in text.as_bytes().iter().enumerate() {

        digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b);

    }

    digest

}


fn main() {

    let digest = compute_digest("Hello");

    println!("Digest: {digest:?}");

}


De acuerdo con Rust RFC Book, estos se insertan en el momento del uso.

También puede declarar variables estáticas:

static BANNER: &str = "Welcome to RustOS 3.14";


fn main() {

    println!("{BANNER}");

}



viernes, 7 de julio de 2023

Rust RFCs - RFC Book - Active RFC List


El proceso "RFC" (solicitud de comentarios) tiene como objetivo proporcionar una ruta coherente y controlada para los cambios en Rust (como nuevas funciones) para que todas las partes interesadas puedan confiar en la dirección del proyecto.

Muchos cambios, incluidas las correcciones de errores y las mejoras de la documentación, se pueden implementar y revisar a través del flujo de trabajo de solicitud de extracción normal de GitHub.

Sin embargo, algunos cambios son "sustanciales", y pedimos que se sometan a un pequeño proceso de diseño y produzcan un consenso entre la comunidad de Rust y los subequipos.

Si queremos solicitar un cambio o revisar los cambios existentes podemos hacerlo revisando esta documentación: https://rust-lang.github.io/rfcs/introduction.html

jueves, 6 de julio de 2023

Rust: Variables


Rust proporciona seguridad de tipos a través de tipos estáticos. Los enlaces de variables son inmutables de forma predeterminada:

fn main() {

    let x: i32 = 10;

    println!("x: {x}");

    // x = 20;

    // println!("x: {x}");

}


Rust verá cómo se usa la variable para determinar el tipo:


fn takes_u32(x: u32) {

    println!("u32: {x}");

}


fn takes_i8(y: i8) {

    println!("i8: {y}");

}


fn main() {

    let x = 10;

    let y = 20;


    takes_u32(x);

    takes_i8(y);

    // takes_u32(y);

}


cargo run

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 0.30s

     Running `target/debug/hello_cargo`

u32: 10

i8: 20


Pero eso no significa que se descuide el tipado estatico, si corremos el programa anterior descomentando la linea "    // takes_u32(y); " obtendremos: 

$ cargo run

   Compiling hello_cargo v0.1.0 

error[E0308]: mismatched types

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

   |

15 |     takes_u32(y);

   |     --------- ^ expected `u32`, found `i8`

   |     |

   |     arguments to this function are incorrect

   |

note: function defined here

  --> src/main.rs:1:4

   |

1  | fn takes_u32(x: u32) {

   |    ^^^^^^^^^ ------

help: you can convert an `i8` to a `u32` and panic if the converted value doesn't fit

   |

15 |     takes_u32(y.try_into().unwrap());

   |                ++++++++++++++++++++


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

error: could not compile `hello_cargo` due to previous error


Es muy importante enfatizar que las variables declaradas así no son de algún tipo de dinámica "cualquier tipo" que pueda contener cualquier dato. El código máquina generado por tal declaración es idéntico a la declaración explícita de un tipo. El compilador hace el trabajo por nosotros y nos ayuda a escribir código más conciso.


El siguiente código le dice al compilador que copie en un determinado contenedor genérico sin que el código especifique explícitamente el tipo contenido, usando _ como marcador de posición:

fn main() {

    let mut v = Vec::new();

    v.push((10, false));

    v.push((20, true));

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


    let vv = v.iter().collect::<std::collections::HashSet<_>>();

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

}


collect se basa en FromIterator, que implementa HashSet.

lunes, 3 de julio de 2023

Vectores y bucles for en Rust


Vimos que una vector se puede declarar así:

let array = [10, 20, 30];

Puede imprimir una matriz con {:?}:

fn main() {
    let array = [10, 20, 30];
    println!("array: {array:?}");
}

Rust te permite iterar cosas como arreglos y rangos usando la palabra clave for:

fn main() {
    let array = [10, 20, 30];
    print!("Iterating over array:");
    for n in array {
        print!(" {n}");
    }
    println!();

    print!("Iterating over range:");
    for i in 0..3 {
        print!(" {}", array[i]);
    }
    println!();
}

Métodos en Rust


Los métodos son funciones asociadas a un tipo. El argumento self de un método es una instancia del tipo al que está asociado:

struct Rectangle {

    width: u32,

    height: u32,

}


impl Rectangle {

    fn area(&self) -> u32 {

        self.width * self.height

    }


    fn inc_width(&mut self, delta: u32) {

        self.width += delta;

    }

}


fn main() {

    let mut rect = Rectangle { width: 10, height: 5 };

    println!("old area: {}", rect.area());

    rect.inc_width(5);

    println!("new area: {}", rect.area());

}

$ cargo run

   Compiling hello_cargo v0.1.0

   Finished dev [unoptimized + debuginfo] target(s) in 0.28s

     Running `target/debug/hello_cargo`

old area: 50

new area: 75


Podemos definir un constructor : 


struct Rectangle {

    width: u32,

    height: u32,

}


impl Rectangle {


    fn new(width: u32, height: u32) -> Rectangle {

        Rectangle { width, height }

    }


    fn area(&self) -> u32 {

        self.width * self.height

    }


    fn inc_width(&mut self, delta: u32) {

        self.width += delta;

    }

}


fn main() {

    let mut rect = Rectangle::new(10, 5);

    println!("old area: {}", rect.area());

    rect.inc_width(5);

    println!("new area: {}", rect.area());

}



Rust: Conversiones implícitas


Rust no aplicará automáticamente conversiones implícitas entre tipos (a diferencia de C++). Puedes ver esto en un programa como este:

fn multiply(x: i16, y: i16) -> i16 {

    x * y

}

fn main() {

    let x: i8 = 15;

    let y: i16 = 1000;

    println!("{x} * {y} = {}", multiply(x, y));

}


Si corremos este código: 

$ cargo run

   Compiling hello_cargo v0.1.0 

error[E0308]: mismatched types

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

  |

9 |     println!("{x} * {y} = {}", multiply(x, y));

  |                                -------- ^ expected `i16`, found `i8`

  |                                |

  |                                arguments to this function are incorrect

  |

note: function defined here

 --> src/main.rs:1:4

  |

1 | fn multiply(x: i16, y: i16) -> i16 {

  |    ^^^^^^^^ ------

help: you can convert an `i8` to an `i16`

  |

9 |     println!("{x} * {y} = {}", multiply(x.into(), y));

  |                                          +++++++


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

error: could not compile `hello_cargo` due to previous error


Vamos a hacerle caso y vamos a correr : rustc --explain E0308


El tipo esperado no coincide con el tipo recibido.


Ejemplos de código erróneo:

``

fn plus_one(x: i32) -> i32 {

    x + 1

}


plus_one("Not a number");

//       ^^^^^^^^^^^^^^ expected `i32`, found `&str`


if "Not a bool" {

// ^^^^^^^^^^^^ expected `bool`, found `&str`

}


let x: f32 = "Not a float";

//     ---   ^^^^^^^^^^^^^ expected `f32`, found `&str`

//     |

//     expected due to this

```

Este error ocurre cuando se usó una expresión en un lugar donde el compilador esperaba una expresión de un tipo diferente. Puede ocurrir en varios casos, siendo el más común cuando se llama a una función y se pasa un argumento que tiene un tipo diferente al tipo coincidente en la declaración de la función.


Genial! no cambio el tipo de forma implicita. 

Todos los tipos enteros de Rust implementan las características From<T> e Into<T> para permitirnos convertir entre ellos. El rasgo From<T> tiene un solo método from() y, de manera similar, el rasgo Into<T> tiene un solo método into(). La implementación de estos rasgos es la forma en que un tipo expresa que se puede convertir en otro tipo.

La biblioteca estándar tiene una implementación de From<i8> para i16, lo que significa que podemos convertir una variable x de tipo i8 en i16 llamando a i16::from(x). O, más simple, con x.into(), porque From<i8> para la implementación de i16 crea automáticamente una implementación de Into<i16> para i8.

Lo mismo se aplica a sus propias implementaciones From para sus propios tipos, por lo que es suficiente implementar solo From para obtener una implementación Into respectiva automáticamente.

Por lo tanto si hacemos .into() este código va funcionar : 


fn multiply(x: i16, y: i16) -> i16 {

    x * y

}


fn main() {

    let x: i8 = 15;

    let y: i16 = 1000;

    println!("{x} * {y} = {}", multiply(x.into(), y));

}


$ cargo run

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 0.83s

     Running `target/debug/hello_cargo`

15 * 1000 = 15000


Sobrecarga de funciones


Rust no soporta la sobrecarga:

  • Cada función tiene una sola implementación:
    • Siempre toma un número fijo de parámetros.
    • Siempre toma un único conjunto de tipos de parámetros.
  • Los valores predeterminados no son compatibles:
    • Todos los sitios de llamadas tienen el mismo número de argumentos.
    • Las macros a veces se utilizan como alternativa.
Sin embargo, los parámetros de función pueden ser genéricos:


fn pick_one<T>(a: T, b: T) -> T {

    if std::process::id() % 2 == 0 { a } else { b }

}


fn main() {

    println!("coin toss: {}", pick_one("heads", "tails"));

    println!("cash prize: {}", pick_one(500, 1000));

}

domingo, 2 de julio de 2023

Rustdoc


Todos los elementos de Lenguaje Rust se pueden documentar usando una sintaxis especial ///.

/// Determine whether the first argument is divisible by the second argument.

///

/// If the second argument is zero, the result is false.

fn is_divisible_by(lhs: u32, rhs: u32) -> bool {

    if rhs == 0 {

        return false;  // Corner case, early return

    }

    lhs % rhs == 0     // The last expression in a block is the return value

}


Los contenidos se tratan como Markdown. Las librerias de Rust se documentan automáticamente en docs.rs mediante la herramienta rustdoc. Es idiomático documentar todos los elementos públicos en una API utilizando este patrón.

Y para utilizarla podemos hacer : 

rustdoc src/main.rs 



sábado, 1 de julio de 2023

Funciones en Rust



Una versión de Rust de la famosa pregunta de entrevista de FizzBuzz:

fn main() {

    print_fizzbuzz_to(20);

}


fn is_divisible(n: u32, divisor: u32) -> bool {

    if divisor == 0 {

        return false;

    }

    n % divisor == 0

}


fn fizzbuzz(n: u32) -> String {

    let fizz = if is_divisible(n, 3) { "fizz" } else { "" };

    let buzz = if is_divisible(n, 5) { "buzz" } else { "" };

    if fizz.is_empty() && buzz.is_empty() {

        return format!("{n}");

    }

    format!("{fizz}{buzz}")

}


fn print_fizzbuzz_to(n: u32) {

    for i in 1..=n {

        println!("{}", fizzbuzz(i));

    }

}


$ cargo run

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 0.38s

     Running `target/debug/hello_cargo`

1

2

fizz

4

buzz

fizz

7

8

fizz

buzz

11

fizz

13

14

fizzbuzz

16

17

fizz

19

buzz


Los parámetros de declaración son seguidos por un tipo (lo contrario de algunos lenguajes de programación), luego un tipo de retorno.

La última expresión en el cuerpo de una función (o cualquier bloque) se convierte en el valor devuelto. Y Simplemente se omite el ; al final de la expresión.

Algunas funciones no tienen valor de retorno y devuelven el 'tipo de unidad', (). El compilador inferirá esto si se omite el tipo de retorno -> ().

La expresión de rango en el ciclo for en print_fizzbuzz_to() contiene =n, lo que hace que incluya el límite superior.

Rust: String vs str

 


Dado este post, podemos entender los dos tipos de cadenas en Rust:

fn main() {

    let s1: &str = "World";

    println!("s1: {s1}");


    let mut s2: String = String::from("Hello ");

    println!("s2: {s2}");

    s2.push_str(s1);

    println!("s2: {s2}");

    

    let s3: &str = &s2[6..];

    println!("s3: {s3}");

}

$ cargo run

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 0.51s

     Running `target/debug/hello_cargo`

s1: World

s2: Hello 

s2: Hello World

s3: World


En Rust: 

  • &str una referencia inmutable a un segmento de cadena.
  • String de un búfer de cadena mutable.

&str introduce un segmento de cadena, que es una referencia inmutable a datos de cadena codificados en UTF-8 almacenados en un bloque de memoria. Los literales de cadena ("Hola") se almacenan en el binario del programa.

El tipo String de Rust es un contenedor alrededor de un vector de bytes. Al igual que con un Vec<T>, es propiedad.

Como ocurre con muchos otros tipos, String::from() crea una cadena a partir de un literal de cadena; String::new() crea una nueva cadena vacía, a la que se pueden agregar datos de cadena usando los métodos push() y push_str().

La macro format!() es una forma conveniente de generar una cadena propia a partir de valores dinámicos. Acepta la misma especificación de formato que println!().

Puede tomar prestados segmentos de &str de String a través de & y, opcionalmente, selección de rango.

Para programadores de C++: piense en &str como const char* de C++, pero el que siempre apunta a una cadena válida en la memoria. Rust String es un equivalente aproximado de std::string de C++ (diferencia principal: solo puede contener bytes codificados en UTF-8 y nunca utilizará una optimización de cadena pequeña).

jueves, 29 de junio de 2023

Slices en Rust


Un segmento o slices te da una vista de una colección más grande:

 fn main() {

    let a: [i32; 6] = [10, 20, 30, 40, 50, 60];

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


    let s: &[i32] = &a[2..4];

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

}

$ cargo run

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 1.62s

     Running `target/debug/hello_cargo`

a: [10, 20, 30, 40, 50, 60]

s: [30, 40]


Creamos un segmento a partir de un arreglo y especificando los índices inicial y final entre paréntesis.

Si el segmento comienza en el índice 0, la sintaxis de rango de Rust nos permite descartar el índice inicial, lo que significa que &a[0..a.len()] y &a[..a.len()] son idénticos.

Lo mismo ocurre con el último índice, por lo que &a[2..a.len()] y &a[2..] son idénticos.

Para crear fácilmente una porción de la matriz completa, podemos usar &a[..].

s es una referencia a una porción de i32s. Y el tipo de s es &[i32] y no menciona la longitud del vector. Esto nos permite realizar cálculos en rebanadas de diferentes tamaños.

Las rebanadas siempre toman prestado de otro objeto. En este ejemplo, a tiene que permanecer "vivo" durante al menos el mismo tiempo que nuestro segmento.

La pregunta sobre la modificación de a[3] puede generar una discusión interesante, pero la respuesta es que, por razones de seguridad de la memoria, no puede hacerlo a través de a después de crear un segmento, pero puede leer los datos de a y s de forma segura. 


miércoles, 28 de junio de 2023

Rust y Referencias colgantes


 Rust prohibirá las referencias colgantes:


fn main() {

    let ref_x: &i32;

    {

        let x: i32 = 10;

        ref_x = &x;

    }

    println!("ref_x: {ref_x}");

}


Se dice que una referencia "toma prestado" el valor al que se refiere.

Rust está rastreando la vida útil de todas las referencias para garantizar que vivan lo suficiente. Y lanza el siguiente error: 


$ cargo run

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

error[E0597]: `x` does not live long enough

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

   |

9  |         ref_x = &x;

   |                 ^^ borrowed value does not live long enough

10 |

11 |     }

   |     - `x` dropped here while still borrowed

12 |

13 |     println!("ref_x: {ref_x}");

   |                       ----- borrow later used here


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

error: could not compile `hello_cargo` due to previous error


lunes, 26 de junio de 2023

Rust: Tipos de compuestos

 

TypesLiterals
Arrays[T; N][20, 30, 40][0; 3]
Tuples()(T,)(T1, T2), …()('x',)('x', 1.2), …


Asignación de matriz y acceso:

fn main() {
    let mut a: [i8; 10] = [42; 10];
    a[5] = 0;
    println!("a: {:?}", a);
}

Asignación de tuplas y acceso:

fn main() {
    let t: (i8, bool) = (7, true);
    println!("1st index: {}", t.0);
    println!("2nd index: {}", t.1);
}

Arrays:

Los Arrays tienen elementos del mismo tipo, T, y longitud, N, que es una constante de tiempo de compilación. Tenga en cuenta que la longitud de la matriz es parte de su tipo, lo que significa que [u8; 3] y [u8; 4] se consideran dos tipos diferentes.

Podemos usar literales para asignar valores a matrices.


Tuplas:

Al igual que los arreglos, las tuplas tienen una longitud fija.

Las tuplas agrupan valores de diferentes tipos en un tipo compuesto.

Se puede acceder a los campos de una tupla por el período y el índice del valor, p. t.0, t.1.

La tupla vacía () también se conoce como el "tipo de unidad". Es a la vez un tipo y el único valor válido de ese tipo, es decir, tanto el tipo como su valor se expresan como (). Se utiliza para indicar, por ejemplo, que una función o expresión no tiene valor de retorno, como veremos más adelante.

Puede considerarlo como un void que puede resultarle familiar de otros lenguajes de programación.

Referencias en Rust


Como C++, Rust tiene referencias:

fn main() {

    let mut x: i32 = 10;

    let ref_x: &mut i32 = &mut x;

    *ref_x = 20;

    println!("x: {x}");

}

Debemos desreferenciar ref_x al asignarleun valor, de forma similar a los punteros de C y C++.

Rust eliminará automáticamente la referencia en algunos casos, en particular al invocar métodos.

Las referencias que se declaran como mut se pueden vincular a diferentes valores durante su vida útil.

Asegúrese de notar la diferencia entre let mut ref_x: &i32 y let ref_x: &mut i32. El primero representa una referencia mutable que se puede vincular a diferentes valores, mientras que el segundo representa una referencia a un valor mutable.