Un rasgo le dice al compilador de Rust sobre la funcionalidad que tiene un tipo en particular y que puede compartir con otros tipos. Los rasgos son una definición abstracta del comportamiento compartido entre diferentes tipos. Entonces, podemos decir que los rasgos son para Rust lo que las interfaces son para Java o las clases abstractas para C++. Un método de rasgo puede acceder a otros métodos dentro de ese rasgo.
Definición de rasgo
Los rasgos se definen proporcionando el nombre del rasgo seguido de la palabra clave «rasgo». Al definir cualquier rasgo, tenemos que proporcionar una declaración de método dentro del rasgo.
Definición de un rasgo:
pub trait Detail { fn Description(&self) -> i32; fn years_since_launched(&self) -> i32; }
En el ejemplo anterior, hemos definido un rasgo llamado Detalle y declaramos dos métodos ( Descripción(), años_since_lanzamiento()) con &self como parámetro y establecemos un tipo de retorno en i32. Por lo tanto, cada vez que un Tipo implemente este Rasgo, deberá anular ambos métodos y deberá definir su cuerpo personalizado.
Implementación de rasgos:
La implementación de rasgos es similar a la implementación de una interfaz en otros lenguajes como Java o C++.
Aquí usamos la palabra clave «impl» para implementar un Trait, luego escribimos el Nombre del Trait que tenemos que implementar, y luego usamos la palabra clave «for» para definir para quién vamos a implementar un Trait. Definiremos cuerpos de métodos para todos los métodos que han sido declarados en la Definición de Trait dentro del bloque impl .
Implementación del rasgo:
impl trait_Name for type_Name { /// method definitions /// }
Entendamos la implementación de ejemplos de rasgos:
Ejemplo 1:
Implementemos un rasgo incorporado llamado Detalle en una estructura de automóvil:
Rust
// Defining a Detail trait by defining the // functionality it should include pub trait Detail { fn description(&self) -> String; fn years_since_launched(&self) -> i32; } struct Car { brand_name : String, color: String, launched_year : i32 } // Implementing an in-built trait Detail // on the Car struct impl Detail for Car { // Method returns an overview of the car fn description(&self) -> String{ return format!("I have a {} which is {} in color.", self.brand_name, self.color); } // Method returns the number of years between // the launched year of this car i.e. // 2020 and the release year of the movie fn years_since_launched(&self) -> i32{ return 2021 - self.launched_year; } } fn main() { let car = Car{ brand_name: "WagonR".to_string(), color: "Red".to_string(), launched_year:1992 }; let car2 = Car{ brand_name: "Venue".to_string(), color: "White".to_string(), launched_year:1997 }; println!("{}", car.description()); println!("The car was released {} years ago.\n", car.years_since_launched()); println!("{}", car.description()); println!("The car was released {} years ago.", car.years_since_launched()); }
Producción:
I have a WagonR which is Red in color. The car was released 29 years ago. I have a WagonR which is Red in color. The car was released 29 years ago.
Ejemplo 2:
Implementemos un rasgo incorporado llamado Matemáticas en una estructura de parámetros:
Rust
// Defining a Maths trait by defining // the functionality it should include pub trait Maths { fn area_of_rectangle(&self) -> i32; fn perimeter_of_rectangle(&self) -> i32; } struct Parameter { l:i32, b:i32 } // Implementing an in-built trait Detail // on the Parameter struct impl Maths for Parameter { // Method returns area of rectangle fn area_of_rectangle(&self) -> i32{ return self.l*self.b ; } // Method returns the perimeter of rectangle fn perimeter_of_rectangle(&self) -> i32{ return 2*(self.l+self.b); } } fn main() { let para =Parameter{ l: 5, b: 6 }; println!("The area of rectangle is {}.", para.area_of_rectangle()); println!("The perimeter of the rectangle is {}.", para.perimeter_of_rectangle()); }
Producción:
The area of rectangle is 30. The perimeter of the rectangle is 22.
Caída de rasgo:
El rasgo de caída es importante para el patrón de puntero inteligente. Drop trait nos permite personalizar lo que sucede cuando un valor está a punto de quedar fuera del alcance. La funcionalidad de soltar rasgo casi siempre se usa cuando se implementa un puntero inteligente. Por ejemplo, Box<T> personaliza Drop para desasignar el espacio en el montón al que apunta el cuadro.
Ejemplo:
Rust
struct SmartPointer { data: String, } // implementing Drop trait impl Drop for SmartPointer { fn drop(&mut self) { println!("Dropping SmartPointer with data `{}`!", self.data); } } fn main() { let _c = SmartPointer { data: String::from("my stuff") }; let _d = SmartPointer { data: String::from("other stuff") }; println!("SmartPointers created."); }
En el ejemplo anterior, hay una estructura SmartPointer cuya funcionalidad personalizada es imprimir Dropping SmartPointer cuando la instancia queda fuera del alcance.
Producción:
SmartPointers created. Dropping SmartPointer with data `other stuff`! Dropping SmartPointer with data `my stuff`!
Rasgo de iterador:
El rasgo Iterator implementa los iteradores sobre colecciones como arrays. El rasgo de iterador relaciona cada tipo de iterador con el tipo de valor que produce.
El rasgo solo requiere que se defina un método para el siguiente elemento, que puede definirse manualmente en un bloque impl (como en arrays y rangos), que devuelve un elemento del iterador a la vez (Opción<Elemento>), el siguiente lo hará devuelve Some(Item) siempre que haya elementos y cuando finaliza la iteración, devuelve None para indicar que la iteración ha finalizado.
Rasgo del clon:
El rasgo de clonación es para los tipos que pueden hacer copias de sí mismos. El clon se define de la siguiente manera:
Rust
trait Clone: Sized { fn clone(&self) -> Self; fn clone_from(&mut self, source: &Self) { *self = source.clone() } }
El método de clonación debe construir una copia independiente de sí mismo y devolverla. Dado que el tipo de devolución del método es propio y las funciones pueden no devolver valores sin tamaño, el rasgo Clonar en sí mismo extiende el rasgo Tamaño (tipos propios para ser dimensionados)
Rust no clona automáticamente los valores, pero le permite realizar una llamada de método explícita. Los tipos de punteros contados por referencia como Rc<T> y Arc<T> son excepciones: la clonación de uno de estos simplemente incrementa el recuento de referencias y le entrega un nuevo puntero.
superrasgo:
Si es posible que necesitemos un rasgo para usar la funcionalidad de otro rasgo. En esta situación, debemos confiar en el rasgo dependiente que también se está implementando. Rust tiene una forma de especificar que un rasgo es una extensión de otro rasgo, dándonos algo similar a la subclasificación en otros idiomas.
Para crear un subrasgo, indica que implementa el superrasgo de la misma manera que lo harías con un tipo:
Rust
trait MyDebug : std::fmt::Debug { fn my_subtrait_function(&self); }
Rasgos que regresan con dyn:
Un objeto de rasgo en Rust es similar a un objeto en Java o C++. Un objeto de rasgo siempre pasa por un puntero y tiene un vtable para que los métodos se puedan enviar dinámicamente. VTable es un tipo de array de puntero de función que contiene las direcciones de todas las funciones virtuales de esta clase.
El tipo de objeto de rasgo usa dyn Rasgo:
p.ej; &din Bar o Caja<dyn Bar>
Función usando objetos de rasgos:
fn f(b: &dyn Bar) -> usize use std::fmt::Debug; fn dyn_trait(n: u8) -> Box<dyn Debug> { todo!() }
La función dyn_trait puede devolver cualquier cantidad de tipos que implementen el rasgo de depuración e incluso puede devolver un tipo diferente según el argumento de entrada.
La variable única, el argumento o el valor devuelto pueden tomar valores de varios tipos diferentes. Pero el despacho virtual tiende a llamar a métodos más lentos. Y los objetos deben pasarse por puntero.
Ejemplo:
Rust
struct Breakfast{} struct Dinner{} trait Food { fn dish(&self) -> &'static str; } // Implement the `Food` trait for `breakfast`. impl Food for Breakfast { fn dish(&self) -> &'static str { "bread-butter!" } } // Implement the `Food` trait for `dinner`. impl Food for Dinner { fn dish(&self) -> &'static str { "paneer-butter-masala!" } } // Returns some struct that implements Food, // but we don't know which one at compile time. fn eat(n: i32) -> Box<dyn Food> { if n < 8 { Box::new(Breakfast {}) } else { Box::new(Dinner {}) } } fn main() { let n = 3; let food = eat(n); println!("You have chosen a random dish for today {}", food.dish()); }
Producción:
You have chosen a random dish for today bread-butter!
Concepto de #[derivar]:
El compilador puede usar el atributo #[derive] para proporcionar la implementación básica de algunos rasgos. Si se requiere un comportamiento más complejo, los rasgos se pueden implementar manualmente.
La siguiente es una lista de rasgos derivables:
Parámetros | Descripción |
---|---|
Rasgos de comparación | Eq, PartialEq, Ord, PartialOrd |
Rasgos del clon | Para crear T a partir de &T |
copiar rasgos | Para dar un tipo ‘copiar semántica’ |
Publicación por entregas | Codificable, Decodificable |
Rand | Para crear una instancia aleatoria de un tipo de datos |
Rasgos de hash | Para calcular un hash de &T |
Cero | Para crear una instancia cero de un tipo de datos numérico |
DePrimitivo | Para crear una instancia a partir de una primitiva numérica |
Rasgos predeterminados | Para crear una instancia vacía de un tipo de datos |
Rasgos de depuración | Para formatear un valor usando el formateador {:?} |
Echa un vistazo al siguiente código:
Rust
#[derive(HelloWorld)] struct Hello; fn main() { Hello::hello_world(); }
El atributo de derivación permite que se generen automáticamente nuevos elementos para las estructuras de datos. Utiliza la sintaxis MetaListPaths para especificar una lista de características para implementar o rutas para derivar macros para procesar.
Sobrecarga de operadores a través de rasgos:
La sobrecarga de operadores es personalizar el comportamiento de un operador en situaciones particulares. No podemos crear nuestro propio operador, pero podemos sobrecargar las operaciones y los rasgos correspondientes enumerados en std::ops implementando los rasgos.
Rust
use std::ops::Add; struct Value1(u32); struct Value2(u32); impl Add<Value2> for Value1 { type Output = Value1; fn add(self, other: Value2) ->Value1 { Value1(self.0 + (other.0 * 1000)) } }
Publicación traducida automáticamente
Artículo escrito por sushmanag73 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA