Rust
Tutorial Rust: Traits
Aprende a definir, implementar y usar traits en Rust, incluyendo traits como parámetros y la implementación externa para tipos propios y externos.
Aprende Rust y certifícateDefinición e implementación
Los traits son una de las características más potentes de Rust, permitiendo definir comportamientos compartidos entre diferentes tipos. Si estás familiarizado con otros lenguajes, puedes pensar en ellos como algo similar a las interfaces, aunque con capacidades adicionales.
Un trait define una colección de métodos que un tipo debe implementar para "cumplir" con ese trait. Esto permite escribir código que funciona con cualquier tipo que implemente un comportamiento específico, sin necesidad de conocer el tipo concreto de antemano.
Definiendo un trait
Para definir un trait en Rust, utilizamos la palabra clave trait
seguida del nombre (por convención en PascalCase) y un bloque que contiene las firmas de los métodos:
trait Describible {
fn describir(&self) -> String;
}
En este ejemplo, hemos definido un trait llamado Describible
que requiere que cualquier tipo que lo implemente proporcione una implementación para el método describir
, que devuelve una cadena de texto.
Implementando un trait
Para implementar un trait para un tipo específico, utilizamos la palabra clave impl
seguida del nombre del trait, la palabra clave for
y el nombre del tipo:
struct Producto {
nombre: String,
precio: f64,
}
impl Describible for Producto {
fn describir(&self) -> String {
format!("{}: ${:.2}", self.nombre, self.precio)
}
}
Ahora cualquier instancia de Producto
puede utilizar el método describir
:
fn main() {
let producto = Producto {
nombre: String::from("Teclado mecánico"),
precio: 89.99,
};
println!("Descripción: {}", producto.describir());
// Imprime: "Descripción: Teclado mecánico: $89.99"
}
Implementaciones por defecto
Una característica poderosa de los traits en Rust es la capacidad de proporcionar implementaciones por defecto para algunos o todos los métodos. Esto permite que los tipos que implementan el trait puedan usar la implementación predeterminada o proporcionar su propia versión:
trait Saludable {
fn nombre(&self) -> String;
fn saludar(&self) -> String {
format!("¡Hola, {}!", self.nombre())
}
}
struct Usuario {
nombre: String,
}
impl Saludable for Usuario {
fn nombre(&self) -> String {
self.nombre.clone()
}
// No implementamos saludar(), usaremos la implementación por defecto
}
En este ejemplo, el trait Saludable
requiere que se implemente el método nombre
, pero proporciona una implementación por defecto para saludar
. Al implementar Saludable
para Usuario
, solo necesitamos proporcionar una implementación para nombre
:
fn main() {
let usuario = Usuario {
nombre: String::from("Ana"),
};
println!("{}", usuario.saludar()); // Imprime: "¡Hola, Ana!"
}
Múltiples métodos en un trait
Los traits pueden definir múltiples métodos, algunos requeridos y otros con implementaciones por defecto:
trait Figura {
fn area(&self) -> f64;
fn perimetro(&self) -> f64;
fn describir(&self) -> String {
format!("Figura con área: {:.2} y perímetro: {:.2}", self.area(), self.perimetro())
}
}
struct Rectangulo {
ancho: f64,
alto: f64,
}
impl Figura for Rectangulo {
fn area(&self) -> f64 {
self.ancho * self.alto
}
fn perimetro(&self) -> f64 {
2.0 * (self.ancho + self.alto)
}
// Usamos la implementación por defecto para describir()
}
Traits con tipos asociados
Los tipos asociados son una característica avanzada de los traits que permite definir un tipo dentro del trait que será especificado por cada implementación:
trait Contenedor {
type Elemento;
fn agregar(&mut self, elemento: Self::Elemento);
fn obtener(&self, indice: usize) -> Option<&Self::Elemento>;
fn longitud(&self) -> usize;
}
struct Vector<T> {
elementos: Vec<T>,
}
impl<T> Contenedor for Vector<T> {
type Elemento = T;
fn agregar(&mut self, elemento: T) {
self.elementos.push(elemento);
}
fn obtener(&self, indice: usize) -> Option<&T> {
self.elementos.get(indice)
}
fn longitud(&self) -> usize {
self.elementos.len()
}
}
En este ejemplo, el trait Contenedor
define un tipo asociado Elemento
. Cuando implementamos este trait para Vector<T>
, especificamos que Elemento
es T
.
Implementando múltiples traits
Un tipo puede implementar múltiples traits, lo que permite una gran flexibilidad:
trait Imprimible {
fn imprimir(&self);
}
trait Comparable {
fn es_igual(&self, otro: &Self) -> bool;
}
struct Libro {
titulo: String,
autor: String,
paginas: u32,
}
impl Imprimible for Libro {
fn imprimir(&self) {
println!("{} por {} ({} páginas)", self.titulo, self.autor, self.paginas);
}
}
impl Comparable for Libro {
fn es_igual(&self, otro: &Self) -> bool {
self.titulo == otro.titulo && self.autor == otro.autor
}
}
Traits derivables
Rust proporciona varios traits que pueden ser derivados automáticamente para tus tipos utilizando el atributo #[derive]
:
#[derive(Debug, Clone, PartialEq)]
struct Punto {
x: f64,
y: f64,
}
En este ejemplo, derivamos:
Debug
: Permite imprimir la estructura con{:?}
en macros comoprintln!
Clone
: Proporciona el métodoclone()
para crear una copia profundaPartialEq
: Permite comparar instancias con==
y!=
Algunos de los traits derivables más comunes son:
Debug
- Para depuraciónClone
yCopy
- Para duplicación de valoresPartialEq
yEq
- Para comparaciones de igualdadPartialOrd
yOrd
- Para ordenaciónHash
- Para hashingDefault
- Para valores por defecto
Traits y métodos sin self
Los traits también pueden definir métodos asociados (similares a métodos estáticos) que no toman self
como parámetro:
trait Creador {
fn crear(nombre: &str) -> Self;
fn crear_predeterminado() -> Self;
}
struct Tarea {
nombre: String,
completada: bool,
}
impl Creador for Tarea {
fn crear(nombre: &str) -> Self {
Tarea {
nombre: nombre.to_string(),
completada: false,
}
}
fn crear_predeterminado() -> Self {
Tarea {
nombre: "Nueva tarea".to_string(),
completada: false,
}
}
}
Estos métodos se pueden llamar utilizando la sintaxis Tarea::crear("Hacer compras")
o <Tarea as Creador>::crear("Hacer compras")
cuando es necesario especificar el trait.
Los traits son fundamentales en Rust para lograr abstracción y polimorfismo de manera segura y eficiente. Permiten escribir código genérico que funciona con cualquier tipo que implemente un comportamiento específico, facilitando la creación de componentes reutilizables y extensibles.
Traits como parámetros
Una vez que hemos definido e implementado traits, el siguiente paso es utilizarlos para crear funciones genéricas que puedan trabajar con cualquier tipo que implemente un determinado comportamiento. En Rust, existen dos formas principales de especificar que un parámetro debe implementar un trait específico.
Trait bounds con sintaxis genérica
La primera forma de utilizar traits como parámetros es mediante trait bounds en funciones genéricas. Esta sintaxis utiliza el operador :
después del parámetro de tipo:
fn imprimir_descripcion<T: Describible>(item: T) {
println!("Descripción: {}", item.describir());
}
En este ejemplo, la función imprimir_descripcion
acepta cualquier tipo T
que implemente el trait Describible
. Esto nos permite llamar al método describir()
dentro de la función, independientemente del tipo concreto que se pase como argumento.
Podemos usar esta función con cualquier tipo que implemente el trait Describible
:
fn main() {
let producto = Producto {
nombre: String::from("Monitor"),
precio: 299.99,
};
imprimir_descripcion(producto);
// Imprime: "Descripción: Monitor: $299.99"
}
Sintaxis impl Trait
La segunda forma, introducida en versiones más recientes de Rust, es la sintaxis impl Trait
. Esta forma es más concisa y se utiliza directamente en la lista de parámetros:
fn imprimir_descripcion(item: impl Describible) {
println!("Descripción: {}", item.describir());
}
Ambas formas son funcionalmente equivalentes para casos simples, pero tienen algunas diferencias sutiles en casos más complejos. La sintaxis impl Trait
es generalmente más legible cuando solo necesitas especificar que un parámetro implementa un trait, sin necesidad de referirte al tipo genérico en otras partes de la función.
Múltiples trait bounds
A veces necesitamos que un tipo implemente varios traits. Podemos especificar múltiples trait bounds utilizando el operador +
:
fn procesar<T: Describible + Clone>(item: T) {
let copia = item.clone();
println!("Original: {}", item.describir());
println!("Copia: {}", copia.describir());
}
Con la sintaxis impl Trait
:
fn procesar(item: impl Describible + Clone) {
let copia = item.clone();
println!("Original: {}", item.describir());
println!("Copia: {}", copia.describir());
}
Cláusula where
Cuando tenemos múltiples parámetros genéricos con varios trait bounds, la sintaxis puede volverse difícil de leer. Para estos casos, Rust proporciona la cláusula where
, que permite especificar los trait bounds de manera más clara:
fn comparar<T, U>(item1: T, item2: U) -> bool
where
T: Describible + PartialEq,
U: Describible + PartialEq + Clone,
{
println!("Comparando {} con {}", item1.describir(), item2.describir());
// Implementación de la comparación...
true
}
La cláusula where
hace que el código sea más legible, especialmente cuando hay múltiples parámetros genéricos con varios trait bounds.
Retornando tipos que implementan traits
También podemos utilizar traits para especificar el tipo de retorno de una función. Esto es particularmente útil cuando queremos devolver diferentes tipos que comparten un comportamiento común:
fn crear_figura(tipo: &str) -> impl Figura {
match tipo {
"rectangulo" => Rectangulo { ancho: 3.0, alto: 4.0 },
"circulo" => Circulo { radio: 5.0 },
_ => Cuadrado { lado: 2.0 },
}
}
En este ejemplo, la función crear_figura
puede devolver diferentes tipos (Rectangulo, Circulo, Cuadrado), pero todos implementan el trait Figura
. Esto permite al código que llama a esta función trabajar con el resultado sin conocer el tipo concreto.
Sin embargo, hay una limitación importante: cuando usas impl Trait
como tipo de retorno, solo puedes devolver un tipo concreto dentro de la función. El siguiente código no compilaría:
// Este código NO compila
fn crear_figura(tipo: &str) -> impl Figura {
if tipo == "rectangulo" {
Rectangulo { ancho: 3.0, alto: 4.0 }
} else {
Circulo { radio: 5.0 } // Error: tipos de retorno incompatibles
}
}
Traits como parámetros en métodos
Los traits también pueden utilizarse como parámetros en métodos de estructuras o enumeraciones:
struct Coleccion<T> {
items: Vec<T>,
}
impl<T> Coleccion<T> {
fn nuevo() -> Self {
Coleccion { items: Vec::new() }
}
fn agregar(&mut self, item: T) {
self.items.push(item);
}
fn procesar_con<F>(&self, procesador: F)
where
F: Fn(&T),
{
for item in &self.items {
procesador(item);
}
}
}
En este ejemplo, el método procesar_con
acepta cualquier función o closure que implemente el trait Fn(&T)
, lo que significa que puede tomar una referencia a un elemento de tipo T
y hacer algo con él.
Ejemplo práctico: filtrado genérico
Veamos un ejemplo más completo que muestra cómo los traits como parámetros pueden hacer nuestro código más flexible:
trait Filtrable {
fn cumple_criterio(&self) -> bool;
}
struct Producto {
nombre: String,
precio: f64,
en_stock: bool,
}
impl Filtrable for Producto {
fn cumple_criterio(&self) -> bool {
self.en_stock && self.precio < 100.0
}
}
struct Usuario {
nombre: String,
edad: u32,
activo: bool,
}
impl Filtrable for Usuario {
fn cumple_criterio(&self) -> bool {
self.activo && self.edad >= 18
}
}
fn filtrar_elementos<T: Filtrable>(elementos: &[T]) -> Vec<&T> {
elementos
.iter()
.filter(|elemento| elemento.cumple_criterio())
.collect()
}
fn main() {
let productos = vec![
Producto { nombre: "Teclado".to_string(), precio: 89.99, en_stock: true },
Producto { nombre: "Monitor".to_string(), precio: 299.99, en_stock: true },
Producto { nombre: "Ratón".to_string(), precio: 49.99, en_stock: false },
];
let usuarios = vec![
Usuario { nombre: "Ana".to_string(), edad: 25, activo: true },
Usuario { nombre: "Carlos".to_string(), edad: 17, activo: true },
Usuario { nombre: "Elena".to_string(), edad: 30, activo: false },
];
let productos_filtrados = filtrar_elementos(&productos);
let usuarios_filtrados = filtrar_elementos(&usuarios);
println!("Productos que cumplen el criterio: {}", productos_filtrados.len());
println!("Usuarios que cumplen el criterio: {}", usuarios_filtrados.len());
}
En este ejemplo, hemos definido un trait Filtrable
que requiere un método cumple_criterio()
. Luego implementamos este trait para dos tipos diferentes (Producto
y Usuario
) con lógicas de filtrado específicas para cada tipo. La función filtrar_elementos
puede trabajar con cualquier colección de elementos que implementen el trait Filtrable
.
Traits como parámetros en constructores
También podemos utilizar traits como parámetros en los constructores de nuestros tipos:
struct Registro<W: std::io::Write> {
escritor: W,
nivel: String,
}
impl<W: std::io::Write> Registro<W> {
fn nuevo(escritor: W, nivel: &str) -> Self {
Registro {
escritor,
nivel: nivel.to_string(),
}
}
fn log(&mut self, mensaje: &str) -> std::io::Result<()> {
writeln!(self.escritor, "[{}] {}", self.nivel, mensaje)
}
}
fn main() -> std::io::Result<()> {
let archivo = std::fs::File::create("log.txt")?;
let mut registro_archivo = Registro::nuevo(archivo, "INFO");
registro_archivo.log("Mensaje guardado en archivo")?;
let mut registro_consola = Registro::nuevo(std::io::stdout(), "DEBUG");
registro_consola.log("Mensaje mostrado en consola")?;
Ok(())
}
En este ejemplo, la estructura Registro
acepta cualquier tipo que implemente el trait std::io::Write
, lo que nos permite crear registros que escriban en diferentes destinos (archivos, consola, red, etc.).
Los traits como parámetros son una herramienta fundamental en Rust para escribir código genérico y reutilizable. Permiten definir comportamientos que pueden ser implementados por diferentes tipos, facilitando la creación de abstracciones poderosas sin sacrificar la seguridad ni el rendimiento.
Implementación externa
En Rust, una de las características más flexibles del sistema de traits es la capacidad de implementar traits para tipos que no te pertenecen. Esta característica permite extender la funcionalidad de tipos existentes sin modificar su código original, siguiendo un patrón similar al de las "extensiones" en otros lenguajes.
Sin embargo, Rust impone una restricción importante conocida como la regla de coherencia (coherence rule), que establece límites claros sobre qué implementaciones están permitidas.
La regla de coherencia
La regla de coherencia en Rust establece que:
- Puedes implementar un trait externo para un tipo propio
- Puedes implementar un trait propio para un tipo externo
- No puedes implementar un trait externo para un tipo externo
Esta regla evita conflictos y ambigüedades que podrían surgir si dos crates diferentes implementaran el mismo trait para el mismo tipo.
// ✅ Implementando trait estándar (externo) para tipo propio
impl std::fmt::Display for MiTipo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MiTipo: {}", self.valor)
}
}
// ✅ Implementando trait propio para tipo estándar (externo)
trait Invertible {
fn invertir(&self) -> Self;
}
impl Invertible for String {
fn invertir(&self) -> Self {
self.chars().rev().collect()
}
}
// ❌ Esto NO compilaría: implementando trait externo para tipo externo
// impl std::fmt::Display for Vec<i32> { ... }
Implementando traits estándar para tipos propios
Una de las aplicaciones más comunes de la implementación externa es añadir comportamientos estándar a nuestros propios tipos:
struct Coordenada {
x: f64,
y: f64,
}
// Implementamos Display para permitir formateo personalizado
impl std::fmt::Display for Coordenada {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "({:.2}, {:.2})", self.x, self.y)
}
}
// Implementamos Add para permitir la suma de coordenadas con el operador +
impl std::ops::Add for Coordenada {
type Output = Self;
fn add(self, otra: Self) -> Self::Output {
Coordenada {
x: self.x + otra.x,
y: self.y + otra.y,
}
}
}
fn main() {
let p1 = Coordenada { x: 1.0, y: 2.0 };
let p2 = Coordenada { x: 3.0, y: 4.0 };
let suma = p1 + p2;
println!("La suma de coordenadas es: {}", suma);
// Imprime: "La suma de coordenadas es: (4.00, 6.00)"
}
En este ejemplo, hemos implementado dos traits estándar para nuestro tipo Coordenada
:
std::fmt::Display
para permitir imprimir la coordenada con formato personalizadostd::ops::Add
para permitir sumar coordenadas usando el operador+
Implementando traits propios para tipos externos
También podemos extender tipos que no nos pertenecen con comportamientos específicos para nuestra aplicación:
trait Estadisticas {
fn suma(&self) -> f64;
fn promedio(&self) -> f64;
fn desviacion_estandar(&self) -> f64;
}
impl Estadisticas for Vec<f64> {
fn suma(&self) -> f64 {
self.iter().sum()
}
fn promedio(&self) -> f64 {
if self.is_empty() {
0.0
} else {
self.suma() / self.len() as f64
}
}
fn desviacion_estandar(&self) -> f64 {
if self.is_empty() || self.len() == 1 {
return 0.0;
}
let prom = self.promedio();
let varianza: f64 = self.iter()
.map(|&x| (x - prom).powi(2))
.sum::<f64>() / (self.len() - 1) as f64;
varianza.sqrt()
}
}
fn main() {
let datos = vec![2.0, 4.0, 6.0, 8.0, 10.0];
println!("Suma: {}", datos.suma());
println!("Promedio: {}", datos.promedio());
println!("Desviación estándar: {}", datos.desviacion_estandar());
}
En este ejemplo, hemos definido un trait Estadisticas
y lo hemos implementado para el tipo Vec<f64>
de la biblioteca estándar, añadiendo métodos para calcular estadísticas básicas.
Implementación condicional con genéricos
Podemos implementar traits de forma condicional para tipos genéricos, lo que nos permite añadir comportamientos solo cuando se cumplen ciertas condiciones:
struct Contenedor<T> {
valor: T,
}
// Implementamos ToString solo si T implementa Display
impl<T: std::fmt::Display> ToString for Contenedor<T> {
fn to_string(&self) -> String {
format!("Contenedor que almacena: {}", self.valor)
}
}
// Implementamos Default solo si T implementa Default
impl<T: Default> Default for Contenedor<T> {
fn default() -> Self {
Contenedor {
valor: T::default(),
}
}
}
Implementación de traits para enums
También podemos implementar traits para enumeraciones, lo que es especialmente útil para tipos que representan variantes:
enum Resultado {
Exito(String),
Error(u32, String),
EnProceso,
}
impl std::fmt::Display for Resultado {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Resultado::Exito(msg) => write!(f, "Operación exitosa: {}", msg),
Resultado::Error(codigo, msg) => write!(f, "Error {}: {}", codigo, msg),
Resultado::EnProceso => write!(f, "Operación en proceso..."),
}
}
}
fn main() {
let r1 = Resultado::Exito(String::from("Datos guardados"));
let r2 = Resultado::Error(404, String::from("Recurso no encontrado"));
println!("{}", r1); // Imprime: "Operación exitosa: Datos guardados"
println!("{}", r2); // Imprime: "Error 404: Recurso no encontrado"
}
Implementación de traits con tipos asociados
Los traits con tipos asociados son particularmente útiles cuando implementamos comportamientos para tipos externos:
trait Coleccionable {
type Item;
fn agregar(&mut self, item: Self::Item);
fn contiene(&self, item: &Self::Item) -> bool;
}
impl<T: PartialEq> Coleccionable for Vec<T> {
type Item = T;
fn agregar(&mut self, item: T) {
self.push(item);
}
fn contiene(&self, item: &T) -> bool {
self.contains(item)
}
}
impl<K, V> Coleccionable for std::collections::HashMap<K, V>
where
K: PartialEq + std::hash::Hash,
V: PartialEq,
{
type Item = (K, V);
fn agregar(&mut self, item: Self::Item) {
self.insert(item.0, item.1);
}
fn contiene(&self, item: &Self::Item) -> bool {
self.get(&item.0).map_or(false, |v| v == &item.1)
}
}
En este ejemplo, implementamos el trait Coleccionable
tanto para Vec<T>
como para HashMap<K, V>
, adaptando el comportamiento a las características específicas de cada estructura de datos.
Extensión de tipos primitivos
Incluso podemos extender tipos primitivos con nuestros propios comportamientos:
trait NumeroExtendido {
fn es_par(&self) -> bool;
fn es_primo(&self) -> bool;
fn factores(&self) -> Vec<Self> where Self: Sized;
}
impl NumeroExtendido for u32 {
fn es_par(&self) -> bool {
self % 2 == 0
}
fn es_primo(&self) -> bool {
if *self <= 1 {
return false;
}
if *self <= 3 {
return true;
}
if self.es_par() || *self % 3 == 0 {
return false;
}
let limite = (*self as f64).sqrt() as u32 + 1;
let mut i = 5;
while i < limite {
if *self % i == 0 || *self % (i + 2) == 0 {
return false;
}
i += 6;
}
true
}
fn factores(&self) -> Vec<Self> {
let mut resultado = Vec::new();
let mut n = *self;
let mut divisor = 2;
while n > 1 {
while n % divisor == 0 {
resultado.push(divisor);
n /= divisor;
}
divisor += 1;
}
resultado
}
}
fn main() {
let numero = 42u32;
println!("¿{} es par? {}", numero, numero.es_par());
println!("¿{} es primo? {}", numero, numero.es_primo());
println!("Factores de {}: {:?}", numero, numero.factores());
}
Consideraciones prácticas
Al implementar traits externos para tipos propios o traits propios para tipos externos, hay algunas consideraciones importantes:
Organización del código: Mantén las implementaciones de traits cerca del código que las utiliza para mejorar la legibilidad.
Documentación: Documenta claramente qué comportamientos estás añadiendo a tipos externos para facilitar el mantenimiento.
Evita colisiones de nombres: Asegúrate de que los nombres de métodos que añades no entren en conflicto con métodos existentes o futuros.
Pruebas: Las implementaciones externas deben probarse exhaustivamente, especialmente cuando extiendes tipos de la biblioteca estándar.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_estadisticas() {
let datos = vec![2.0, 4.0, 6.0, 8.0, 10.0];
assert_eq!(datos.suma(), 30.0);
assert_eq!(datos.promedio(), 6.0);
assert!((datos.desviacion_estandar() - 3.16).abs() < 0.01);
}
}
La implementación externa de traits es una herramienta poderosa que permite extender la funcionalidad de tipos existentes de forma segura y controlada. La regla de coherencia de Rust garantiza que estas extensiones no causen conflictos, mientras que el sistema de tipos estático asegura que todas las implementaciones son correctas en tiempo de compilación.
Otras lecciones de Rust
Accede a todas las lecciones de Rust y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Rust
Introducción Y Entorno
Primer Programa
Introducción Y Entorno
Instalación Del Entorno
Introducción Y Entorno
Funciones
Sintaxis
Operadores
Sintaxis
Estructuras De Control Condicional
Sintaxis
Arrays Y Strings
Sintaxis
Manejo De Errores Panic
Sintaxis
Variables Y Tipos Básicos
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Colecciones Estándar
Estructuras De Datos
Option Y Result
Estructuras De Datos
Pattern Matching
Estructuras De Datos
Estructuras (Structs)
Estructuras De Datos
Enumeraciones Enums
Estructuras De Datos
El Concepto De Ownership
Ownership
Lifetimes Básicos
Ownership
Slices Y Referencias Parciales
Ownership
References Y Borrowing
Ownership
Funciones Anónimas Closures
Abstracción
Traits De La Biblioteca Estándar
Abstracción
Traits
Abstracción
Generics
Abstracción
Channels Y Paso De Mensajes
Concurrencia
Memoria Compartida Segura
Concurrencia
Threads Y Sincronización Básica
Concurrencia
Introducción A Tokio
Asincronía
Fundamentos Asíncronos Y Futures
Asincronía
Async/await
Asincronía
Ejercicios de programación de Rust
Evalúa tus conocimientos de esta lección Traits con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué son los traits y cómo definirlos en Rust.
- Implementar traits para tipos propios y externos, incluyendo métodos con implementaciones por defecto.
- Utilizar traits como parámetros en funciones genéricas y métodos, incluyendo sintaxis con trait bounds, impl Trait y cláusula where.
- Aplicar traits con tipos asociados y múltiples traits para lograr abstracción y reutilización de código.
- Entender la regla de coherencia para implementaciones externas y cómo extender tipos estándar o primitivos con traits personalizados.