0Pricing
Learn Rust Coding · Lección

Definición e implementación de traits

Domine los traits para definir comportamientos compartidos entre distintos tipos, de forma similar a las interfaces de otros lenguajes.

Definición e implementación de traits es una lección gratuita de Learn Rust Coding en CoddyKit. Esta es la lección 2 de 3. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Learn Rust Coding, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Learn Rust Coding incluye 3 lecciones en total.

¿Qué son los traits en Rust?

En Rust, los traits son una forma potente de definir comportamientos compartidos entre distintos tipos. Puede considerarlos similares a las interfaces de otros lenguajes.

Un trait indica a Rust que un tipo concreto tiene determinada funcionalidad. Si un tipo implementa un trait, significa que proporciona los métodos definidos por ese trait.

  • Los traits permiten el polimorfismo: trabajar con distintos tipos de manera uniforme.
  • Son fundamentales para el sistema de tipos de Rust y sus abstracciones seguras.

Declarar su primer trait

Defina un trait mediante la palabra clave trait. Dentro de él, enumere las firmas de los métodos (nombres, parámetros y tipos de retorno) que debe proporcionar cualquier tipo que lo implemente.

Definamos un trait sencillo llamado Summary para los elementos que se pueden resumir.

trait Summary {
  fn summarize(&self) -> String;
}

fn main() {
  // Traits are definitions, not directly executable.
  // We will implement and use them in later scenes!
  println!("Trait 'Summary' defined.");
}

Implementar traits para tipos

Para hacer que un tipo use un trait, utilice la sintaxis impl Trait for Type. Después, proporcione la implementación concreta de cada método definido en el trait.

Aquí implementamos Summary para una estructura NewsArticle.

trait Summary {
  fn summarize(&self) -> String;
}

struct NewsArticle {
  headline: String,
  location: String,
  author: String,
  content: String,
}

impl Summary for NewsArticle {
  fn summarize(&self) -> String {
    format!("{}, by {} ({})", self.headline, self.author, self.location)
  }
}

fn main() {
  let article = NewsArticle {
    headline: String::from("Penguins win Stanley Cup!"),
    location: String::from("Pittsburgh, PA"),
    author: String::from("Iceburgh"),
    content: String::from("The Pittsburgh Penguins have won..."),
  };

  println!("New article summary: {}", article.summarize());
}

Usar funciones con restricciones de traits

Una vez que un tipo implementa un trait, puede escribir funciones que acepten cualquier tipo que implemente ese trait. Esto permite crear código flexible y genérico.

La función notify puede aceptar cualquier tipo que implemente Summary.

trait Summary {
  fn summarize(&self) -> String;
}

struct NewsArticle {
  headline: String,
  location: String,
  author: String,
  content: String,
}

impl Summary for NewsArticle {
  fn summarize(&self) -> String {
    format!("{}, by {} ({})", self.headline, self.author, self.location)
  }
}

struct Tweet {
  username: String,
  content: String,
  reply: bool,
  retweet: bool,
}

impl Summary for Tweet {
  fn summarize(&self) -> String {
    format!("{}: {}", self.username, self.content)
  }
}

fn notify(item: &impl Summary) { // impl Summary is syntax sugar for a trait bound
  println!("Breaking news! {}", item.summarize());
}

fn main() {
  let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
  };

  let article = NewsArticle {
    headline: String::from("Rust is awesome!"),
    location: String::from("Internet"),
    author: String::from("Rustacean"),
    content: String::from("Rust's type system is amazing."),
  };

  notify(&tweet);
  notify(&article);
}

Traits con comportamiento predeterminado

Los traits también pueden proporcionar implementaciones predeterminadas para sus métodos. Esto significa que los tipos no tienen que implementar todos los métodos si se proporciona una implementación predeterminada.

Pueden elegir usar la predeterminada o sobrescribirla con su propia lógica.

trait Summary {
  fn summarize_author(&self) -> String; // New required method
  fn summarize(&self) -> String { // Default implementation
    format!("(Read more from {})", self.summarize_author())
  }
}

struct Tweet {
  username: String,
  content: String,
  reply: bool,
  retweet: bool,
}

impl Summary for Tweet {
  fn summarize_author(&self) -> String { // Must implement required method
    format!("@{}", self.username)
  }
  // We are using the default summarize() method here!
}

fn main() {
  let tweet = Tweet {
    username: String::from("dog_lover"),
    content: String::from("My dog is the best!"),
    reply: false,
    retweet: false,
  };

  println!("Tweet summary: {}", tweet.summarize());
}

Funciones genéricas con traits

Al escribir funciones o estructuras genéricas, puede usar restricciones de traits para especificar que un parámetro de tipo genérico T debe implementar un trait determinado.

Esto garantiza que los métodos de ese trait estén disponibles para T.

trait Displayable {
  fn display(&self);
}

struct Point<T> {
  x: T,
  y: T,
}

// Implement Displayable for Point<i32>
impl Displayable for Point<i32> {
  fn display(&self) {
    println!("Point: ({}, {})", self.x, self.y);
  }
}

// Generic function that works for any type T that implements Displayable
fn print_item<T: Displayable>(item: T) {
  item.display();
}

fn main() {
  let p = Point { x: 10, y: 20 };
  print_item(p);
  // This function only works for types that implement Displayable
  // let s = String::from("Hello");
  // print_item(s); // This would cause a compile-time error
}

Combinar varias restricciones de traits

A veces, un tipo genérico debe implementar más de un trait. Puede especificar varias restricciones de traits mediante el operador +.

En situaciones más complejas, especialmente cuando hay muchos parámetros genéricos, la cláusula where puede mejorar la legibilidad.

use std::fmt::Debug;

trait Printable {
  fn print_info(&self);
}

struct Book {
  title: String,
  pages: u32,
}

impl Printable for Book {
  fn print_info(&self) {
    println!("Book: '{}' ({} pages)", self.title, self.pages);
  }
}

impl Debug for Book { // Book also implements Debug trait
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "Book {{ title: {}, pages: {} }}", self.title, self.pages)
  }
}

// A function that requires its argument to be both Printable and Debug
fn process_item<T: Printable + Debug>(item: T) {
  item.print_info();
  println!("Debug info: {:?}", item);
}

fn main() {
  let my_book = Book {
    title: String::from("The Rust Book"),
    pages: 600,
  };

  process_item(my_book);
}

Devolver tipos con `impl Trait`

La sintaxis impl Trait no se usa únicamente para los parámetros de las funciones; también puede utilizarse en las posiciones de retorno. Resulta útil cuando desea devolver un tipo que implemente un trait determinado, pero no quiere exponer su tipo concreto exacto.

Simplifica las firmas de las funciones y mantiene la flexibilidad de su API.

trait Greeter {
  fn greet(&self) -> String;
}

struct FriendlyGreeter;
impl Greeter for FriendlyGreeter {
  fn greet(&self) -> String {
    String::from("Hello there!")
  }
}

struct FormalGreeter;
impl Greeter for FormalGreeter {
  fn greet(&self) -> String {
    String::from("Greetings and salutations.")
  }
}

// This function returns *some* type that implements Greeter
// The caller doesn't need to know if it's FriendlyGreeter or FormalGreeter
fn get_greeter(formal: bool) -> impl Greeter {
  if formal {
    FormalGreeter
  } else {
    FriendlyGreeter
  }
}

fn main() {
  let casual_greet = get_greeter(false);
  println!("{}", casual_greet.greet());

  let formal_greet = get_greeter(true);
  println!("{}", formal_greet.greet());
}

Construir sobre supertraits

A veces, un trait puede depender lógicamente de otro. Rust permite definir supertraits, lo que significa que un trait puede exigir que cualquier tipo que lo implemente también implemente otro trait específico.

Esto se hace con la sintaxis trait SubTrait: SuperTrait. Es como decir: «si puede hacer X, también debe poder hacer Y».

trait Printable {
  fn print_content(&self);
}

// Debug is a supertrait of PrintableDebug
// Any type implementing PrintableDebug must also implement Debug
trait PrintableDebug: Printable + std::fmt::Debug {
  fn print_debug_and_content(&self) {
    self.print_content();
    println!("Debug info: {:?}", self);
  }
}

struct Item {
  id: u32,
  name: String,
}

impl Printable for Item {
  fn print_content(&self) {
    println!("Item ID: {}, Name: {}", self.id, self.name);
  }
}

impl std::fmt::Debug for Item {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "Item {{ id: {}, name: {} }}", self.id, self.name)
  }
}

// Now we can implement PrintableDebug because Item implements both Printable and Debug
impl PrintableDebug for Item {}

fn main() {
  let my_item = Item {
    id: 101,
    name: String::from("Widget A"),
  };
  my_item.print_debug_and_content();
}

Ponga a prueba sus conocimientos sobre traits

Considere el siguiente fragmento de código Rust. ¿Cuál de las siguientes afirmaciones sobre los traits y sus restricciones es VERDADERA?

Traits: su conjunto de herramientas para definir comportamientos

¡Hemos explorado el potente sistema de traits de Rust!

  • Los traits definen un conjunto de métodos que un tipo puede implementar y actúan como interfaces.
  • Los traits se implementan para tipos específicos mediante impl Trait for Type.
  • Las restricciones de traits (T: Trait) permiten que las funciones y estructuras genéricas operen con cualquier tipo que implemente el trait requerido.
  • Los traits pueden tener implementaciones predeterminadas, y puede especificar varias restricciones de traits (T: Trait1 + Trait2) o usar supertraits.
  • impl Trait también puede utilizarse en las posiciones de retorno para ocultar los tipos concretos.

¡Los traits son fundamentales para escribir código Rust flexible, reutilizable y seguro!

Preguntas frecuentes

¿La lección «Definición e implementación de traits» es gratis?

Sí — el texto completo de «Definición e implementación de traits» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Learn Rust Coding, actualiza a CoddyKit PRO. El curso de Learn Rust Coding incluye 3 lecciones en total.

¿Qué aprenderé en «Definición e implementación de traits»?

Domine los traits para definir comportamientos compartidos entre distintos tipos, de forma similar a las interfaces de otros lenguajes. Practicas Learn Rust Coding con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Learn Rust Coding?

No se requiere experiencia previa. Learn Rust Coding en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 3.

¿Cuánto tiempo toma la lección «Definición e implementación de traits»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Learn Rust Coding?

Sí. Cada lección de Learn Rust Coding incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Escritura de código genérico en Rust
  2. Definición e implementación de traits
  3. Uso avanzado de traits: tipos asociados
← Volver a Learn Rust Coding