En Rust, la gestión de cadenas de texto se divide fundamentalmente en dos tipos: String y &str. La diferencia principal radica en la propiedad (ownership) y la ubicación de memoria.
Un String es un tipo con propiedad (owned). Se almacena en el heap, lo que significa que su tamaño es dinámico; puedes añadir o eliminar caracteres durante la ejecución. Internamente, un String es una estructura que contiene un puntero a un buffer en el heap, la longitud actual y la capacidad total. Debes usar String cuando necesites modificar el texto o cuando necesites que el dato viva más allá de la vida del bloque de código actual (por ejemplo, al devolverlo de una función o guardarlo en una estructura).
Por el contrario, &str es un “slice” de cadena o una referencia. No posee los datos, sino que es una “vista” hacia una secuencia de bytes UTF-8 que ya existe en algún lugar. Un &str es un “puntero gordo” (fat pointer) que guarda únicamente la dirección de memoria del primer byte y la longitud. Puede apuntar a:
1. Un literal de texto en el binario del programa (tipo &'static str).
2. Una parte de un String existente (un sub-slice).
3. Un buffer de bytes en cualquier parte de la memoria.
Una regla de diseño fundamental en Rust es: usa &str para los argumentos de tus funciones. Si defines una función que acepta &String, estarás limitando su uso, ya que solo aceptará referencias a String. Sin embargo, si defines la función para aceptar &str, podrás pasarle un &String (gracias a la coerción de desreferencia), un literal de texto o un sub-slice, haciendo tu código mucho más flexible y eficiente al evitar asignaciones de memoria innecesarias.
fn analizar_texto(s: &str) {
// La función recibe un slice, es decir, una vista de datos
println!("Analizando: {}", s);
println!("Longitud en bytes: {}", s.len());
}
fn main() {
// 1. String: Posee la propiedad, vive en el heap y es mutable
let mut texto_propio = String::from("Hola Mundo");
texto_propio.push_str(" Rust");
// 2. &str como literal: Vive en el binario del programa ('static)
let literal: &str = "Soy un literal";
// 3. &str como slice de un String: Referencia a una parte de texto_propio
let slice_de_string: &str = &texto_propio[0..4];
// 4. Raw strings y multilínea: r#"..."# para evitar escapar comillas
let raw_string = r#"Este es un "raw string" con caracteres especiales"#;
let multiline = "Línea 1
Línea 2";
// Uso de la función con diferentes tipos
analizar_texto(&texto_propio); // Coerción automática de &String a &str
analizar_texto(literal); // El literal ya es &str
analizar_texto(slice_de_string); // Es un slice de un String
// Conversiones de &str a String (crea una nueva copia en el heap)
let nuevo_string: String = literal.to_string();
let clon_string: String = texto_propio.to_owned();
println!("Original: {}, Clon: {}", texto_propio, clon_string);
println!("Raw: {}", raw_string);
println!("Multilínea:\n{}", multiline);
}
Explicación del Código
analizar_texto(s: &str): Esta función acepta un tipo&str. Esto le permite recibir tanto&Stringcomo literales sin necesidad de convertir los tipos manualmente.texto_propio: Es una instancia deString. Al usarpush_str, se modifica el contenido directamente en el heap.literal: Es un&strque apunta directamente al segmento de memoria donde se guardan los textos constantes en el ejecutable.slice_de_string: Se obtiene mediante la sintaxis de rango[0..4]sobretexto_propio. Representa una vista de los primeros 4 bytes detexto_propiosin copiar el texto.raw_string: Utiliza la sintaxisr#"..."#para permitir el uso de comillas dobles dentro de la cadena sin necesidad de usar secuencias de escape como\".analizar_texto(&texto_propio): Aquí ocurre la coerción de desreferencia. Aunque pasamos una referencia a unString(&String), Rust la trata como un&strpara satisfacer la firma de la función.literal.to_string()ytexto_propio.to_owned(): Ambos métodos se utilizan para convertir una referencia&stren un objetoStringque tiene su propia propiedad de los datos, realizando una copia en el heap.
N° 33