En esta página
Tipos de datos compuestos y funciones
Tipos de datos compuestos
Rust tiene dos tipos de datos compuestos integrados en el lenguaje: tuplas y arrays. Ambos tienen tamaño fijo conocido en tiempo de compilación y viven en la pila (stack).
Tuplas
Una tupla agrupa valores de tipos distintos. Su tamaño y tipos son parte de la firma de tipo de la tupla:
fn main() {
// Tupla con tres tipos distintos
let persona: (String, u8, f64) = (
String::from("Ana García"),
30,
1.75,
);
// Acceso por índice con punto
println!("Nombre: {}", persona.0);
println!("Edad: {}", persona.1);
println!("Altura: {:.2}m", persona.2);
// Desestructuración (destructuring)
let (nombre, edad, altura) = persona;
println!("{nombre} tiene {edad} años y mide {altura:.2}m");
// Tupla vacía: () se llama "unit" — el tipo de retorno implícito de funciones sin retorno
let nada: () = ();
// Tupla con un solo elemento (nota la coma)
let singleton = (42,);
println!("Singleton: {}", singleton.0);
}Las tuplas son especialmente útiles para retornar múltiples valores desde una función, sin necesidad de crear un struct.
Arrays
Los arrays en Rust tienen longitud fija conocida en tiempo de compilación. Todos los elementos deben ser del mismo tipo:
fn main() {
// Declaración explícita: [tipo; longitud]
let dias: [&str; 7] = [
"Lunes", "Martes", "Miércoles", "Jueves",
"Viernes", "Sábado", "Domingo"
];
// Inicializar todos los elementos con el mismo valor
let zeros: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]
let unos = [1_u8; 10]; // [1, 1, 1, ..., 1] x10
// Acceso por índice — Rust verifica límites en tiempo de ejecución
println!("Primer día: {}", dias[0]);
println!("Último día: {}", dias[6]);
// Longitud
println!("Días en la semana: {}", dias.len());
// Iteración
for dia in &dias {
println!("{dia}");
}
// Slices: referencias a una porción del array
let fin_de_semana: &[&str] = &dias[5..7];
println!("Fin de semana: {:?}", fin_de_semana);
}La diferencia entre arrays y vectores (Vec<T>) es que los arrays tienen tamaño fijo. Los vectores pueden crecer y se almacenan en el heap. Para la mayoría de colecciones dinámicas usarás Vec<T>.
Funciones en Rust
Las funciones se declaran con la palabra clave fn. A diferencia de algunos lenguajes, todos los parámetros requieren anotación de tipo explícita — Rust no infiere tipos de parámetros para mantener las firmas de función siempre claras y auto-documentadas.
// Función sin parámetros ni retorno (retorna implícitamente ())
fn saludar() {
println!("¡Hola desde una función!");
}
// Función con parámetros y tipo de retorno
fn elevar_al_cuadrado(n: i32) -> i32 {
n * n // Sin punto y coma: expresión de retorno
}
// Múltiples parámetros
fn formatear_temperatura(celsius: f64, unidad: &str) -> String {
match unidad {
"F" => format!("{:.1}°F", celsius * 1.8 + 32.0),
"K" => format!("{:.1}K", celsius + 273.15),
_ => format!("{:.1}°C", celsius),
}
}
fn main() {
saludar();
let n = 5;
println!("{n}² = {}", elevar_al_cuadrado(n));
println!("{}", formatear_temperatura(100.0, "F"));
println!("{}", formatear_temperatura(0.0, "K"));
}Expresiones vs declaraciones
Esta distinción es fundamental en Rust y diferente a muchos otros lenguajes:
- Una declaración realiza una acción pero no produce un valor. Termina con
; - Una expresión evalúa a un valor. No termina con
;
fn main() {
// Declaración: let es una declaración
let x = 5;
// Expresión: el bloque { } es una expresión
let y = {
let temp = x * 2;
temp + 1 // Sin ;: esto es el valor del bloque
};
println!("y = {y}"); // y = 11
// if también es una expresión en Rust
let es_par = if x % 2 == 0 { "par" } else { "impar" };
println!("x es {es_par}");
// Comparar con:
let z = {
let temp = x * 2;
temp + 1; // Con ;: el bloque produce ()
};
// z es de tipo ()
}Esta característica hace que Rust sea muy expresivo. Puedes usar if, bloques, match y casi cualquier construcción del lenguaje como parte de una expresión.
Retorno anticipado con `return`
Usa return cuando necesitas salir de una función antes de llegar al final:
fn dividir(a: f64, b: f64) -> f64 {
if b == 0.0 {
return f64::NAN; // Retorno anticipado
}
// Retorno normal al final
a / b
}
fn clasificar_numero(n: i32) -> &'static str {
if n < 0 {
return "negativo";
}
if n == 0 {
return "cero";
}
"positivo" // Retorno implícito al final
}
fn main() {
println!("{}", dividir(10.0, 3.0));
println!("{}", dividir(10.0, 0.0));
for n in [-5, 0, 7] {
println!("{n} es {}", clasificar_numero(n));
}
}Funciones con múltiples valores de retorno
Rust no tiene retornos múltiples nativos, pero las tuplas los emulan perfectamente:
fn estadisticas(datos: &[f64]) -> (f64, f64, f64) {
let n = datos.len() as f64;
let suma: f64 = datos.iter().sum();
let promedio = suma / n;
let min = datos.iter().cloned().fold(f64::INFINITY, f64::min);
let max = datos.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
(promedio, min, max)
}
fn main() {
let mediciones = [23.5, 18.2, 31.7, 25.0, 19.8, 27.3];
let (promedio, min, max) = estadisticas(&mediciones);
println!("Promedio: {promedio:.2}");
println!("Mínimo: {min:.2}");
println!("Máximo: {max:.2}");
println!("Rango: {:.2}", max - min);
}Funciones anidadas y closures
Las funciones pueden anidarse dentro de otras funciones. Son útiles para encapsular lógica auxiliar:
fn procesar_datos(entrada: &[i32]) -> Vec<i32> {
// Función auxiliar anidada
fn es_valido(n: &i32) -> bool {
*n > 0 && *n < 1000
}
fn transformar(n: i32) -> i32 {
n * 2 + 1
}
entrada
.iter()
.filter(|n| es_valido(n))
.map(|&n| transformar(n))
.collect()
}
fn main() {
let datos = [-5, 10, 500, 1500, 42, 0, 99];
let resultado = procesar_datos(&datos);
println!("{:?}", resultado);
}El tipo `!` (never)
Rust tiene un tipo especial ! llamado "never" que representa funciones que nunca retornan:
fn entrar_en_panico() -> ! {
panic!("¡Algo salió muy mal!");
}
fn bucle_infinito() -> ! {
loop {
// Esto nunca termina
}
}
fn main() {
// El tipo ! es compatible con cualquier tipo esperado
let x: i32 = if true { 42 } else { panic!("imposible") };
println!("{x}");
}Las macros panic!, todo!, unimplemented! y unreachable! todas tienen tipo !.
Con estos fundamentos sobre tipos compuestos y funciones, estás listo para el concepto más importante de Rust: ownership, el sistema que hace posible la seguridad de memoria sin recolector de basura.
// Las funciones se declaran con fn
// Los parámetros SIEMPRE requieren anotación de tipo
fn sumar(a: i32, b: i32) -> i32 {
a + b // Sin punto y coma: expresión de retorno
}
fn calcular_area(base: f64, altura: f64) -> f64 {
let area = base * altura / 2.0;
area // retorno implícito
}
// Puede retornar múltiples valores con tupla
fn min_max(lista: &[i32]) -> (i32, i32) {
let min = *lista.iter().min().unwrap();
let max = *lista.iter().max().unwrap();
(min, max)
}
fn main() {
let resultado = sumar(3, 7);
println!("3 + 7 = {resultado}");
let area = calcular_area(5.0, 4.0);
println!("Área = {area:.2}");
let nums = [3, 1, 4, 1, 5, 9, 2, 6];
let (min, max) = min_max(&nums);
println!("Min = {min}, Max = {max}");
}
Inicia sesión para guardar tu progreso