Aprende Rust

Lección 03 de 26 Primeros pasos

Tipos de datos

  • 5 min de lectura
  • 4 ejercicios
  1. Cada valor tiene un tipo y Rust lo conoce al compilar. Casi siempre lo infiere: 42 es un i32.
  2. Los enteros tienen tamaño: un u8 usa 8 bits y va de 0 a 255. Si un valor no cabe, Rust no lo deja pasar.
  3. Una tupla agrupa valores de tipos distintos y tamaño fijo. Lees cada uno con .0, .1, .2...
  4. Un array guarda valores del mismo tipo con longitud fija: [&str; 3]. Los índices empiezan en 0.
1/4

Rust es un lenguaje de tipado estático: el compilador conoce el tipo de cada valor antes de ejecutar el programa. Gracias a eso detecta al compilar muchos errores que en otros lenguajes descubrirías en producción, como sumar un número con un texto.

Eso no significa que tengas que escribir los tipos en todas partes, porque casi siempre Rust los infiere. En esta lección vas a conocer los tipos básicos: los escalares, que guardan un único valor, y los compuestos, que agrupan varios.

Inferencia y anotaciones de tipo

fn main() {
    let entero = 42;          // i32
    let decimal = 3.14;       // f64
    let activo = true;        // bool
    let puntos: u64 = 1_500;  // anotación explícita

    println!("{entero} {decimal} {activo} {puntos}");
}

Si no dices nada, un número entero es i32 y uno con decimales es f64. Para elegir otro tipo, añade una anotación con : tipo después del nombre. También puedes usar un sufijo en el propio número, como 255u8 o 2.5f32.

Enteros

Los enteros son números sin parte decimal. Su nombre indica dos cosas: si admiten negativos (i, con signo) o no (u, de unsigned, sin signo) y cuántos bits ocupan.

TamañoCon signoSin signo
8 bitsi8u8
16 bitsi16u16
32 bitsi32u32
64 bitsi64u64
128 bitsi128u128
Según la arquitecturaisizeusize

Con n bits caben 2ⁿ valores distintos. Un tipo sin signo va de 0 a 2ⁿ - 1 y uno con signo reparte esos valores entre negativos y positivos: de -2ⁿ⁻¹ a 2ⁿ⁻¹ - 1.

No hace falta que memorices los límites: cada tipo los tiene como constantes MIN y MAX.

fn main() {
    println!("i8:  {} a {}", i8::MIN, i8::MAX);
    println!("u8:  {} a {}", u8::MIN, u8::MAX);
    println!("i32: {} a {}", i32::MIN, i32::MAX);
    println!("u64: {} a {}", u64::MIN, u64::MAX);
}

isize y usize ocupan 64 bits en un ordenador de 64 bits. usize es el tipo que Rust usa para posiciones y tamaños, como la longitud de un texto o el índice de un array.

Qué tipo elegir: Si no tienes un motivo concreto, usa i32. Usa usize para posiciones y tamaños, y un tipo sin signo cuando el valor nunca pueda ser negativo.

Desbordamiento (overflow)

¿Qué pasa si un valor se sale del rango de su tipo? Si el compilador lo ve venir, directamente no compila: let x: u8 = 256; es un error. Si ocurre mientras el programa se ejecuta, en modo debug el programa se detiene con un panic:

fn main() {
    let mut volumen: u8 = 250;
    volumen += 10;
    println!("Volumen: {volumen}");
}
thread 'main' panicked at src/main.rs:3:5:
attempt to add with overflow

Al compilar en modo release (cargo build --release) no se comprueba y el valor da la vuelta: en un u8, 255 + 1 pasa a ser 0. Como casi nunca es lo que quieres, si necesitas ese comportamiento pídelo de forma explícita:

fn main() {
    let volumen: u8 = 250;
    println!("{}", volumen.wrapping_add(10));   // 4: da la vuelta
    println!("{}", volumen.saturating_add(10)); // 255: se queda en el límite
}

Números con decimales

Rust tiene dos tipos de coma flotante: f64, el predeterminado, y f32, que ocupa la mitad y tiene menos precisión. Usa f64 salvo que tengas un buen motivo.

fn main() {
    let precio = 19.99;   // f64
    let ancho: f32 = 2.5;
    println!("{} {}", precio * 2.0, ancho);

    println!("{}", 7 / 2);     // 3: la división entre enteros descarta los decimales
    println!("{}", 7.0 / 2.0); // 3.5
    println!("{}", 7 % 2);     // 1: el resto de la división
}

Rust no mezcla tipos en las operaciones. No puedes sumar un entero y un flotante sin convertir antes uno de ellos:

fn main() {
    let enteros = 5;
    let decimales = 2.5;
    let suma = enteros + decimales;
    println!("{suma}");
}
error[E0277]: cannot add a float to an integer

Tampoco dos enteros de tipos distintos, como i32 e i64. Verás cómo convertirlos al final de la lección.

Booleanos

El tipo bool solo tiene dos valores: true y false. Es el resultado de las comparaciones y se combina con && (y), || (o) y ! (no):

fn main() {
    let edad = 20;
    let es_mayor = edad >= 18;
    let tiene_entrada = false;

    println!("¿Puede entrar? {}", es_mayor && tiene_entrada);
    println!("¿Es menor? {}", !es_mayor);
}

Caracteres

El tipo char representa un único carácter Unicode y se escribe entre comillas simples. Ocupa 4 bytes, así que sirve para letras con tilde, otros alfabetos o emojis:

fn main() {
    let letra = 'ñ';
    let omega = 'Ω';
    let cangrejo = '🦀';

    println!("{letra} {omega} {cangrejo}");
}

Comillas: Las simples son para un char ('a') y las dobles, para un texto ("a"). No son intercambiables: 'hola' no compila.

Tuplas

Una tupla agrupa varios valores, que pueden ser de tipos distintos, en un único valor. Su tamaño es fijo:

fn main() {
    let jugador: (&str, u32, f64) = ("Ferris", 3, 98.5);

    // Acceso por posición, empezando en 0
    println!("Nombre: {}", jugador.0);
    println!("Nivel: {}", jugador.1);

    // Desestructuración: una variable para cada elemento
    let (nombre, nivel, puntos) = jugador;
    println!("{nombre} está en el nivel {nivel} con {puntos} puntos");
}

&str es el tipo de los textos entre comillas dobles; lo verás a fondo más adelante. La desestructuración reparte los elementos de la tupla en variables, en el mismo orden.

La tupla vacía () tiene nombre propio: unit. Representa «ningún valor» y volverás a verla en la lección de funciones.

Arrays

Un array guarda varios valores del mismo tipo con una longitud fija. Su tipo se escribe [T; N]: el tipo de los elementos y cuántos hay.

fn main() {
    let dias: [&str; 3] = ["lunes", "martes", "miércoles"];
    let ceros = [0; 5]; // cinco ceros

    println!("El primero es {}", dias[0]);
    println!("Hay {} días", dias.len());
    println!("{:?}", ceros);
}
  • Se accede a cada elemento con su índice entre corchetes, empezando en 0.
  • .len() devuelve cuántos elementos tiene.
  • {:?} imprime el array completo (también sirve para tuplas). Es el formato de depuración.

Si usas un índice fuera del array, Rust no lee memoria que no le corresponde, como pasaría en C: el programa se detiene con un panic.

fn main() {
    let dias = ["lunes", "martes", "miércoles"];
    let indice = dias.len();
    println!("{}", dias[indice]);
}
index out of bounds: the len is 3 but the index is 3

Arrays y vectores: Un array no puede crecer. Cuando necesites una lista que cambie de tamaño usarás Vec, que verás más adelante.

Conversiones con as

Rust nunca convierte entre tipos numéricos por su cuenta: lo haces tú con as.

fn main() {
    let total = 7;
    let partes = 2;
    let media = total as f64 / partes as f64;
    println!("Media: {media}"); // 3.5

    println!("{}", 3.99 as i32); // 3: se descartan los decimales
    println!("{}", 'A' as u32);  // 65: el código Unicode de la letra

    let grande: i32 = 300;
    println!("{}", grande as u8); // 44: ¡no cabe en un u8!
}

Cuidado: as no avisa si pierdes información. Úsalo cuando sepas que el valor cabe en el tipo de destino.

Hora de practicar

Ejercicios

0/4 resueltos

Reto 1 de 4

Este programa no compila porque algunos valores no caben en su tipo. Cambia solo las anotaciones de tipo (sin quitarlas) para que imprima 30 -5 48000000.
Salida esperada
30 -5 48000000
src/main.rs
$ cargo run Listo

Pulsa Ejecutar para compilar y ver aquí la salida de tu programa.

¿Algo no cuadra en este reto? Avísanos