En Rust, un String o un &str es una secuencia de bytes codificada en UTF-8. A diferencia de otros lenguajes donde un string puede ser un array de caracteres de longitud fija, en Rust la longitud de un carácter puede variar entre 1 y 4 bytes. Esto significa que la indexación directa por posición (como s[i]) no existe porque sería una operación $O(n)$ y, lo que es más importante, podría romper la integridad de un carácter si se corta a mitad de su secuencia de bytes.
Para iterar o manipular texto de forma correcta, existen tres enfoques principales:
1. bytes(): Itera sobre los valores u8 crudos. Es extremadamente rápido y es ideal cuando trabajas con protocolos de red que usan ASCII o cuando necesitas manipulación de bajo nivel.
2. chars(): Itera sobre valores char (Unicode Scalar Values). Esta es la forma estándar de procesar texto cuando necesitas trabajar con caracteres lógicos (como letras o emojis), aunque técnicamente un “carácter visual” (grapheme cluster) podría estar compuesto por múltiples char.
3. char_indices(): Retorna una tupla que contiene el índice del byte donde comienza el carácter y el carácter mismo. Es fundamental cuando necesitas conocer la posición exacta en el buffer de bytes para realizar slicing posterior de forma segura.
Para evitar errores fatales en tiempo de ejecución (panics), nunca uses el slicing directo &s[start..end] si no estás absolutamente seguro de que start y end caen sobre los límites de un carácter. En su lugar, utiliza el método .get(range), que devuelve un Option<&str>, permitiénde manejar de forma segura los casos donde el rango es inválido.
fn main() {
// Definimos un string con ASCII, un emoji (4 bytes) y un número
let text = "Rust 🦀 100";
// 1. Iteración sobre bytes (u8)
println!("--- Bytes ---");
for b in text.bytes() {
print!("{} ", b);
}
println!();
// 2. Iteración sobre caracteres Unicode (char)
println!("\n--- Caracteres ---");
for c in text.chars() {
print!("{} ", c);
}
println!();
// 3. Iteración con índices de bytes (crucial para slicing)
println!("\n--- Indices de Bytes ---");
for (idx, c) in text.char_indices() {
println!("Índice byte {}: '{}'", idx, c);
}
// 4. Slicing seguro vs inseguro
// Intentar cortar con &text[0..6] causaría un panic porque 5 es el inicio de un emoji
// pero el emoji ocupa 4 bytes (5, 6, 7, 8).
println!("\n--- Slicing Seguro ---");
if let Some(emoji_part) = text.get(5..9) {
println!("Emoji extraído: {}", emoji_part);
} else {
println!("Rango inválido para un slice seguro");
}
// 5. División de strings (split)
println!("\n--- Split (espacios) ---");
for word in text.split_whitespace() {
println!("Palabra: '{}'", word);
}
// 6. Conversión (parse)
let number_part = "100";
match number_part.parse::<i32>() {
Ok(n) => println!("\nNúmero parseado: {}", n),
Err(_) => println!("Error al parsear"),
}
// 7. Contar caracteres reales (no bytes)
let char_count = text.chars().count();
println!("\nTotal de caracteres Unicode: {}", char_count);
}
Análisis del código
text: Es nuestra cadena de prueba. Contiene el emoji🦀que es un carácter de 4 bytes, lo que demuestra la variabilidad de UTF-8.text.bytes(): Procesa la cadena byte a byte. En la salida, verás que el emoji se descompone en cuatro valores numéricos distintos.text.chars(): Itera sobre los puntos de código Unicode. A diferencia debytes(), aquí el emoji se trata como una unidad única.text.char_indices(): Es la forma más robusta de iterar. Nos devuelveidx(el desplazamiento en bytes) yc(elchar). Por ejemplo, si el emoji empieza en el byte 5,idxserá 5.text.get(5..9): Este método es la clave de la seguridad. Si intentáramos acceder a un índice que rompe un carácter, devolveríaNoneen lugar de hacer que el programa falle. Aquí extrae con éxito el emoji porque el rango5..9cubre exactamente sus 4 bytes.text.split_whitespace(): Crea un iterador que divide la cadena por cualquier espacio en blanco, permitiendo aislar palabras fácilmente.number_part.parse::<i32>(): Convierte una subcadena en un tipo numéricoi32. Es una operación que devuelve unResult, por lo que se maneja conmatch.text.chars().count(): Es necesario para obtener el número de caracteres humanos. No se debe usartext.len()para esto, ya quelen()devuelve el tamaño en bytes, no en caracteres.
N° 69