Compuestos
- Compound Data Types
- Pueden agrupar varios valores en una sola estructura
- Rust tiene dos tipos de compuestos primitivos: tuplas (tuples) y matrices (arrays)
- Los Strings, slices y slice string también son compuestos pero no primitivos
Tuplas
Las tuplas son tipos de datos compuestos que permiten agrupar múltiples valores de diferentes tipos en una sola estructura ordenada.
- Heterogéneas: Pueden contener elementos de diferentes tipos
- Tamaño fijo: Definido en tiempo de compilación y no puede cambiar
- Ordenadas: Los elementos tienen posiciones específicas (0, 1, 2, 3...)
- Inmutables por defecto: Como todas las variables en Rust
- Sintaxis:
(T1, T2, T3, ...)dondeTpuede ser un tipo diferente
Declaración e inicialización
fn main(){
// 1. Declaración explícita con tipos
let person: (String, u8, bool, f64) = (
"Jhon Doe".to_string(),
25,
true,
5.6
);
// 2. Inferencia de tipos
let product = (101, "Laptop", 1299.99, true, "Electronics");
// 3. Tupla mixta con diferentes tipos complejos
let complex_data = (
vec![1, 2, 3], // Vector
"metadata".to_string(), // String
[10, 20, 30], // Array
42 // Integer
);
// 4. Tupla mutable
let mut mutable_tuple = (100, "changeable", false);
mutable_tuple.0 = 200; // Cambia el primer elemento
mutable_tuple.2 = true; // Cambia el tercer elemento
println!("Person: {:#?}", person);
println!("Product: {:#?}", product);
println!("Modified tuple: {:?}", mutable_tuple);
}
Tupla Unit ()
- La tupla vacía
()se conoce comounit - Representan un valor vacío o un tipo de retorno vacío, similar a
voiden otros lenguajes
// Tupla unitaria (un solo elemento) - requiere coma final
let single_element = (42,); // Sin coma sería solo paréntesis de agrupación
let not_a_tuple = (42); // Esto es solo un i32, no una tupla
// Tupla vacía - tipo unit
let empty = (); // Tipo: ()
println!("Single element tuple: {:?}", single_element);
println!("Unit type: {:?}", empty);
// Funciones que no retornan valor explícitamente retornan ()
fn greet_user(name: &str) {
println!("¡Hola, {}!", name);
// Retorna implícitamente ()
}
fn explicit_unit_return() -> () {
println!("Esta función retorna explícitamente unit");
() // Retorno explícito de unit
}
// Equivalente a la función anterior
fn implicit_unit_return() {
println!("Esta función retorna implícitamente unit");
// Sin return explícito, retorna ()
}
fn main() {
let result = greet_user("Carlos");
println!("Result type: {:?}", result); // ()
// Unit también se usa en macros y expresiones que no producen valores
let unit_from_print = println!("Esto retorna unit");
println!("Unit from print: {:?}", unit_from_print); // ()
}
Acceso a elementos
- Indexación con punto
- Inicia con índice 0
- Los índices deben ser literales conocidos en tiempo de compilación
let student_record = ("María López", 20, "Ingeniería", 9.2, true);
println!("Nombre: {}", student_record.0); // "María López"
println!("Edad: {}", student_record.1); // 20
println!("Carrera: {}", student_record.2); // "Ingeniería"
println!("Promedio: {}", student_record.3); // 9.2
println!("Activo: {}", student_record.4); // true
Destructuring (desestructuración)
Pattern matching básico
fn main() {
let weather_data = ("Madrid", 22.5, 65, true);
// Destructuring completo
let (city, temperature, humidity, is_sunny) = weather_data;
println!("City: {}", city);
println!("Temperature: {}°C", temperature);
println!("Humidity: {}", humidity);
println!("Is sunny: {}", if is_sunny {"Yes" } else { "No" });
}
Destructuring parcial
fn main() {
let transaction = (1234, "Compra", 150.75, "05-12-2024", true);
// Ignorar elementos que no necesitamos con _ (guión bajo)
let (id, transaction_type, amount, _, _) = transaction;
// o usar .. (dos puntos) para ignorar el resto
let (id_alt, type_alt, ..) = transaction;
// Extraer elementos específicos
let (_, _, amount_only, ..) = transaction;
println!("ID: {}, Type: {}, Amount: ${}", id, transaction_type, amount);
println!("Amount only: {}", amount_only);
}
Destructuring anidado
fn nested_destructuring() {
let nested_data = (
(10, 20), // Tupla anidada
"outer_value", // String
((1, 2), (3, 4)) // Tuplas doblemente anidadas
);
// Destructuring anidado
let ((x, y), text, ((a, b), (c, d))) = nested_data;
println!("x: {}, y: {}", x, y);
println!("Text: {}", text);
println!("a: {}, b: {}, c: {}, d: {}", a, b, c, d);
}
Funciones con tuplas
Retorno de múltiples valores
fn calculate_stats(numbers: &[i32]) -> (i32, i32, f64) {
let sum: i32 = numbers.iter().sum();
let max = *numbers.iter().max().unwrap_or(&0);
let min = *numbers.iter().min().unwrap_or(&0);
let average = sum as f64 / numbers.len() as f64;
(max, min, average) // Retorna tupla con multiples valores
}
fn divide_with_remainder(dividend: i32, divisor: i32) -> (i32, i32) {
(dividend / divisor, dividend % divisor)
}
fn main(){
let data: [i32; 6] = [42, 53, 50, 99, 35, 75];
let (max, min, average) = calculate_stats(&data);
println!("Max: {}, Min: {}, Average: {}", max, min, average);
let (quotient, remainder) = divide_with_remainder(10, 5);
println!("Quotient: {}, Remainder: {}", quotient, remainder);
}
Funciones que reciben tuplas
fn display_person_info(person: (String, i32, String)) {
let (name, age, profession) = person;
println!("Name: {}, age: {}, profession: {}", name, age, profession);
}
fn calculate_distance(point1: (f64, f64), point2: (f64, f64)) -> f64 {
let (x1, y1) = point1;
let (x2, y2) = point2;
((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}
fn main() {
let person = ("Dr. Jhon".to_string(), 32, "Doctor".to_string());
display_person_info(person);
let point_a = (0.0, 0.0);
let point_b = (3.0, 4.0);
let distance = calculate_distance(point_a, point_b);
println!("Distancia entre puntos: {:.2}", distance);
}
Arrays
Los arrays son estructuras de datos que almacenan múltiples valores del mismo tipo en una secuencia contigua de memoria.
- Tamaño fijo: Definido en tiempo de compilación y no puede cambiar
- Almacenamiento: Los datos se guardan en el stack (que es más rápido que el heap)
- Homogéneos: Todos los elementos deben ser del mismo tipo
- Indexación: Acceso mediante índices que comienzan desde 0.
- Pánico: Si se intenta acceder a un índice no válido, causará pánico en tiempo de ejecución
- Sintaxis:
[T; N]dondeTes el tipo yNes la longitud
Declaración e inicialización
fn main() {
// 0. No se pueden crear arrays con rangos, [1..=100]
// 1. Declaración explícita con tipo y tamaño
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
// 2. Slice: referencia a una parte del array
// El primer limite se incluye, el segundo límite se excluye
let slice = &numbers[1..4]; // posiciones 1, 2 y 3 => [2, 3, 4]
// Para incluir el segundo indice se puede usar "..="
let slice = &numbers[1..=3];
// 3. Inferencia de tipos (Rust deduce [&str; 7])
let days_of_week = ["Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"];
// 4. Inicialización con valor repetido: [valor; cantidad]
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let greetings = ["Hello"; 3]; // ["Hello", "Hello", "Hello"]
// 5. Array mutable (permite modificar elementos)
let mut mutable_array = [1, 2, 3, 4, 5];
mutable_array[0] = 10; // Cambia el primer elemento
println!("Numbers: {:?}", numbers);
println!("Slice of numbers: {:?}", slice);
println!("Days: {:?}", days_of_week);
println!("Zeros: {:?}", zeros);
println!("Modified array: {:?}", mutable_array);
}
Acceso a elementos
- Al acceder a un elemento, Rust verifica en tiempo de ejecución que el índice sea menor que la longitud del array
- Esta comprobación ocurre en tiempo de ejecución porque el compilador no puede predecir qué valor ingresará el usuario
- Si el índice es válido, se devuelve el valor correspondiente
- Si se intenta acceder a una posición fuera de los límites del array, el programa se detendrá y mostrará un mensaje de error
- Para evitar que se detenga se pueden hacer comprobaciones
ifo con el método.get()
Indexación directa
let fruits = ["apple", "banana", "cherry", "date"];
println!("First fruit: {}", fruits[0]); // "apple"
println!("Last fruit: {}", fruits[3]); // "date"
println!("Array length: {}", fruits.len()); // 4
Método .get() (acceso seguro)
let numbers = [10, 20, 30, 40, 50];
// Retorna Option<&T> - más seguro que indexación directa
match numbers.get(2) {
Some(value) => println!("Value at index 2: {}", value),
None => println!("Index out of bounds"),
}
// Usando if let para simplificar
if let Some(value) = numbers.get(100) {
println!("Value: {}", value);
} else {
println!("Index 100 is out of bounds"); // Este mensaje se imprimirá
}
Se podría hacer la verificación manual, validando primero la longitud del array y después accediendo al índice
let colors = ["red", "green", "blue"];
let index = 5; // Índice que sabemos es inválido
if index < colors.len() {
println!("Color: {}", colors[index]);
} else {
println!("Index {} is out of bounds for array of length {}", index, colors.len());
}
Slices (fragmentos)
Los slices permiten referenciar una porción contigua de un array sin tomar posesión de los datos.
let array = [1, 2, 3, 4, 5, 6, 7, 8];
// Diferentes formas de crear slices
let slice_1 = &array[1..4]; // [2, 3, 4] - del índice 1 al 3 (4 excluido)
let slice_2 = &array[1..=4]; // [2, 3, 4, 5] - del índice 1 al 4 (4 incluido)
let slice_3 = &array[..3]; // [1, 2, 3] - desde el inicio hasta índice 2
let slice_4 = &array[3..]; // [4, 5, 6, 7, 8] - desde índice 3 hasta el final
let slice_5 = &array[..]; // [1, 2, 3, 4, 5, 6, 7, 8] - todo el array
println!("Original: {:?}", array);
println!("Slice 1..4: {:?}", slice_1);
println!("Slice 1..=4: {:?}", slice_2);
Iteración sobre arrays
let numbers = [10, 20, 30, 40, 50];
// 1. Iteración por valor (consume el array si no es Copy)
for num in numbers {
println!("Value: {}", num);
}
// 2. Iteración por referencia (no consume el array)
for num in &numbers {
println!("Value: {}", num);
}
// 3. Iteración con índices
for (index, value) in numbers.iter().enumerate() {
println!("Index {}: Value {}", index, value);
}
// 4. Usando while con índice manual
let mut i = 0;
while i < numbers.len() {
println!("numbers[{}] = {}", i, numbers[i]);
i += 1;
}
Métodos comunes
let mut scores = [85, 92, 78, 96, 88];
println!("Length: {}", scores.len()); // 5
println!("Is empty: {}", scores.is_empty()); // false
println!("First element: {:?}", scores.first()); // Some(85)
println!("Last element: {:?}", scores.last()); // Some(88)
println!("Contains 92: {}", scores.contains(&92)); // true
// Sorting (requiere mutabilidad)
scores.sort();
println!("Sorted: {:?}", scores); // [78, 85, 88, 92, 96]
scores.reverse();
println!("Reversed: {:?}", scores); // [96, 92, 88, 85, 78]
Ejemplo 1: Procesamiento de Calificaciones
fn analyze_grades() {
let grades = [85, 92, 78, 96, 88, 76, 94, 82];
let sum: i32 = grades.iter().sum();
let average = sum as f64 / grades.len() as f64;
let max_grade = *grades.iter().max().unwrap();
let min_grade = *grades.iter().min().unwrap();
println!("Grades: {:?}", grades);
println!("Average: {:.2}", average);
println!("Highest: {}", max_grade);
println!("Lowest: {}", min_grade);
}
Ejemplo 2: Búsqueda en Array
fn find_element(arr: &[i32], target: i32) -> Option<usize> {
for (index, &value) in arr.iter().enumerate() {
if value == target {
return Some(index);
}
}
None
}
fn main() {
let numbers = [10, 25, 30, 45, 60];
match find_element(&numbers, 30) {
Some(index) => println!("Found 30 at index {}", index),
None => println!("30 not found in array"),
}
}
Array vs Vectores
| Característica | Array [T; N] | Vector Vec<T> |
|---|---|---|
| Tamaño | Fijo en compilación | Dinámico en ejecución |
| Memoria | Stack | Heap |
| Performance | Más rápido (sin indirección) | Ligeramente más lento |
| Flexibilidad | Limitada | Alta (push, pop, resize) |
| Uso recomendado | Datos con tamaño conocido | Colecciones que crecen/decrecen |
// Usar arrays cuando:
let rgb_colors = [255, 128, 64]; // Siempre 3 componentes
let days_in_week = 7;
let fixed_buffer = [0u8; 1024]; // Buffer de tamaño fijo
// Usar Vec<T> cuando:
let mut dynamic_list = Vec::new();
dynamic_list.push("elemento");
dynamic_list.push("otro elemento");
// La lista puede crecer según sea necesario
Conversión Array-Vector
// Array a Vector
let array = [1, 2, 3, 4, 5];
let vector = array.to_vec();
// Vector a Array (requiere tamaño conocido en compilación)
let vector = vec![1, 2, 3];
let array: [i32; 3] = vector.try_into().unwrap();