Translate

martes, 19 de septiembre de 2023

Cell y RefCell en Rust


Cell y RefCell implementan lo que Rust llama mutabilidad interior: mutación de valores en un contexto inmutable.

La celda se usa normalmente para tipos simples, ya que requiere copiar o mover valores. Los tipos de interiores más complejos suelen utilizar RefCell, que rastrea referencias compartidas y exclusivas en tiempo de ejecución y entra en pánico si se utilizan incorrectamente.


use std::cell::RefCell;

use std::rc::Rc;


#[derive(Debug, Default)]

struct Node {

    value: i64,

    children: Vec<Rc<RefCell<Node>>>,

}


impl Node {

    fn new(value: i64) -> Rc<RefCell<Node>> {

        Rc::new(RefCell::new(Node { value, ..Node::default() }))

    }


    fn sum(&self) -> i64 {

        self.value + self.children.iter().map(|c| c.borrow().sum()).sum::<i64>()

    }

}


fn main() {

    let root = Node::new(1);

    root.borrow_mut().children.push(Node::new(5));

    let subtree = Node::new(10);

    subtree.borrow_mut().children.push(Node::new(11));

    subtree.borrow_mut().children.push(Node::new(12));

    root.borrow_mut().children.push(subtree);


    println!("graph: {root:#?}");

    println!("graph sum: {}", root.borrow().sum());

}


Si estuviéramos usando Cell en lugar de RefCell en este ejemplo, tendríamos que sacar el Nodo de Rc para empujar a los elementos secundarios y luego volver a colocarlo. Esto es seguro porque siempre hay un valor sin referencia en la celda, pero no es ergonómico.

Para hacer cualquier cosa con un Nodo, debe llamar a un método RefCell, generalmente borrow o borrow_mut.

sábado, 16 de septiembre de 2023

Rc en Rust


Rc es un puntero compartido, Rc es de contado por referencias, es decir que va contando las referencias. Se usa cuando se necesita hacer referencia a los mismos datos de varios lugares:

use std::rc::Rc;


fn main() {

    let mut a = Rc::new(10);

    let mut b = Rc::clone(&a);


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

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

}


Rc garantiza que el valor contenido sea válido mientras haya referencias.

Rc en Rust es como std::shared_ptr en C++.

Rc::clone es económico: crea un puntero a la misma asignación y aumenta el recuento de referencias. No realiza una clonación profunda y generalmente se puede ignorar cuando se buscan problemas de rendimiento en el código.

make_mut en realidad clona el valor interno si es necesario (“clonar en escritura”) y devuelve una referencia mutable.

Utilice Rc::strong_count para comprobar el recuento de referencia.

Rc::downgrade le brinda un objeto con recuento débil de referencias para crear ciclos que se eliminarán correctamente (probablemente en combinación con RefCell, en la siguiente post).

viernes, 15 de septiembre de 2023

Y es Erlang tan bueno?


Actualmente, Erlang está ganando mucha popularidad debido a conversaciones entusiastas que pueden llevar a la gente a creer que es más de lo que realmente es. El primer caso de esto está relacionado con las enormes capacidades de escalamiento de Erlang debido a sus procesos livianos. Es cierto que los procesos de Erlang son muy ligeros: puedes tener cientos de miles de ellos existentes al mismo tiempo, pero esto no significa que tengas que usarlos de esa manera sólo porque puedas. Por ejemplo, crear un juego de disparos en el que todo, incluidas las balas, sea su propio actor, es una locura. Lo único que dispararás en un juego como este será tu propio pie. Todavía hay un pequeño costo al enviar un mensaje de actor a actor, y si divides demasiado las tareas, ¡harás las cosas más lentas!

También se dice que Erlang puede escalar de manera directamente proporcional a la cantidad de núcleos que tiene su computadora, pero esto generalmente no es cierto. Es posible, pero la mayoría de los problemas no se comportan. de una manera que te permita ejecutar todo al mismo tiempo.

Hay algo más a tener en cuenta: si bien Erlang hace algunas cosas muy bien, técnicamente todavía es posible obtener los mismos resultados en otros lenguajes. Lo opuesto también es cierto; evaluar cada problema como debe ser y elegir la herramienta adecuada según el problema que se aborda. Erlang no es una solución mágica y será particularmente malo en cosas como procesamiento de imágenes y señales, controladores de dispositivos de sistemas operativos, etc. y brillará en cosas como software grande para uso de servidor (es decir, colas, reducción de mapas), haciendo algo de levantamiento junto con otros lenguajes, implementación de protocolos de nivel superior, etc. Las áreas intermedias dependerán de usted. No necesariamente debes encerrarte en un software de servidor con Erlang: ha habido casos de personas que hacen cosas inesperadas y sorprendentes. Un ejemplo es IANO, un robot creado por el equipo UNICT, que utiliza Erlang por su inteligencia artificial y ganó la medalla de plata en el concurso eurobot de 2009. Otro ejemplo es Wings 3D, un modelador 3D de código abierto (pero no un renderizador) escrito en Erlang y, por lo tanto, multiplataforma.

Dejo link: https://learnyousomeerlang.com/introduction

Transformar el mundo de las aplicaciones de mensajería

 Me llego este mail y se los quiero compartir: 

¿Qué es Erlang?


Erlang es un lenguaje de programación funcional. Si alguna vez ha trabajado con lenguajes imperativos, declaraciones como i++ pueden resultarle normales; en programación funcional no están permitidos. De hecho, ¡está estrictamente prohibido cambiar el valor de cualquier variable! Esto puede sonar extraño al principio, pero si recuerdas tus clases de matemáticas, en realidad así es como las aprendiste:

y = 2

x = y + 3

x = 2 + 3

x = 5

Si hubiera agregado lo siguiente:

x = 5 + 1

x = x

∴ 5 = 6

Habrías estado muy confundido. La programación funcional reconoce esto: si digo que x es 5, entonces lógicamente no puedo afirmar que también sea 6. Esto sería deshonesto. Esta es también la razón por la que una función con el mismo parámetro siempre debería devolver el mismo resultado:

x = add_two_to(3) = 5

∴ x = 5

Las funciones que siempre devuelven el mismo resultado para el mismo parámetro se denominan transparencia referencial. Es lo que nos permite reemplazar add_two_to(3) con 5, ya que el resultado de 3+2 siempre será 5. Eso significa que luego podemos unir docenas de funciones para resolver problemas más complejos mientras nos aseguramos de que nada se rompa. Lógico y limpio ¿no? Sin embargo, hay un problema:


x = today() = 2009/10/22

-- wait a day --

x = today() = 2009/10/23

x = x

∴ 2009/10/22 = 2009/10/23


¡Oh, no! ¡Mis hermosas ecuaciones! ¡De repente todos se equivocaron! ¿Cómo es que mi función arroja un resultado diferente cada día?

Evidentemente, hay algunos casos en los que resulta útil romper la transparencia referencial. Erlang tiene este enfoque muy pragmático con la programación funcional: obedece sus principios más puros (transparencia referencial, evitar datos mutables, etc.), pero aléjate de ellos cuando surgen problemas del mundo real.

Ahora, definimos Erlang como un lenguaje de programación funcional, pero también hay un gran énfasis en la concurrencia y la alta confiabilidad. Para poder realizar docenas de tareas al mismo tiempo, Erlang utiliza el modelo de actor, y cada actor es un proceso separado en la máquina virtual. En pocas palabras, si fueras un actor en el mundo de Erlang, serías una persona solitaria, sentada en una habitación oscura sin ventanas, esperando junto a tu buzón para recibir un mensaje. Una vez que recibes un mensaje, reaccionas de una manera específica: pagas las facturas al recibirlas, respondes a las tarjetas de cumpleaños con una carta de agradecimiento e ignoras las cartas que no puedes entender.

El modelo de actor de Erlang puede imaginarse como un mundo en el que todos están sentados solos en su propia habitación y pueden realizar algunas tareas distintas. Todos se comunican estrictamente escribiendo cartas y listo. Si bien suena como una vida aburrida (y una nueva era para el servicio postal), significa que puedes pedirle a muchas personas que realicen tareas muy específicas por ti, y ninguna de ellas hará algo mal o cometerá errores que tendrán repercusiones en tu vida. el trabajo de otros; es posible que ni siquiera conozcan la existencia de otras personas además de ti (y eso es genial).

Para escapar de esta analogía, Erlang te obliga a escribir actores (procesos) que no compartirán información con otros bits de código a menos que se pasen mensajes entre sí. Cada comunicación es explícita, rastreable y segura.

Cuando definimos Erlang, lo hicimos a nivel de lenguaje, pero en un sentido más amplio, esto no es todo: Erlang es también un entorno de desarrollo en su conjunto. El código se compila en código de bytes y se ejecuta dentro de una máquina virtual. Entonces Erlang, al igual que Java, puede ejecutarse en cualquier lugar. La distribución estándar incluye (entre otras) herramientas de desarrollo (compilador, depurador, generador de perfiles, framework de prueba), el framework Open Telecom Platform (OTP), un servidor web, un generador de analizadores y la base de datos mnesia, un sistema de almacenamiento de valores clave capaz de replicarse en muchos servidores, admitiendo transacciones anidadas y permitiéndole almacenar cualquier tipo de datos de Erlang.

La VM y las librería también le permiten actualizar el código de un sistema en ejecución sin interrumpir ningún programa, distribuir su código con facilidad en muchas computadoras y administrar errores y fallas de una manera simple pero poderosa.

Una política general relacionada en Erlang: dejar que se explote. No como un avión con decenas de pasajeros muriendo, sino más bien como un equilibrista con una red de seguridad debajo. Si bien debes evitar cometer errores, en la mayoría de los casos no será necesario verificar cada tipo o condición de error.

La capacidad de Erlang para recuperarse de errores, organizar el código con actores y hacerlo escalar con la distribución y la concurrencia suena increíble!

Dejo link: https://learnyousomeerlang.com/introduction


jueves, 14 de septiembre de 2023

Prueba Erlang


Quiero recomendarles el tutorial tryerlang que encontre de casualidad. Si bien es basico, es muy interactivo y esta bueno. 

Dejo link: https://www.tryerlang.org/

Box en Rust parte 2


Y siguiendo con Box, los tipos de datos recursivos o tipos de datos con tamaños dinámicos necesitan usar un Box:

#[derive(Debug)]

enum List<T> {

    Cons(T, Box<List<T>>),

    Nil,

}


fn main() {

    let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

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

}

Si no se usaba Box e intentábamos incrustar una Lista directamente en la Lista, el compilador no calcularía un tamaño fijo de la estructura en la memoria (la Lista sería de tamaño infinito).

Box resuelve este problema ya que tiene el mismo tamaño que un puntero normal y solo apunta al siguiente elemento de la Lista.

Veamos que pasa si lo eliminamos: 

enum List<T> {

    Cons(T, List<T>),

    Nil,

}


fn main() {

    let list: List<i32> = List::Cons(1, List::Cons(2, List::Nil));

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

}

Y si ejecutamos esto: 

cargo run

   Compiling hello_cargo v0.1.0 

error[E0072]: recursive type `List` has infinite size

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

  |

2 | enum List<T> {

  | ^^^^^^^^^^^^

3 |     Cons(T, List<T>),

  |             ------- recursive without indirection

  |

help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle

  |

3 |     Cons(T, Box<List<T>>),

  |             ++++       +


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

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


Un Box no puede estar vacío, por lo que el puntero siempre es válido y no nulo. Esto permite al compilador optimizar el diseño de la memoria.

sábado, 9 de septiembre de 2023

Como crear un nuevo proyecto con Clojure y Leiningen?



Bueno, todo tiene un principio y vamos a empezar a crear un proyectito de consola con Clojure y Leiningen

$ lein new app my-project

Fijate vos que si queres utilizar nombre de proyecto con mayuscula, no te deja, ojo ahi. 

Ahora vamos a ejecutarlo :

$ lein run

podemos generar un jar con : 

$ lein jar

Si estamos trabajando en un proyecto de aplicación, Leiningen nos brinda la posibilidad de construir lo que se llama un uberjar. Este es un archivo JAR que contiene el proyecto en sí y todas las dependencias y está configurado para permitir que se ejecute tal cual.

$ lein uberjar

Compiling secuencia-clojure.core

$ java -jar target/uberjar/el-jar-standalone.jar 

Y ya podemos empezar!!

viernes, 8 de septiembre de 2023

Box en Rust


Box es un puntero a los datos del monticulo:

fn main() {

    let five = Box::new(5);

    println!("five: {}", *five);

}

Box<T> implementa Deref<Target = T>, lo que significa que puedes llamar métodos desde T directamente en un Box<T>.

Box es como std::unique_ptr en C++, excepto que se garantiza que el elemento no será nulo.

En el ejemplo anterior, incluso podemos omitir el * en println! gracias a Deref.

Un Box puede resultar útil cuando:

  • tiene un tipo cuyo tamaño no se puede conocer en el momento de la compilación, pero el compilador de Rust quiere saber un tamaño exacto.
  • desea transferir una propiedad de una gran cantidad de datos. Para evitar copiar grandes cantidades de datos en la pila, almacene los datos en el monticulo en un Box de modo que solo se mueva el puntero.

lunes, 4 de septiembre de 2023

HashMap en Rust


Veamos un ejemplo: 


use std::collections::HashMap;

fn main() {

    let mut page_counts = HashMap::new();

    page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);

    page_counts.insert("Grimms' Fairy Tales".to_string(), 751);

    page_counts.insert("Pride and Prejudice".to_string(), 303);


    if !page_counts.contains_key("Les Misérables") {

        println!("We know about {} books, but not Les Misérables.",

                 page_counts.len());

    }


    for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {

        match page_counts.get(book) {

            Some(count) => println!("{book}: {count} pages"),

            None => println!("{book} is unknown.")

        }

    }


    // Use the .entry() method to insert a value if nothing is found.

    for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {

        let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0);

        *page_count += 1;

    }


    println!("{page_counts:#?}");

}


Si ejecutamos este programa: 

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

     Running `target/debug/hello_cargo`

We know about 3 books, but not Les Misérables.

Pride and Prejudice: 303 pages

Alice's Adventure in Wonderland is unknown.

{

    "Pride and Prejudice": 304,

    "Grimms' Fairy Tales": 751,

    "Adventures of Huckleberry Finn": 207,

    "Alice's Adventure in Wonderland": 1,

}


HashMap no está definido por defecto y es necesario incluirlo : use std::collections::HashMap;

Desde Rust 1.56, HashMap implementa From<[(K, V); N]>, que nos permite inicializar fácilmente un mapa hash a partir de una matriz literal:

  let page_counts = HashMap::from([

    ("Harry Potter and the Sorcerer's Stone".to_string(), 336),

    ("The Hunger Games".to_string(), 374),

  ]);

Alternativamente, HashMap se puede construir a partir de cualquier iterador que produzca tuplas de valores clave.

Mostramos HashMap<String, i32> y evitamos usar &str como clave para facilitar los ejemplos. Por supuesto, se puede utilizar referencias en colecciones, pero puede generar complicaciones con el verificador de préstamos.

Podemos agregar estas lineas : 

  let pc1 = page_counts

      .get("Harry Potter and the Sorcerer's Stone ")

      .unwrap_or(&336);

  let pc2 = page_counts

      .entry("The Hunger Games".to_string())

      .or_insert(374);

La primera línea verá si un libro está en el HashMap y, en caso contrario, devolverá un valor alternativo. La segunda línea insertará el valor alternativo en el HashMap si no se encuentra el libro.

 


Vec en Rust


Vec es el búfer estándar de tamaño variable de Rust, veamos un ejemplo:


fn main() {

    let mut v1 = Vec::new();

    v1.push(42);

    println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());


    let mut v2 = Vec::with_capacity(v1.len() + 1);

    v2.extend(v1.iter());

    v2.push(9999);

    println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());


    // Canonical macro to initialize a vector with elements.

    let mut v3 = vec![0, 0, 1, 2, 3, 4];


    // Retain only the even elements.

    v3.retain(|x| x % 2 == 0);

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


    // Remove consecutive duplicates.

    v3.dedup();

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

}


Vec implementa Deref<Target = [T]>, lo que significa que puedes llamar a métodos de slice.

  • Vec es un tipo de colección, junto con String y HashMap. Los datos que contiene se almacenan en el monticulo. Esto significa que no es necesario conocer la cantidad de datos en el momento de la compilación. Puede crecer o reducirse en tiempo de ejecución.
  • Observe que Vec<T> también es un tipo genérico, pero no es necesario especificar T explícitamente. Como siempre con la inferencia de tipo Rust, la T se estableció durante la primera llamada push.
  • vec![...] es una macro canónica para usar en lugar de Vec::new() y admite agregar elementos iniciales al vector.
  • Para indexar el vector se puede utilizar [], pero entrarán en pánico si están fuera de los límites. Alternativamente, usar get devolverá un Option. La función pop eliminará el último elemento.

domingo, 3 de septiembre de 2023

String en Rust


 String es el búfer de cadena UTF-8 estándar:


fn main() {

    let mut s1 = String::new();

    s1.push_str("Hello");

    println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());


    let mut s2 = String::with_capacity(s1.len() + 1);

    s2.push_str(&s1);

    s2.push('!');

    println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());


    let s3 = String::from("🇨🇭");

    println!("s3: len = {}, number of chars = {}", s3.len(),

             s3.chars().count());

}


Si ejecutamos este código: 


Compiling hello_cargo v0.1.0 

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

     Running `target/debug/hello_cargo`

s1: len = 5, capacity = 8

s2: len = 6, capacity = 6

s3: len = 8, number of chars = 2

String implementa Deref<Target = str>, lo que significa que puedes llamar a todos los métodos str en un String.

  • String::new devuelve una nueva cadena vacía, se usa String::with_capacity cuando se sabe cuántos caracteres tiene la cadena.
  • String::len devuelve el tamaño de la cadena en bytes (que puede ser diferente de su longitud en caracteres).
  • String::chars devuelve un iterador sobre los caracteres reales. Un carácter puede ser diferente de lo que un humano considerará un "carácter" debido a los grupos de grafemas.
  • Cuando las personas se refieren a cadenas, podrían estar hablando de &str o String.
  • Cuando un tipo implementa Deref<Target = T>, el compilador le permitirá llamar de forma transparente a métodos desde T.
  • String implementa Deref<Target = str> que le da acceso de forma transparente a los métodos de str.
  • String se implementa como un contenedor alrededor de un vector de bytes; muchas de las operaciones que ve admitidas en vectores también se admiten en String, pero con algunas garantías adicionales.


sábado, 2 de septiembre de 2023

Rust Standard Library


Rust tiene una biblioteca estándar que ayuda a establecer un conjunto de tipos comunes utilizados por las bibliotecas y los programas de Rust. De esta manera, dos bibliotecas pueden funcionar juntas sin problemas porque ambas usan el mismo tipo de cadena, por ejemplo.

Los tipos de vocabulario comunes incluyen:

  • Option y Result: se utilizan para valores opcionales y manejo de errores.
  • String: el tipo de cadena predeterminado utilizado para los datos propios.
  • Vec: un vector extensible estándar.
  • HashMap: un tipo de mapa hash con un algoritmo hash configurable.
  • Box: un puntero propio para datos asignados al monticulo.
  • Rc: un puntero compartido.

De hecho, Rust contiene varias capas de la biblioteca estándar: core, alloc y std.

core incluye los tipos y funciones más básicos que no dependen de libc, asignador o incluso de la presencia de un sistema operativo.

alloc incluye tipos que requieren un asignador de monticulo global, como Vec, Box y Arc.

Option y Result en Rust


Los tipos representan datos opcionales:


fn main() {

    let numbers = vec![10, 20, 30];

    let first: Option<&i8> = numbers.first();

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


    let idx: Result<usize, usize> = numbers.binary_search(&10);

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

}

Si ejecuto este codigo : 

cargo run

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

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

     Running `target/debug/hello_cargo`

first: Some(10)

idx: Ok(0)


Si por ejemplo busco el 39 : 

    let idx: Result<usize, usize> = numbers.binary_search(&39); 

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


Si corremos esto : 

cargo run

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

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

     Running `target/debug/hello_cargo`

first: Some(10)

idx: Err(3)

El resultado es Err y no Ok. 

  • Option y Result se utilizan ampliamente no solo en la biblioteca estándar.
  • La Option <&T> tiene cero espacio adicional en comparación con &T.
  • El resultado es el tipo estándar para implementar el manejo de errores.
  • binario_search devuelve Result<usize, usize>.
    • Si se encuentra, Result::Ok contiene el índice donde se encuentra el elemento.
    • De lo contrario, Result::Err contiene el índice donde se debe insertar dicho elemento.

viernes, 1 de septiembre de 2023

Metodos en Rust parte 3


Veamos un ejemplo de metodos:

#[derive(Debug)]

struct Race {

    name: String,

    laps: Vec<i32>,

}


impl Race {

    fn new(name: &str) -> Race {  // No receiver, a static method

        Race { name: String::from(name), laps: Vec::new() }

    }


    fn add_lap(&mut self, lap: i32) {  // Exclusive borrowed read-write access to self

        self.laps.push(lap);

    }


    fn print_laps(&self) {  // Shared and read-only borrowed access to self

        println!("Recorded {} laps for {}:", self.laps.len(), self.name);

        for (idx, lap) in self.laps.iter().enumerate() {

            println!("Lap {idx}: {lap} sec");

        }

    }


    fn finish(self) {  // Exclusive ownership of self

        let total = self.laps.iter().sum::<i32>();

        println!("Race {} is finished, total lap time: {}", self.name, total);

    }

}


fn main() {

    let mut race = Race::new("Monaco Grand Prix");

    race.add_lap(70);

    race.add_lap(68);

    race.print_laps();

    race.add_lap(71);

    race.print_laps();

    race.finish();

    // race.add_lap(42);

}

Lo ejecutamos : 

cargo run

   Compiling hello_cargo v0.1.0 

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

     Running `target/debug/hello_cargo`

Recorded 2 laps for Monaco Grand Prix:

Lap 0: 70 sec

Lap 1: 68 sec

Recorded 3 laps for Monaco Grand Prix:

Lap 0: 70 sec

Lap 1: 68 sec

Lap 2: 71 sec

Race Monaco Grand Prix is finished, total lap time: 209


Los cuatro métodos aquí utilizan un receptor de método diferente.

Si intentamos llamar a finalizar dos veces o descomentamos // race.add_lap(42); , lanza este error : 

cargo run

   Compiling hello_cargo v0.1.0 

error[E0382]: borrow of moved value: `race`

  --> src/main.rs:37:5

   |

30 |     let mut race = Race::new("Monaco Grand Prix");

   |         -------- move occurs because `race` has type `Race`, which does not implement the `Copy` trait

...

36 |     race.finish();

   |          -------- `race` moved due to this method call

37 |     race.add_lap(42);

   |     ^^^^^^^^^^^^^^^^ value borrowed here after move

   |

note: this function takes ownership of the receiver `self`, which moves `race`

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

   |

23 |     fn finish(self) {  // Exclusive ownership of self

   |               ^^^^

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

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

Y es porque finish toma todo el control del registro. 

Tenga en cuenta que, aunque los receptores de métodos son diferentes, las funciones no estáticas se denominan de la misma manera en el cuerpo principal. Rust permite la referencia y desreferenciación automática al llamar a métodos. Rust agrega automáticamente los cambios &, *, para que ese objeto coincida con la firma del método.