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 &String como literales sin necesidad de convertir los tipos manualmente.
  • texto_propio: Es una instancia de String. Al usar push_str, se modifica el contenido directamente en el heap.
  • literal: Es un &str que 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] sobre texto_propio. Representa una vista de los primeros 4 bytes de texto_propio sin copiar el texto.
  • raw_string: Utiliza la sintaxis r#"..."# 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 un String (&String), Rust la trata como un &str para satisfacer la firma de la función.
  • literal.to_string() y texto_propio.to_owned(): Ambos métodos se utilizan para convertir una referencia &str en un objeto String que tiene su propia propiedad de los datos, realizando una copia en el heap.

33