Arrays y strings

Intermedio
Rust
Rust
Actualizado: 16/05/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

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.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

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

⭐⭐⭐⭐⭐
4.9/5 valoración