Lección 03 de 26 Primeros pasos
Tipos de datos
- Cada valor tiene un tipo y Rust lo conoce al compilar. Casi siempre lo infiere: 42 es un i32.
- 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.
- Una tupla agrupa valores de tipos distintos y tamaño fijo. Lees cada uno con .0, .1, .2...
- Un array guarda valores del mismo tipo con longitud fija: [&str; 3]. Los índices empiezan en 0.
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ño | Con signo | Sin signo |
|---|---|---|
| 8 bits | i8 | u8 |
| 16 bits | i16 | u16 |
| 32 bits | i32 | u32 |
| 64 bits | i64 | u64 |
| 128 bits | i128 | u128 |
| Según la arquitectura | isize | usize |
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.
Los dos ocupan 8 bits y tienen 256 valores posibles: solo cambia dónde empieza el rango.
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. Usausizepara 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 overflowAl 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 integerTampoco 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 3Arrays 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:
asno 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 Resuelto
30 -5 48000000.30 -5 48000000
Al ejecutar, unos tests ocultos comprueban tus funciones.
Pulsa Ejecutar para compilar y ver aquí la salida de tu programa.