En Rust, el tipo String es una colección de bytes en el heap que representa texto en formato UTF-8. A diferencia de los literales de cadena (&str), que son referencias inmutables a datos almacenados en el binario, un String es un tipo owned (con propiedad). Esto significa que el programa es dueño de la memoria y puede modificarla, expandirla o liberarla cuando ya no sea necesaria.

Para crear un String de forma eficiente, se puede usar String::new() para una cadena vacía o String::with_capacity(n) cuando se conoce de antemano el tamaño aproximado. Usar with_capacity es una buena práctica de optimización, ya que evita que el sistema tenga que reasignar y copiar la memoria múltiples veces a medida que la cadena crece. Otras formas comunes de creación son String::from("texto") y el método .to_string(), que convierten literales en objetos String.

La concatenación presenta comportamientos importantes sobre la propiedad de los datos. El operador + permite unir un String con un &str, pero tiene una regla crítica: consume la variable de la izquierda. Después de usar s1 + &s2, s1 ya no es válido porque su propiedad se ha movido a la nueva cadena. Para evitar perder la propiedad de tus variables, la macro format! es la alternativa ideal, ya que permite construir cadenas complejas usando múltiples variables sin consumir ninguna de ellas.

Finalmente, un String hereda la mayoría de sus métodos de manipulación de las rebanadas de texto (str) mediante la implementación de Deref. Esto permite usar funciones como contains, trim, to_uppercase o replace directamente. Es vital recordar que el método len() devuelve la longitud en bytes, no en número de caracteres; en textos con caracteres especiales (como emojis o tildes), esto puede ser diferente a la cantidad de letras visibles.

fn main() {
    // 1. Construcción y capacidad
    let mut texto_dinamico = String::with_capacity(20);
    texto_dinamico.push_str("Hola"); // Añade un &str
    texto_dinamico.push('!');        // Añade un char
    
    // 2. Concatenación y Ownership
    let s1 = String::from("Rust");
    let s2 = String::from(" es");
    let s3 = s2.clone(); // Clonamos para que s2 siga siendo usable

    // El operador '+' consume s1. s1 ya no es válido después de esta línea.
    let concatenado = s1 + " increíble"; 
    
    // Usamos format! para combinar sin consumir las variables originales
    let mensaje_formateado = format!("{}-{}", concatenado, s3);

    // 3. Métodos de manipulación (vía Deref)
    let limpio = "  espacios  ".trim();
    let mayusculas = "hola".to_uppercase();
    let tiene_rust = mensaje_formateado.contains("Rust");

    // 4. Propiedades y conversión
    let capacidad_actual = texto_dinamico.capacity();
    let bytes_de_texto = "A".into_bytes(); // Convierte String a Vec<u8>
    let de_bytes = String::from_utf8(bytes_de_texto).unwrap(); // De bytes a String

    println!("Texto: {}", texto_dinamico);
    println!("Mensaje: {}", mensaje_formateado);
    println!("¿Contiene 'Rust'?: {}", tiene_rust);
    println!("Capacidad de texto_dinamico: {}", capacidad_actual);
    println!("Convertido de nuevo: {}", de_bytes);
}

Explicación del Código

  • texto_dinamico: Es un String creado con String::with_capacity(20), reservando espacio en el heap para 20 bytes. Se usa push_str para añadir la cadena "Hola" y push para añadir el carácter '!'.
  • s1, s2 y s3: s1 y s2 son objetos String. Usamos s2.clone() para crear una copia exacta en el heap, permitiéndonos usar s2 más tarde.
  • concatenado: Se crea mediante la operación s1 + " increíble". El operador + consume la propiedad de s1, por lo que s1 queda invalidado para el resto del programa.
  • mensaje_formateado: Utiliza la macro format! para unir concatenado y s3. A diferencia del operador +, format! no consume las variables, permitiendo que s3 siga siendo accesible.
  • limpio, mayusculas y tiene_rust: Demuestran métodos de manipulación. trim() elimina espacios, to_uppercase() devuelve una nueva String en mayúsculas, y contains devuelve un booleano verificando la existencia de una subcadena.
  • capacidad_actual: Llama a capacity() para mostrar cuánta memoria total tiene reservada texto_dinamico.
  • bytes_de_texto: Utiliza into_bytes() para transformar el String en un Vec<u8> (una colección de bytes).
  • de_bytes: Utiliza String::from_utf8 para convertir la colección de bytes de nuevo a un tipo String válido.

68