Tipos de Datos
Domina los tipos escalares, tipos compuestos, inferencia de tipos y anotaciones de tipo explícitas en Rust
Tipos de Datos
Rust es un lenguaje estáticamente tipado — toda variable debe tener un tipo conocido en tiempo de compilación. El compilador es inteligente infiriendo tipos, pero siempre puedes anotarlos explícitamente.
Dos Categorías de Tipos
| Categoría | Descripción | Ejemplos |
|---|---|---|
| Escalar | Valor único | enteros, floats, bool, char |
| Compuesto | Grupo de valores | tuplas, arrays, structs, enums |
Tipos Escalares
Tipos Enteros
Rust ofrece enteros con signo (i) y sin signo (u) en varios tamaños:
| Tamaño | Con Signo | Sin Signo | Rango (con signo) |
|---|---|---|---|
| 8 bits | i8 | u8 | -128 a 127 |
| 16 bits | i16 | u16 | -32.768 a 32.767 |
| 32 bits | i32 | u32 | -2³¹ a 2³¹ -1 |
| 64 bits | i64 | u64 | -2⁶³ a 2⁶³ -1 |
| 128 bits | i128 | u128 | -2¹²⁷ a 2¹²⁷ -1 |
| arch | isize | usize | depende de la plataforma (32/64 bits) |
fn main() {
let a = 42; // i32 (por defecto)
let b: u8 = 255; // Byte sin signo explícito
let c = 100_000; // Separadores de guion bajo para legibilidad
let d = 0xff; // Hexadecimal
let e = 0o77; // Octal
let f = 0b1111; // Binario
let g = b'A'; // Literal de byte (u8, ASCII)
// usize/isize para indexar colecciones
let arr = [1, 2, 3];
let index: usize = 1;
println!("{0}", &arr[index]); // 2
}i32 es el tipo entero por defecto porque es rápido en CPUs modernas y evita problemas de desbordamiento comunes con tipos más pequeños.
Desbordamiento de Entero
fn main() {
let mut x: u8 = 255;
// x = x + 1; // PANICA en modo debug (verificación de desbordamiento)
// Wrapping explícito:
let y = x.wrapping_add(1); // 0 (envuelve)
let z = x.saturating_add(1); // 255 (satura en máximo)
println!("wrapping: {y}, saturating: {z}");
}| Método | Comportamiento |
|---|---|
wrapping_add | Envuelve (complemento a dos) |
saturating_add | Se detiene en valor mínimo/máximo |
overflowing_add | Retorna (resultado, desbordó bool) |
checked_add | Retorna Option (None en desbordamiento) |
En modo debug, el desbordamiento de enteros paniquea. En modo release, envuelve silenciosamente. Nunca confíes en el comportamiento de desbordamiento — usa métodos explícitos.
Tipos de Punto Flotante
fn main() {
let x = 2.0; // f64 (por defecto, doble precisión)
let y: f32 = 3.0; // f32 (precisión simple)
// Operaciones f64
let al_cuadrado = x.powi(2); // 4.0
let raiz = x.sqrt(); // 1.414...
let resto = 5.0 % 2.0; // 1.0
}| Tipo | Precisión | Tamaño | Caso de Uso |
|---|---|---|---|
f32 | ~7 dígitos decimales | 4 bytes | Gráficos, GPUs |
f64 | ~15 dígitos decimales | 8 bytes | Computación general |
Los floats no implementan Eq u Ord — NaN y problemas de precisión hacen que la comparación no sea confiable. Usa f64::EPSILON para comparación aproximada:
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < f64::EPSILON
}El Tipo Booleano
fn main() {
let rust_es_divertido = true;
let es_dificil: bool = false;
if rust_es_divertido {
println!("¡Rust es divertido!");
}
// Conversión a entero
println!("{}", true as u8); // 1
println!("{}", false as u8); // 0
}El Tipo Carácter
char tiene 4 bytes y representa un Valor Escalar Unicode:
fn main() {
let c = 'z';
let z: char = 'ℤ';
let gato_ojos_corazon = '😻';
println!("{c} {z} {gato_ojos_corazon}");
// char como número
println!("{}", 'A' as u8); // 65
println!("{}", '😻' as u32); // 128571
}char tiene 4 bytes (no 1 como el char de C). Soporta Unicode completo pero no es ASCII — usa u8 o &[u8] para datos a nivel de byte.
Tipos Compuestos
Tuplas
Las tuplas agrupan valores de tipos diferentes. Tamaño fijo, conocido en tiempo de compilación.
fn main() {
let tup: (i32, f64, char) = (500, 6.4, 'x');
// Desestructuración
let (x, y, z) = tup;
println!("{x}, {y}, {z}");
// Notación de punto (indexada desde 0)
println!("{}", tup.0); // 500
println!("{}", tup.1); // 6.4
// Tupla unitaria (tupla vacía)
let unit: () = ();
}| Patrón | Ejemplo | Cuándo Usar |
|---|---|---|
| Desestructurar | let (a, b) = tup | Extraer todos los valores |
| Acceso por punto | tup.0 | Extraer un valor |
| Ignorar | let (a, _, _) = tup | Extraer algunos valores |
Arrays
Los arrays tienen tamaño fijo, todos los elementos mismo tipo, almacenados en la stack:
fn main() {
let arr = [1, 2, 3, 4, 5];
// Anotación de tipo: [tipo; longitud]
let tipado: [i32; 5] = [1, 2, 3, 4, 5];
// Expresión de repetición: [valor; cuenta]
let ceros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// Acceso
let primero = arr[0];
let segundo = arr[1];
// Verificación de límites en ejecución
// let oops = arr[10]; // Panica: índice fuera de límites
}| Característica | Array | Tupla |
|---|---|---|
| ¿Mismo tipo? | Sí | No |
| ¿Tamaño fijo? | Sí | Sí |
| Tipo por defecto | [T; N] | (T1, T2, ...) |
| Acceso | arr[i] (verificado en ejecución) | tup.i (tiempo de compilación) |
Vectores (Avance)
Los vectores son arrays asignados en el heap, redimensionables:
fn main() {
let mut vec = vec![1, 2, 3];
vec.push(4); // Ahora [1, 2, 3, 4]
println!("{vec:?}");
}Inferencia de Tipos y Anotaciones
Rust infiere tipos en la mayoría de las situaciones:
fn main() {
// La inferencia funciona
let x = 42; // i32
let y = 3.14; // f64
let cond = true; // bool
// Las anotaciones aclaran intención
let puerto: u16 = 8080;
let pi: f32 = 3.14159;
// A veces obligatorio (ambigüedad de tipo)
let ambiguo = "hola".parse(); // ERROR: no puede inferir tipo
let analizado: u32 = "42".parse().unwrap(); // OK con anotación
// Sintaxis Turbofish
let n = "100".parse::<i32>().unwrap();
}Alias de Tipo
type Kilometros = i32;
type Thunk = Box<dyn FnOnce() + Send>;
fn main() {
let distancia: Kilometros = 100;
println!("{distancia} km");
}Tipos Sized y Unsized
| Categoría | Ejemplos | ¿Tamaño en Tiempo de Compilación? |
|---|---|---|
| Sized | i32, f64, [i32; 5], String | Sí |
| Unsized | str, [i32], dyn Trait | No (detrás de puntero) |
Los tipos unsized deben estar siempre detrás de un puntero: &str, Box<dyn Trait>.
Preguntas de Práctica
- ¿Cuál es el tipo entero por defecto en Rust? ¿Y el tipo float?
- ¿Cuántos bytes tiene un
charen Rust? ¿Cómo es diferente del char de C? - ¿Cuál es la diferencia entre una tupla y un array?
- ¿Por qué no puedes comparar dos valores
f64con==directamente? - ¿Qué sucede si accedes a un índice de array fuera de los límites?
- ¿Cuándo debes anotar el tipo de una variable explícitamente?
- ¿Para qué sirve el tipo unitario
()? - ¿Cómo analizar una cadena en un entero?
- ¿Cuál es la diferencia entre
usizeyu64? - ¿Qué métodos puedes usar para manejar el desbordamiento de enteros de forma segura?