Derive
- "Derive" significa "derivar", en el contexto de Rust significa "obtener algo de otra cosa" o "auto-implementar" o "auto-generar"
- El compilador puede generar ciertas características básicas para algunos traits
- Le dice al compilador que genere automáticamente el código necesario para implementar ciertos traits sin necesidad de que el programador lo escriba manualmente.
- Se agrega
#[derive(...)]sobre las estructuras o enumeraciones y toma como argumentos los nombres de los traits que se desean derivar automáticamente - Simplifica el código y evita la necesidad de escribir implementaciones repetitivas para traits comunes.
- Estas características se pueden implementar de forma manual si se desea un funcionamiento más complejo
- En Rust, existen varios
deriveque se usan comúnmente para generar automáticamente implementaciones de traits para estructuras (structs) y enumeraciones (enums); Debug, Clone, Copy, Eq y PartialEq, Ord y PartialOrd, Default, Hash - Serialize y Deserealize a través de
serde
Debug
- El trait
Debugpermite que una estructura o enumeración sea formateada utilizando{:?}enprintln!o en macros similares. - Útil para imprimir valores durante la depuración.
- Imprime el estado interno de estructuras, enumeraciones y otros tipos que el compilador no puede imprimir de manera predeterminada.
- Para imprimir un valor que implemente
Debug, se utiliza el formato{:?}dentro deprintln!oformat! - Además de
{:?}, se puede usar{:#?}para imprimir valores en un formato más legible (multilínea), especialmente útil para estructuras complejas. - Si se intenta imprimir o mostrar un valor de algún tipo no implementado, se obtienen errores de compilación
#[derive(Debug)]
struct Inches(i32);
fn main() {
let foot = Inches(12);
println!("One foot equals {:?}", foot);
}
// Sin #[Derive(Debug)]
struct Centimeters(f64);
fn main() {
let cm = Centimeters(100.0);
// Esto causará un error de compilación:
// "error[E0277]: `Centimeters` doesn't implement `Debug`"
println!("100 cm equals {:?}", cm);
}
Clone
- Permite crear una copia exacta de un valor.
- Cuando se implementa
#[derive(Clone)], puedes clonar instancias de ese tipo con el método.clone(). - Algunos tipos de datos ya implementan este método
- Para crear un valor de tipo
Tde una variable&T Tpuede ser cualquier variable- Se clona el valor por si se desea utilizar en lugares diferentes o de maneras diferentes
#[derive(Clone)]
struct Punto {
x: i32,
y: i32,
}
fn main() {
let p1 = Punto { x: 10, y: 20 };
let p2 = p1.clone();
}
Copy
- Permite realizar copias bit a bit de tipos, similar a
Clonepero más ligero. Copyes para tipos que no necesitan realizar una copia profunda (como tipos simples). Se utiliza junto conClone.
#[derive(Copy, Clone)]
struct Punto {
x: i32,
y: i32,
}
fn main() {
let p1 = Punto { x: 10, y: 20 };
let p2 = p1; // p1 sigue siendo válido
println!("{:?}", p1);
}
Default
- Permite crear una instancia predeterminada de una estructura o enumeración utilizando el método
.default(). Es útil para inicializar valores con un estado predeterminado.
#[derive(Default)]
struct Config {
debug: bool,
verbose: bool,
}
fn main() {
// Config { debug: false, verbose: false }
let config = Config::default();
}
PartialEq y Eq
- Permite que una estructura sea comparada directamente usando los operadores
==y!= - Ayuda a garantizar la integridad de los datos y evitar duplicados
- En la gestión de perfiles de usuario o bases de datos, es común comparar si dos registros de usuario son idénticos o si la información de un usuario ha cambiado
- Compara los campos y sus valores
Solo con PartialEq
- Si se cambia el nombre o la edad de la persona, lo evalúa como
No iguales
#[derive(PartialEq)]
struct Persona {
nombre: String,
edad: Option<u8>,
dinero: f64
}
fn main() {
let persona1 = Persona {
nombre: "Juan".to_string(),
edad: Some(30u8),
dinero: 10.5
};
let persona2 = Persona {
nombre: "Juan".to_string(),
edad: Some(30u8),
dinero: 10.5
};
if persona1 == persona2 {
println!("Son iguales");
} else {
println!("No son iguales");
}
}
Eq
Eqes un "supertrait" dePartialEqque indica que todas las instancias comparadas deben cumplir con una igualdad total (==)- Derivar
Eqen una estructura que contienef64causará un error de compilación. f64yf32no implementanEqdebido a problemas con valores especiales comoNaN.- Hasta el momento, para mis casos de uso, no he utilizado
Eq, aunque funcionaria bien si no incluyera valoresf64of32
#[derive(PartialEq, Eq)]
PartialOrd y Ord
- PartialEq se agrega porque es un requisito para implementar PartialOrd
- Jerarquía de traits: PartialEq es un prerrequisito para PartialOrd, y por extensión, para Ord
- Para poder comparar si un valor es menor, mayor o igual a otro (lo que hace PartialOrd), primero debe ser posible determinar si dos valores son iguales (lo que hace PartialEq).
PartialEq
^
|
PartialOrd
^
|
Ord
PartialOrd
- Método Principal:
partial_cmpdevuelve unOption<Ordering>. - Permite comparar dos valores para determinar su orden relativo
- También permite la comparación de dos valores usando los operadores de comparación (
<,<=,>,>=). - No garantiza que todos los valores puedan ser comparados en términos de orden (por ejemplo, con
NaNen flotantes). - Por ejemplo, si se tiene un tipo que incluye
NaN, la comparación conNaNno es bien definida - Comparación con NaN devuelve None
- Esto es útil para tipos donde la comparación puede ser parcial o donde algunos valores no tienen un orden definido
use std::cmp::PartialOrd;
fn main() {
let a = 10;
let b = 20;
if a < b {
println!("a es menor que b");
}
let x = f64::NAN;
let y = 1.0;
// Comparación con NaN devuelve None
match x.partial_cmp(&y) {
Some(ordering) => println!("Comparación: {:?}", ordering),
None => println!("Comparación no definida"),
}
}
Ord
- El trait
OrdextiendePartialOrdy proporciona una comparación de orden total - Es adecuado para tipos que tienen una relación de orden bien definida y completa
- Garantiza que todos los valores se pueden comparar entre sí, sin casos donde la comparación no está definida.
- Permite comparaciones de orden total usando los operadores de comparación (
<,<=,>,>=).
use std::cmp::Ord;
use std::cmp::Ordering;
fn main() {
let a = 20;
let b = 20;
match a.cmp(&b) {
Ordering::Less => println!("'a' es menor que 'b'"),
Ordering::Equal => println!("'a' es igual a 'b'"),
Ordering::Greater => println!("'a' es mayor que 'b'"),
}
}
Ejemplo con estructuras
La comparación de orden para los campos se realiza en el mismo orden en que están definidos en la estructura
Primero compara el primer campo, luego el segundo campo, y así sucesivamente. Esto significa que:
- Primero se compara el campo
nombredePersona. - Si los valores de
nombreson iguales, se compara el campoedad.
Rust compara el campo nombre de persona1 con el de persona2.
Dado que "Juan" es mayor que "Ana" lexicográficamente (según el orden de los caracteres en la cadena), persona1 se considera mayor que persona2 en la comparación basada en el campo nombre.
- Entre más "alta" la letra, es mayor
- B es mayor que A
- A es menor que B
Como los nombres son diferentes y el primer campo ya determina el orden, Rust no necesita comparar el campo edad en este caso.
use std::cmp::{PartialOrd, Ord, Ordering};
#[derive(PartialOrd, Ord)]
struct Persona {
nombre: String,
edad: u8,
}
fn main() {
let persona1 = Persona { nombre: "Juan".to_string(), edad: 30 };
let persona2 = Persona { nombre: "Ana".to_string(), edad: 25 };
// Comparar usando PartialOrd
if persona1 < persona2 {
println!("persona1 es menor que persona2");
} else {
println!("persona1 no es menor que persona2");
}
// Comparar usando Ord
match persona1.cmp(&persona2) {
Ordering::Less => println!("persona1 es menor que persona2"),
Ordering::Equal => println!("persona1 es igual a persona2"),
Ordering::Greater => println!("persona1 es mayor que persona2"),
}
}
Serialize y Deserialize (serde y serde_json)
- A través del paquete (
crate)serde - Estos traits permiten convertir estructuras y enumeraciones en formatos de datos (como JSON) y viceversa.
- Si se desea trabajar con algún formato, como JSON, se deben agregar ambos paquetes, el principal
serdey el de la implementación específica, comoserde_json
[package]
name = "exercises"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "=1.0.127"
serde
- Define traits como
SerializeyDeserializeque pueden ser implementados o derivados para los tipos personalizados - Puede serializar y deserializar a y desde una amplia variedad de formatos, no solo JSON. Otros ejemplos incluyen YAML, TOML, XML, BSON, etc.
serde_json
- Depende de
serdey lo utiliza como base - Es un paquete específico para trabajar con JSON
- Proporciona funcionalidad específica para serializar a JSON y deserializar desde JSON
- Ofrece métodos como
to_stringyfrom_strpara convertir rápidamente entre JSON y estructuras de Rust. Value, que representa un valor JSON genérico
serde_derive
Anteriormente, serde_derive era un crate separado, pero en versiones más recientes de serde, las macros de derivación (#[derive(Serialize, Deserialize)]) se incluyen como parte de serde cuando se habilita la característica "derive".
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Persona {
nombre: String,
edad: u8,
email: String,
}
fn main() {
// Crear una instancia de Persona
let persona = Persona {
nombre: "Juan Pérez".to_string(),
edad: 30,
email: "[email protected]".to_string(),
};
// Serializar la instancia a JSON
let json = serde_json::to_string(&persona).unwrap();
println!("JSON: {}", json);
// Deserializar de JSON a una instancia de Persona
let persona_de_json: Persona = serde_json::from_str(&json).unwrap();
println!("STRUCT PERSONA: {:#?}", persona_de_json);
}
Convertir de Struct a JSON
// Serializar la instancia a JSON
let json = serde_json::to_string(&persona).unwrap();
println!("JSON: {}", json);
Convertir de JSON a Struct
// Deserializar de JSON a una instancia de Persona
let persona_de_json: Persona = serde_json::from_str(&json).unwrap();
println!("Sruct Persona: {:?}", persona_de_json);