La cláusula where
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 42 de 61.
A medida que agregas más restricciones de trait, las firmas de las funciones pueden volverse difíciles de leer. Considera una función con múltiples parámetros genéricos, cada uno de los cuales requiere varios traits:
fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
// ...
}
Esto funciona, pero la firma se está saturando. La cláusula where ofrece una alternativa más limpia al mover los límites de los traits después de la lista de parámetros:
fn complex_operation<T, U>(first: T, second: U)
where
T: Display + Clone,
U: Debug + Summary,
{
// ...
}
Ambas versiones son funcionalmente idénticas; la cláusula where trata puramente sobre la legibilidad. Los requisitos de cada tipo aparecen en su propia línea, lo que facilita ver rápidamente lo que cada parámetro genérico necesita.
La cláusula where se vuelve especialmente valiosa cuando los límites son complejos o cuando tienes muchos parámetros genéricos. Mantiene el nombre de la función y los parámetros visibles de un vistazo, con las restricciones enumeradas por separado a continuación. Este es el estilo preferido en la mayoría de las bases de código de Rust cuando los límites se extienden más allá de un simple T: Trait.
Desafío
Fácil¡Vamos a construir un sistema de procesamiento de datos que utiliza la cláusula where para mantener legibles los límites de rasgos (trait bounds) complejos! Crearás una función genérica con múltiples parámetros de tipo, cada uno de los cuales requiere diferentes rasgos, y organizarás los límites de forma limpia utilizando la sintaxis where.
Organizarás tu código en dos archivos:
processor.rs: Define dos rasgos públicos y una función genérica que los utilice a ambos:Summarize— un rasgo con un métodosummaryque toma&selfy devuelve unStringValidate— un rasgo con un métodovalidateque toma&selfy devuelve unbool
process_itemsque acepte dos parámetros de diferentes tipos genéricos. Usa una cláusulawherepara especificar que el primer tipo debe implementar tantoClonecomoSummarize, mientras que el segundo tipo debe implementarValidate. La función debe imprimir el resumen del primer elemento y luego imprimir si el segundo elemento es válido.main.rs: Crea dos estructuras públicas que implementen los rasgos requeridos:Article— con un campo públicotitle(String). ImplementaSummarizepara que devuelva"Article: {}"con el título, y derivaClone.Form— con un campo públicofilled(bool). ImplementaValidatepara que devuelva el valor defilled.
process_itemscon ambos.
La cláusula where hace que la firma de tu función sea mucho más limpia que amontonar todos los límites dentro de los paréntesis angulares. Los requisitos de cada tipo aparecen en su propia línea, lo que facilita ver qué necesita cada parámetro genérico.
Tu salida debe mostrar el resumen seguido del resultado de la validación:
Article: {title}
Valid: {true/false}Por ejemplo, con las entradas Rust Tips y true:
Article: Rust Tips
Valid: trueY con las entradas Breaking News y false:
Article: Breaking News
Valid: falseRecibirás dos entradas: el título del artículo y si el formulario está completo (analízalo como bool).
Hoja de referencia
La cláusula where proporciona una forma más limpia de especificar límites de rasgos (trait bounds) para funciones genéricas al mover las restricciones después de la lista de parámetros:
fn complex_operation<T, U>(first: T, second: U)
where
T: Display + Clone,
U: Debug + Summary,
{
// ...
}
Esto es funcionalmente idéntico a los límites de rasgos en línea, pero mejora la legibilidad:
fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
// ...
}
La cláusula where es especialmente útil cuando:
- Tienes múltiples parámetros genéricos
- Cada parámetro requiere varios límites de rasgos
- Quieres mantener la firma de la función limpia y fácil de leer
Los requisitos de cada tipo aparecen en su propia línea, lo que facilita ver qué necesita cada parámetro genérico. Este es el estilo preferido en la mayoría de las bases de código de Rust cuando los límites se extienden más allá de un simple T: Trait.
Pruébalo tú mismo
mod processor;
use processor::{Summarize, Validate, process_items};
use std::io;
// TODO: Definir una estructura pública Article con un campo público title (String)
// Derivar Clone para Article
// TODO: Implementar Summarize para Article
// El método summary debe devolver "Article: {title}"
// TODO: Definir una estructura pública Form con un campo público filled (bool)
// TODO: Implementar Validate para Form
// El método validate debe devolver el valor de filled
fn main() {
let mut title = String::new();
io::stdin().read_line(&mut title).expect("Failed to read line");
let title = title.trim().to_string();
let mut filled_input = String::new();
io::stdin().read_line(&mut filled_input).expect("Failed to read line");
let filled: bool = filled_input.trim().parse().expect("Failed to parse bool");
// TODO: Crear un Article con el título dado
// TODO: Crear un Form con el valor filled dado
// TODO: Llamar a process_items con el artículo y el formulario
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Métodos y Comportamiento
Intro a Bloques de ImplementaciónEl Parámetro SelfMétodos MutablesFunciones AsociadasMúltiples Bloques de ImplementaciónEncadenamiento de MétodosResumen - Acciones de Rectángulo4Proyecto: Mascota virtual
Definir la mascotaAlimentar a la mascota7Traits estándar
El trait DebugEl trait DisplayClone y CopyTraits de igualdadResumen - Punto imprimible10Proyecto: Sistema de documentos
El Trait DrawComponente de texto2Encapsulamiento y módulos
Conceptos básicos de módulosLa palabra clave PublicCampos privadosGettersSettersRepaso - Casillero seguro5Genéricos
Estructuras genéricasMétodos genéricosMúltiples tipos genéricosFunciones genéricasResumen - Punto de coordenadas8Traits como límites
Sintaxis de límites de traitsLímites múltiplesLa cláusula whereRetornar tipos con traitsResumen: Impresora genérica11Patrones de diseño en Rust
Patrón NewtypeComposiciónEl Trait DropFrom e IntoResumen - Mock de Smart Pointer3Enums avanzados
Enums con datosMétodos en EnumsCoincidencia de variantes de datosEl Enum Option revisitadoResumen - Enum Shape6Definición de Traits
¿Qué es un Trait?Implementación de TraitsImplementaciones por defectoSobrescribir valores por defectoTraits con parámetrosResumen - Reproductor multimedia