Saltar al contenido principal

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, ...) donde T puede 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 como unit
  • Representan un valor vacío o un tipo de retorno vacío, similar a void en 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] donde T es el tipo y N es 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 if o 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ísticaArray [T; N]Vector Vec<T>
TamañoFijo en compilaciónDinámico en ejecución
MemoriaStackHeap
PerformanceMás rápido (sin indirección)Ligeramente más lento
FlexibilidadLimitadaAlta (push, pop, resize)
Uso recomendadoDatos con tamaño conocidoColecciones 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();