Inferencia de tipos de dato

Puedes ver este capítulo en YouTube en inglés

La inferencia de tipos de datos consiste en que si no se le indica el tipo de datos al compilador, pero lo puede determinar por sí mismo, él decide que tipo utilizar. El compilador siempre necesita conocer el tipo de las variables, pero no siempre es necesario decírselo expresamente. En realidad, normalmente no necesitas indicárselo. Por ejemplo, en la sentencia let mi_numero = 8, mi_numero será de tipo i32. Esto se debe a que el compilador elige siempre el tipo i32 para los números enteros si no se le indica uno. Sin embargo, en la siguiente sentencia let mi_numero: u8 = 8, la variable mi_numero es de tipo u8 ya que así se le ha indicado.

Así que normalmente el compilador puede deducir el tipo de datos, pero en ocasiones será necesario indicárselo por una de las siguientes dos razones:

  1. Estás programando algo muy complejo y el compilador no puede deducir el tipo de datos que es necesario.
  2. Quieres usar un tipo de datos diferente (por ejemplo, quieres un i128, no el i32 que se usa por defecto).

Para especificar un tipo, se añaden dos puntos después del nombre de la variable seguido del tipo.

fn main() {
    let numerito: u8 = 10;
}

Para los números, se puede especificar el tipo después del número, no se necesita un espacio - solo teclearlo justo después del número.

fn main() {
    let numerito = 10u8; // 10u8 = 10 de tipo u8
}

También se puede añadir _ para añadir claridad a la lectura.

fn main() {
    let numerito = 10_u8; // Esto es más fácil de leer
    let numerazo = 100_000_000_i32; // 100 millones es de fácil lectura con _
}

El _ no modifica el número. Solo lo hace más fácil de leer. Y no importa el cuantos _ se utilizan.

fn main() {
    let numero = 0________u8;
    let numero2 = 1___6______2____4______i32;
    println!("{}, {}", numero, numero2);
}

Lo anterior imprime 0, 1624.

Números decimales

Los números decimales son aquellos que tienen coma decimal1. 5.5 es un número decimal y 6 es un número entero. 5.0 también es un número decimal e incluso 5. lo es.

1 N.T.: en español se usa una coma como carácter para separar la parte entera de un número de su parte decimal. En Rust, la coma decimal española se sustituye por el punto decimal que es el que se usa habitualmente en los lenguajes de programación.

fn main() {
    let mi_decimal = 5.; // Rust ve un . y sabe que es un decimal (float, en inglés)
}

Rust utiliza diversos tipos de dato para almacenar números decimales, son el f32 y el f64. Al igual que en los números enteros, el número tras f muestra el número de bits utilizados en cada caso para almacenar el dato. Si no se indica el tipo, Rust elige f64.

fn main() {
    let mi_decimal: f64 = 5.0; // Esta variable es de tipo f64
    let mi_otro_decimal: f32 = 8.5; // Esta es de tipo f32
    let tercer_decimal = mi_decimal + mi_otro_decimal; // ⚠️
}

Cuanto se intenta ejecutar el código anterior, Rust se queja diciendo:

error[E0308]: mismatched types
 --> src/main.rs:4:39
  |
5 |     let tercer_decimal = mi_decimal + mi_otro_decimal; // ⚠️
  |                                       ^^^^^^^^^^^^^^^ expected `f64`, found `f32`

El compilador indica "expected (tipo), found (type)" cuando se usa el tipo erróneo. Rust lee el código de esta forma:

fn main() {
    let mi_decimal: f64 = 5.0; // El compilador ve un f64
    let mi_otro_decimal: f32 = 8.5; // El compilador ve un f64. Es un tipo diferente.
    let tercer_decimal = mi_decimal + // Se quiere sumar mi_decimal, que es f64 a algún
                                      // otro número. Ahora espera otro f64...  
        mi_otro_decimal; // ⚠️ pero se encuentra un f32, no se pueden sumar.
}

Así que cuando veas que el compilador indica "expected (tipo), found (type)", debes buscar la causa por la que el compilador esperaba un tipo de datos diferente.

Con los números simples es fácil arreglarlo. Puedes convertir el f32 a f64 con un as.

fn main() {
    let mi_decimal: f64 = 5.0;
    let mi_otro_decimal: f32 = 8.5;
    // En la siguiente línea, se utiliza mi_otro decimal como un f64
    let tercer_decimal = mi_decimal + mi_otro_decimal as f64;
}

O simplemente, se pueden eliminar las declaraciones de tipo, Rust elegirá tipos que se puedan sumar entre sí.

fn main() {
    let mi_decimal = 5.0; // Rust elige f64
    let mi_otro_decimal = 8.5; // Rust elige de nuevo f64
    let tercer_decimal = mi_decimal + mi_otro_decimal;
}

El compilador de Rust es inteligente y no elegirá f64 si necesitas f32:

fn main() {
    let mi_decimal: f32 = 5.0; // Rust elige f64
    let mi_otro_decimal = 8.5; // Normalmente Rust elegiría f64
    // pero al conocer que lo vamos a sumar a un f32, elige un f32 para mi_otro_decimal
    let tercer_decimal = mi_decimal + mi_otro_decimal;
}