Buscar..


Introducción

Los rasgos son una forma de describir un "contrato" que una struct debe implementar. Los rasgos generalmente definen las firmas de los métodos, pero también pueden proporcionar implementaciones basadas en otros métodos del rasgo, siempre que los límites de los rasgos lo permitan.

Para aquellos familiarizados con la programación orientada a objetos, los rasgos se pueden considerar como interfaces con algunas diferencias sutiles.

Sintaxis

  • rasgo Rasgo {método fn (...) -> ReturnType; ...}
  • rasgo Rasgo: Bound {método fn (...) -> ReturnType; ...}
  • impl Trait for Type {fn method (...) -> ReturnType {...} ...}
  • impl <T> Rasgo para T donde T: Límites {método fn (...) -> ReturnType {...} ...}

Observaciones

  • Los rasgos se suelen comparar con las interfaces, pero es importante hacer una distinción entre los dos. En lenguajes OO como Java, las interfaces son una parte integral de las clases que las extienden. En Rust, el compilador no sabe nada de los rasgos de una estructura a menos que se usen esos rasgos.

Lo esencial

Creando un Rasgo

trait Speak {
    fn speak(&self) -> String;
}

Implementando un Rasgo

struct Person;
struct Dog;

impl Speak for Person {
    fn speak(&self) -> String {
        String::from("Hello.")
    }
}

impl Speak for Dog {
    fn speak(&self) -> String {
        String::from("Woof.")
    }
}

fn main() {
    let person = Person {};
    let dog = Dog {};
    println!("The person says {}", person.speak());
    println!("The dog says {}", dog.speak());
}

Despacho estático y dinámico

Es posible crear una función que acepte objetos que implementen un rasgo específico.

Despacho estático

fn generic_speak<T: Speak>(speaker: &T) {
    println!("{0}", speaker.speak());
}

fn main() {
    let person = Person {};
    let dog = Dog {};

    generic_speak(&person);
    generic_speak(&dog);
}

Aquí se usa el envío estático, lo que significa que el compilador Rust generará versiones especializadas de la función generic_speak para los tipos Dog y Person . Esta generación de versiones especializadas de una función polimórfica (o cualquier entidad polimórfica) durante la compilación se denomina monomorfización .

Despacho dinámico

fn generic_speak(speaker: &Speak) {
    println!("{0}", speaker.speak());
}

fn main() {
    let person = Person {};
    let dog = Dog {};

    generic_speak(&person as &Speak);
    generic_speak(&dog); // gets automatically coerced to &Speak
}

Aquí, solo existe una versión única de generic_speak en el binario compilado, y la llamada speak() se realiza mediante una búsqueda vtable en tiempo de ejecución. Por lo tanto, el uso del envío dinámico da como resultado una compilación más rápida y un tamaño más pequeño del binario compilado, mientras que es un poco más lento en el tiempo de ejecución.

Los objetos de tipo &Speak o Box<Speak> se denominan objetos de rasgo .

Tipos asociados

  • Utilice el tipo asociado cuando exista una relación de uno a uno entre el tipo que implementa el rasgo y el tipo asociado.
  • A veces también se conoce como el tipo de salida , ya que este es un elemento dado a un tipo cuando le aplicamos un rasgo.

Creación

trait GetItems {
    type First;
//  ^~~~ defines an associated type. 
    type Last: ?Sized;
//           ^~~~~~~~ associated types may be constrained by traits as well
    fn first_item(&self) -> &Self::First;
//                           ^~~~~~~~~~~ use `Self::` to refer to the associated type 
    fn last_item(&self) -> &Self::Last;
//                          ^~~~~~~~~~ associated types can be used as function output...
    fn set_first_item(&mut self, item: Self::First);
//                                     ^~~~~~~~~~~  ... input, and anywhere.
}

Implementacion

impl<T, U: ?Sized> GetItems for (T, U) {
    type First = T;
    type Last = U;
//              ^~~ assign the associated types
    fn first_item(&self) -> &Self::First { &self.0 }
    fn last_item(&self) -> &Self::Last { &self.1 }
    fn set_first_item(&mut self, item: Self::First) { self.0 = item; }
}

impl<T> GetItems for [T; 3] {
    type First = T;
    type Last = T;
    fn first_item(&self) -> &T { &self[0] }
//                           ^ you could refer to the actual type instead of `Self::First`
    fn last_item(&self) -> &T { &self[2] }
    fn set_first_item(&mut self, item: T) { self[0] = item; }
}

Haciendo referencia a tipos asociados.

Si estamos seguros de que un tipo T implementa GetItems por ejemplo, en genéricos, simplemente podríamos usar T::First para obtener el tipo asociado.

fn get_first_and_last<T: GetItems>(obj: &T) -> (&T::First, &T::Last) {
//                                               ^~~~~~~~ refer to an associated type
    (obj.first_item(), obj.last_item())
}

De lo contrario, debe indicar explícitamente al compilador qué rasgo está implementando el tipo

let array: [u32; 3] = [1, 2, 3];
let first: &<[u32; 3] as GetItems>::First = array.first_item();
//          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [u32; 3] may implement multiple traits which many
//                                        of them provide the `First` associated type.
//                                        thus the explicit "cast" is necessary here.
assert_eq!(*first, 1);

Restricción con tipos asociados

fn clone_first_and_last<T: GetItems>(obj: &T) -> (T::First, T::Last)
    where T::First: Clone, T::Last: Clone
//  ^~~~~ use the `where` clause to constraint associated types by traits
{
    (obj.first_item().clone(), obj.last_item().clone())
}

fn get_first_u32<T: GetItems<First=u32>>(obj: &T) -> u32 {
//                          ^~~~~~~~~~~ constraint associated types by equality
    *obj.first_item()
}

Métodos predeterminados

trait Speak {
    fn speak(&self) -> String {
        String::from("Hi.")
    }
}

El método se llamará de forma predeterminada, excepto si se sobrescribe en el bloque impl .

struct Human;
struct Cat;

impl Speak for Human {}

impl Speak for Cat {
    fn speak(&self) -> String {
        String::from("Meow.")
    }
}

fn main() {
    let human = Human {};
    let cat = Cat {};
    println!("The human says {}", human.speak());
    println!("The cat says {}", cat.speak());
}

Salida:

El humano dice hola.

El gato dice miau.

Poner un límite en un rasgo

Al definir un nuevo rasgo, es posible imponer que los tipos que deseen implementar este rasgo verifiquen una serie de restricciones o límites.

Tomando un ejemplo de la biblioteca estándar, el rasgo DerefMut requiere que un tipo implemente primero su rasgo Deref hermano:

pub trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

Esto, a su vez, permite que DerefMut use el tipo asociado Target definido por Deref .


Mientras que la sintaxis podría ser una reminiscencia de la herencia:

  • trae todos los elementos asociados (constantes, tipos, funciones, ...) del rasgo enlazado
  • permite el polimorfismo de &DerefMut a &Deref

Esto es diferente en la naturaleza:

  • es posible usar un tiempo de vida (como 'static ) como un límite
  • no es posible anular los elementos de rasgos vinculados (ni siquiera las funciones)

Por lo tanto, es mejor pensar en él como un concepto separado.

Múltiples tipos de objetos enlazados

También es posible agregar varios tipos de objetos a una función de envío estático .

fn mammal_speak<T: Person + Dog>(mammal: &T) {
    println!("{0}", mammal.speak());
}

fn main() {
    let person = Person {};
    let dog = Dog {};

    mammal_speak(&person);
    mammal_speak(&dog);
}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow