Arrays y slices
En Rust, los arrays son colecciones de elementos del mismo tipo con un tamaño fijo conocido en tiempo de compilación. A diferencia de otros lenguajes, los arrays en Rust son una estructura de datos fundamental que ofrece un rendimiento predecible y seguridad en el acceso a memoria.
Arrays de tamaño fijo
Un array en Rust se declara especificando tanto el tipo de los elementos como su cantidad. La sintaxis básica es [T; N]
, donde T
es el tipo de los elementos y N
es el número de elementos.
// Array de 5 enteros
let numeros: [i32; 5] = [1, 2, 3, 4, 5];
// Array de 3 booleanos
let banderas: [bool; 3] = [true, false, true];
// Array de caracteres
let vocales: [char; 5] = ['a', 'e', 'i', 'o', 'u'];
Rust también ofrece una forma concisa de crear arrays con el mismo valor repetido:
// Array de 10 ceros
let ceros = [0; 10]; // Equivalente a [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// Array de 5 cadenas vacías
let cadenas_vacias = [""; 5];
Acceso a elementos
Para acceder a los elementos de un array, utilizamos la notación de índice con corchetes. Los índices en Rust comienzan en 0:
let colores = ["rojo", "verde", "azul"];
let primer_color = colores[0]; // "rojo"
let segundo_color = colores[1]; // "verde"
Una característica importante de Rust es que realiza comprobaciones de límites en tiempo de ejecución:
let numeros = [1, 2, 3];
// let valor = numeros[5]; // ¡Error en tiempo de ejecución!
// thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 5'
Iteración sobre arrays
Podemos recorrer los elementos de un array de varias formas:
let notas = [7, 8, 9, 6, 10];
// Usando for
for nota in notas {
println!("Nota: {}", nota);
}
// Usando for con índices
for i in 0..notas.len() {
println!("Nota {}: {}", i + 1, notas[i]);
}
// Usando iter() para obtener referencias inmutables
for nota in notas.iter() {
println!("Referencia a nota: {}", nota);
}
Métodos útiles para arrays
Los arrays tienen varios métodos útiles disponibles:
let valores = [10, 20, 30, 40, 50];
// Obtener la longitud
let longitud = valores.len(); // 5
// Comprobar si está vacío
let esta_vacio = valores.is_empty(); // false
// Obtener una referencia al primer elemento
if let Some(primero) = valores.first() {
println!("Primer valor: {}", primero);
}
// Obtener una referencia al último elemento
if let Some(ultimo) = valores.last() {
println!("Último valor: {}", ultimo);
}
// Comprobar si contiene un valor
let contiene_30 = valores.contains(&30); // true
Mutabilidad en arrays
Por defecto, los arrays son inmutables en Rust. Para crear un array mutable:
let mut puntuaciones = [0, 0, 0, 0];
puntuaciones[0] = 10;
puntuaciones[1] = 20;
println!("Puntuaciones: {:?}", puntuaciones); // [10, 20, 0, 0]
Slices: vistas de arrays
Los slices son una vista o "referencia" a una sección de un array. No poseen los datos, sino que "apuntan" a ellos. La sintaxis de un slice es &[T]
, donde T
es el tipo de los elementos.
let numeros = [1, 2, 3, 4, 5];
// Slice que referencia a todo el array
let todos = &numeros[..];
// Slice que referencia a los primeros tres elementos
let primeros_tres = &numeros[0..3]; // [1, 2, 3]
// Slice que referencia a los últimos dos elementos
let ultimos_dos = &numeros[3..5]; // [4, 5]
// Formas abreviadas
let desde_inicio = &numeros[..3]; // Equivalente a [0..3]
let hasta_final = &numeros[2..]; // Equivalente a [2..5]
Los slices son especialmente útiles para pasar secciones de arrays a funciones sin necesidad de copiar los datos:
fn suma_slice(numeros: &[i32]) -> i32 {
let mut total = 0;
for &n in numeros {
total += n;
}
total
}
let valores = [10, 20, 30, 40, 50];
let total = suma_slice(&valores[1..4]); // Suma 20+30+40=90
println!("La suma es: {}", total);
Slices de dos dimensiones
También podemos crear slices de arrays multidimensionales:
let matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// Slice de la primera fila
let primera_fila = &matriz[0]; // &[1, 2, 3]
// Slice de las dos primeras filas
let dos_filas = &matriz[0..2]; // &[[1, 2, 3], [4, 5, 6]]
Seguridad y rendimiento
Los arrays y slices en Rust ofrecen varias ventajas:
- Rendimiento predecible: Al tener tamaño fijo, los arrays se almacenan completamente en la pila (stack), lo que proporciona acceso rápido.
- Seguridad de memoria: Rust verifica los límites de los arrays en tiempo de ejecución, evitando accesos fuera de rango.
- Eficiencia en slices: Los slices permiten trabajar con secciones de datos sin copiarlos, solo referenciándolos.
fn procesar_datos(datos: &[f64]) {
// Trabajar con una referencia a los datos sin copiarlos
let promedio = datos.iter().sum::<f64>() / datos.len() as f64;
println!("Promedio: {:.2}", promedio);
}
let mediciones = [23.5, 24.0, 22.8, 23.2, 24.1];
procesar_datos(&mediciones); // Pasamos una referencia a todo el array
procesar_datos(&mediciones[2..]); // Pasamos solo las últimas mediciones
Los arrays y slices forman la base para estructuras de datos más complejas en Rust, como los vectores (Vec<T>
), que veremos en lecciones posteriores sobre colecciones estándar.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
&str vs String
En Rust, trabajar con texto implica entender dos tipos fundamentales: &str
y String
. Estos tipos representan cadenas de texto pero con características y propósitos diferentes que resultan de la filosofía de Rust sobre seguridad y rendimiento.
Cadenas literales (&str)
El tipo &str
(pronunciado "string slice" o "rebanada de cadena") es la forma más básica de representar texto en Rust. Se trata de una referencia inmutable a una secuencia de caracteres UTF-8 almacenada en algún lugar de la memoria.
// Cadena literal asignada a una variable
let saludo: &str = "Hola, mundo";
// El tipo &str se infiere automáticamente para literales de cadena
let nombre = "Rust";
Las características principales de &str
son:
- Inmutabilidad: No puedes modificar su contenido
- Tamaño fijo: Conocido en tiempo de compilación
- Eficiencia: No requiere asignación de memoria en el heap
- Referencia: No posee los datos, solo los "observa"
Las cadenas literales como "Hola"
están almacenadas en el binario del programa y tienen una duración igual a la del programa completo.
Cadenas dinámicas (String)
El tipo String
es una estructura de datos que posee una secuencia de caracteres UTF-8 y la almacena en el heap, permitiendo que su contenido sea modificable y su tamaño pueda cambiar durante la ejecución.
// Crear una String vacía
let mut mensaje = String::new();
// Crear una String a partir de un literal
let mut nombre = String::from("Ana");
// Otra forma de crear una String
let apellido = "García".to_string();
Las características principales de String
son:
- Mutabilidad: Puedes modificar su contenido
- Tamaño dinámico: Puede crecer o reducirse en tiempo de ejecución
- Propiedad: Posee sus datos y los gestiona
- Asignación en heap: Requiere memoria del montículo (heap)
Operaciones básicas con strings
Concatenación
Con String
podemos añadir contenido de varias formas:
let mut saludo = String::from("Hola");
// Añadir un &str con push_str()
saludo.push_str(", ");
// Añadir un solo carácter con push()
saludo.push('R');
saludo.push('u');
saludo.push('s');
saludo.push('t');
println!("{}", saludo); // "Hola, Rust"
// Concatenar con el operador +
let nombre = String::from("Programación ");
let lenguaje = "Rust";
// Nota: + toma propiedad del primer argumento
let curso = nombre + "en " + lenguaje;
println!("{}", curso); // "Programación en Rust"
Para concatenaciones más complejas, el macro format!
es más eficiente y legible:
let nombre = "Ana";
let edad = 28;
let presentacion = format!("Me llamo {} y tengo {} años", nombre, edad);
println!("{}", presentacion); // "Me llamo Ana y tengo 28 años"
Búsqueda y comprobación
Podemos buscar texto dentro de cadenas:
let frase = "Rust es un lenguaje seguro y eficiente";
// Comprobar si contiene una subcadena
let contiene_seguro = frase.contains("seguro"); // true
// Comprobar si comienza con una subcadena
let comienza_con_rust = frase.starts_with("Rust"); // true
// Comprobar si termina con una subcadena
let termina_con_rapido = frase.ends_with("rápido"); // false
Reemplazo y transformación
let codigo = "let x = 10;";
// Reemplazar texto
let codigo_nuevo = codigo.replace("10", "42");
println!("{}", codigo_nuevo); // "let x = 42;"
// Convertir a mayúsculas/minúsculas
let texto = "Rust";
println!("{}", texto.to_uppercase()); // "RUST"
println!("{}", texto.to_lowercase()); // "rust"
Conversión entre &str y String
Es común necesitar convertir entre estos dos tipos:
// De &str a String
let literal = "Hola";
let string_objeto = literal.to_string(); // o String::from(literal)
// De String a &str (préstamo)
let objeto = String::from("Mundo");
let slice: &str = &objeto;
// También funciona automáticamente cuando se pasa como argumento
Indexación y rebanado
A diferencia de otros lenguajes, Rust no permite indexar strings directamente con [i]
debido a que los caracteres UTF-8 pueden ocupar múltiples bytes:
let texto = "Rust 🦀";
// let primer_caracter = texto[0]; // ¡ERROR! No se puede indexar por byte
// En su lugar, podemos iterar sobre caracteres
for c in texto.chars() {
println!("{}", c);
}
// O convertir a un vector de caracteres
let caracteres: Vec<char> = texto.chars().collect();
let primer_caracter = caracteres[0]; // 'R'
Para obtener rebanadas (slices) de strings, debemos asegurarnos de cortar en límites válidos de caracteres UTF-8:
let mensaje = "Hola Rust";
let hola = &mensaje[0..4]; // "Hola"
let rust = &mensaje[5..]; // "Rust"
// Cuidado: esto causaría pánico si cortamos a mitad de un carácter multibyte
// let emoji_incorrecto = "🦀"[0..1]; // ¡ERROR en tiempo de ejecución!
Cuándo usar cada tipo
-
Usa &str cuando:
-
Necesites una referencia inmutable a texto
-
Quieras pasar texto como parámetro sin transferir propiedad
-
Trabajes con texto de solo lectura
-
Usa String cuando:
-
Necesites modificar el contenido del texto
-
Debas construir texto dinámicamente
-
Requieras ser propietario del texto
// Función que acepta una referencia a str (más flexible)
fn saludar(nombre: &str) {
println!("¡Hola, {}!", nombre);
}
// Podemos llamarla con &str o con String
let nombre_literal = "Carlos";
let nombre_string = String::from("Ana");
saludar(nombre_literal);
saludar(&nombre_string); // Coerción automática de &String a &str
Recuerda que String
es propietario de su contenido mientras que &str
generalmente es una referencia. Esta distinción es fundamental para entender cómo Rust gestiona la memoria y garantiza la seguridad sin necesidad de un recolector de basura.
Aprendizajes de esta lección
- Comprender la declaración y uso de arrays de tamaño fijo en Rust.
- Aprender a trabajar con slices como vistas de arrays para manipular secciones sin copiar datos.
- Diferenciar entre los tipos de cadenas &str y String, y cuándo usar cada uno.
- Realizar operaciones básicas con strings, incluyendo concatenación, búsqueda y transformación.
- Entender la seguridad y rendimiento que Rust ofrece en el manejo de arrays, slices y cadenas de texto.
Completa Rust y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs