En Rust, la conversión entre tipos se divide en tres categorías principales según su seguridad y semántica: casting primitivo, conversiones garantizadas y conversiones fallibles. Entender cuándo usar cada una es crucial para evitar errores de lógica y pérdida de datos silenciosa.

El operador as se utiliza para el casting de tipos primitivos. Es una operación rápida que no verifica si la conversión es segura. En tipos enteros, si conviertes un tipo grande a uno pequeño (por ejemplo, de i32 a i8), el valor se truncará, manteniendo solo los bits significativos del tipo destino. En tipos de punto flotante, as puede producir NaN (Not a Number) o valores de infinito. El mayor riesgo de as es que no devuelve un error; si la conversión resulta en un valor inesperado, el programa continuará con datos erróneos.

Para conversiones que siempre tienen éxito y son semánticamente seguras, se utilizan los traits From e Into. El trait From<T> permite definir cómo convertir un tipo T en otro. Una vez que implementas From, el trait Into se implementa automáticamente para el tipo de destino. Estas conversiones son ideales cuando el tipo de destino puede contener todos los valores posibles del tipo de origen sin pérdida de información (como de u32 a u64). Además, existe la identidad: From<T> for T siempre está implementado, ya que convertir un tipo en sí mismo no requiere transformación.

Cuando la conversión es potencialmente insegura (como pasar un i32 con valor 300 a un i8), debemos usar TryFrom o TryInto. Estos métodos devuelven un Result<T, T::Error>, obligando al programador a manejar el caso en que el valor no quepa en el tipo de destino. Por último, para convertir cadenas de texto (&str o String) en tipos numéricos o estructuras, se utiliza el trait FromStr mediante el método .parse().

fn main() {
    // 1. Casting con 'as' (Potencialmente inseguro/truncamiento)
    let valor_grande: i32 = 300;
    // El valor 300 en binario no cabe en i8, se produce un truncamiento
    let valor_truncado = valor_grande as i8; 
    println!("Casting 'as' (300 as i8): {}", valor_truncado);

    // 2. Conversión segura con 'From' e 'Into' (Sin pérdida de datos)
    let num_u32: u32 = 100;
    // 'From' es usado para la conversión directa
    let num_u64_from = u64::from(num_u32);
    // 'Into' es la forma invertida y permite encadenar métodos
    let num_u64_into: u64 = num_u32.into();
    println!("From: {}, Into: {}", num_u64_from, num_u64_into);

    // 3. Conversión fallible con 'TryFrom' y 'TryInto'
    let numero_i32: i32 = 300;
    // Intentamos convertir un i32 que excede el rango de i8
    let resultado_try = i8::try_from(numero_i32);

    match resultado_try {
        Ok(valor_exitoso) => println!("TryFrom exitoso: {}", valor_exitoso),
        Err(e) => println!("TryFrom falló (error de rango): {}", e),
    }

    // 4. Parseo de strings con 'FromStr' (vía .parse())
    let texto = "42";
    // .parse() utiliza la implementación de FromStr para el tipo especificado
    let numero_parseado = texto.parse::<i32>().expect("Error al parsear");
    println!("Parseo de string: {}", numero_parseado);
}

Explicación del Código

  • valor_grande as i8: Aquí se aplica el operador as. Como 300 supera el límite máximo de un i8 (127), el compilador trunca los bits, resultando en un valor distinto al original sin lanzar error.
  • u64::from(num_u32): Utiliza el trait From. Es seguro porque un u64 puede representar cualquier valor que un u32 contenga, garantizando que no haya pérdida de información.
  • num_u32.into(): Es la versión de Into. Funciona exactamente igual que From, pero se llama desde la instancia del objeto.
  • i8::try_from(numero_i32): Implementa TryFrom. Al intentar meter 300 en un i8, el método devuelve un Err conteniendo la información del desbordamiento, permitiendo un manejo controlado mediante match.
  • texto.parse::<i32>(): Este método invoca internamente i32::from_str. El uso de la turbofish ::<i32> le indica al compilador qué tipo queremos obtener del string.

122