Tuplas en Rust
Una tupla agrupa varios valores, que pueden ser de tipos distintos, en uno solo. Es una forma rápida de juntar cosas relacionadas sin tener que definir un tipo nuevo para ellas.
Crear una tupla
Sección titulada «Crear una tupla»fn main() { let person: (&str, i32, bool) = ("Alicia", 30, true); println!("{:?}", person);}Salida:
("Alicia", 30, true)Fíjate en que la tupla mezcla un &str, un i32 y un bool en un solo valor. Un array no puede hacer eso, porque todos sus elementos tienen que ser del mismo tipo.
Para imprimir una tupla entera, usa {:?} (formato de depuración) en lugar de {}. Un {} a secas no funciona con tuplas.
Acceder a los elementos por posición
Sección titulada «Acceder a los elementos por posición»Usa .0, .1, .2, etc. Las tuplas se numeran empezando desde cero:
fn main() { let person = ("Alicia", 30, true);
println!("Nombre: {}", person.0); println!("Edad: {}", person.1); println!("Activa: {}", person.2);}Salida:
Nombre: AliciaEdad: 30Activa: trueLa posición tiene que ser un número escrito tal cual en el código. No puedes usar una variable, como person.i, porque cada posición puede guardar un tipo distinto, y Rust tiene que conocer el tipo al compilar.
Desestructurar una tupla
Sección titulada «Desestructurar una tupla»Puedes sacar cada valor a su propia variable en una sola línea:
fn main() { let person = ("Alicia", 30, true); let (name, age, is_active) = person;
println!("{name} tiene {age} años, activa: {is_active}");}Salida:
Alicia tiene 30 años, activa: trueEsto suele ser más claro que escribir .0, .1, .2 una y otra vez, sobre todo cuando la tupla tiene más de dos o tres campos.
Devolver varios valores desde una función
Sección titulada «Devolver varios valores desde una función»Las tuplas son la forma natural de devolver más de un valor, ya que una función solo puede declarar un tipo de retorno:
fn min_max(numbers: &[i32]) -> (i32, i32) { let min = *numbers.iter().min().unwrap(); let max = *numbers.iter().max().unwrap(); (min, max)}
fn main() { let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]); println!("mínimo: {smallest}, máximo: {largest}");}Salida:
mínimo: 1, máximo: 9No te preocupes todavía por &[i32], .iter() o .unwrap(). Los verás en las páginas de Slices, Iteradores y Option. Lo que importa aquí es el tipo de retorno (i32, i32) y cómo main lo desestructura enseguida en dos variables.
Modificar tuplas e intercambiar valores
Sección titulada «Modificar tuplas e intercambiar valores»Si declaras una tupla con mut, puedes cambiar sus campos uno a uno, usando las mismas posiciones .0, .1. Los valores pueden cambiar, pero la forma no: una tupla siempre conserva el número y los tipos de campos con los que empezó.
La desestructuración también funciona al revés: puedes asignar a variables que ya existen. Así consigues un intercambio limpio en una sola línea, sin variable temporal. Si solo quieres algunos de los campos, escribe _ en los que no necesites:
fn main() { let mut point = (3, 4); point.0 += 10; // cambia un campo directamente println!("point es {:?}", point);
let mut a = 1; let mut b = 2; (a, b) = (b, a); // intercambia los dos valores println!("a = {a}, b = {b}");
let (x, _) = point; // nos quedamos con x e ignoramos el segundo campo println!("x = {x}");}Salida:
point es (13, 4)a = 2, b = 1x = 13El intercambio funciona porque Rust construye primero el lado derecho, (b, a). Solo después lo desempaqueta en a y b. Puedes hacer mucho más con _ y los patrones en la página de Coincidencia de patrones.
La tupla vacía: ()
Sección titulada «La tupla vacía: ()»En realidad ya conociste una tupla sin darte cuenta. () se llama tipo unitario (unit type), y es lo que devuelve una función cuando no devuelve nada útil. Es el tipo de retorno implícito de fn main().
Cuándo usar una tupla y cuándo un struct
Sección titulada «Cuándo usar una tupla y cuándo un struct»Las tuplas son ideales para agrupar cosas rápido y sin nombres, sobre todo para devolver dos o tres valores desde una función. Cuando un grupo de valores representa un concepto real con campos con nombre (como una Person con nombre y edad a los que te vas a referir por su nombre en todo tu código), un struct suele ser la opción mejor y más legible.
Práctica
Sección titulada «Práctica»1. Suma y producto
Sección titulada «1. Suma y producto»Escribe una función sum_and_product(a: i32, b: i32) -> (i32, i32) que devuelva a la vez la suma y el producto de dos números. En main, llámala con 5 y 6 y desestructura el resultado. Pista: la última línea de la función es la propia tupla, (a + b, a * b), sin punto y coma.
Ver solución
fn sum_and_product(a: i32, b: i32) -> (i32, i32) { (a + b, a * b)}
fn main() { let (sum, product) = sum_and_product(5, 6); println!("suma = {sum}, producto = {product}");}Salida:
suma = 11, producto = 302. Un rectángulo hecho de dos esquinas
Sección titulada «2. Un rectángulo hecho de dos esquinas»Un rectángulo se puede guardar como una tupla de dos puntos, donde cada punto es a su vez una tupla: let rect = ((1, 2), (5, 5));. Calcula su ancho, su alto y su área. Pista: las tuplas se pueden anidar, y rect.1.0 significa “el primer campo del segundo punto”.
Ver solución
fn main() { let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0; let height = rect.1.1 - rect.0.1;
println!("ancho {width}, alto {height}, área {}", width * height);}Salida:
ancho 4, alto 3, área 12También puedes desestructurar los dos puntos a la vez: let ((x1, y1), (x2, y2)) = rect;. Hay que escribir más, pero x2 - x1 se lee mejor que rect.1.0 - rect.0.0. Cuando los puntos empiezan a acumularse así, es señal de que un struct con campos con nombre sería más claro.
Resumen
Sección titulada «Resumen»- Una tupla agrupa valores que pueden ser de tipos distintos:
(name, age, is_active). - Accede a los campos con
.0,.1,.2, o sácalos todos a la vez desestructurando. - Las tuplas son la forma natural de devolver varios valores desde una función.
- Los campos de una tupla
mutpueden cambiar, pero su tamaño y sus tipos no.(a, b) = (b, a);intercambia dos valores. - Para agrupaciones con nombre y reutilizables, usa mejor un struct.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué puede guardar una tupla que un array no puede?
2. ¿Cómo accedes al segundo elemento de una tupla llamada person?
3. Después de let mut a = 1; let mut b = 2; (a, b) = (b, a);, ¿cuánto valen a y b?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- El tipo tupla — El libro de Rust (en inglés): la introducción oficial a cómo crear, desestructurar e indexar tuplas.
- Tuplas — Rust by Example (en inglés): ejemplos ejecutables con tuplas anidadas y tuplas como argumentos de funciones.
- El tipo
tuple— documentación de la biblioteca estándar (en inglés): qué traits admiten las tuplas, como comparar, copiar e imprimir con{:?}. - El tipo
unit— documentación de la biblioteca estándar (en inglés): qué es()y dónde aparece. - Asignaciones con desestructuración — The Rust Reference (en inglés): las reglas exactas detrás de
(a, b) = (b, a);.