Rust

Rust

Tutorial Rust: Arrays y strings

Aprende en Rust a manejar arrays, slices y cadenas de texto (&str y String) con ejemplos claros y seguridad en memoria.

Aprende Rust y certifícate

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.

&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.

Aprende Rust online

Otras lecciones de Rust

Accede a todas las lecciones de Rust y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Rust y certifícate

Ejercicios de programación de Rust

Evalúa tus conocimientos de esta lección Arrays y strings con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

En esta lección

Objetivos de aprendizaje 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.