2. Tipos, mutabilidad y expresiones
Ejemplo completo: examples/l02_types.rs — ejecútalo con cargo run --example l02_types.
Inmutable por defecto
Sección titulada «Inmutable por defecto»let answer = 42; // inferido como i32, no puede cambiarlet mut counter: u32 = 0; // explícitamente mutablecounter += 1;En términos de C#, cada let es como una variable local que nunca puedes reasignar; en Java, como final var. La mutabilidad se elige con mut:
let count = 0;count += 1;error[E0384]: cannot assign twice to immutable variable `count` --> e_immutable.rs:3:5 |2 | let count = 0; | ----- first assignment to `count`3 | count += 1; | ^^^^^^^^^^ cannot assign twice to immutable variable |help: consider making this binding mutable |2 | let mut count = 0; | +++Sombreado (shadowing)
Sección titulada «Sombreado (shadowing)»Puedes declarar una variable nueva con el mismo nombre, incluso con otro tipo. Resulta práctico para «analizar y reemplazar»:
let input = " 7 ";let input: i32 = input.trim().parse().expect("not a number");println!("input + 1 = {}", input + 1); // input + 1 = 8Esto no es una mutación: el primer input (un &str) sigue existiendo, simplemente queda oculto. C# y Java prohíben volver a declarar una variable local en el mismo ámbito.
Tipos escalares
Sección titulada «Tipos escalares»| Rust | C# | Java | Notas |
|---|---|---|---|
i8 / u8 |
sbyte / byte |
byte (con signo) / — |
Java no tiene enteros sin signo |
i16 / u16 |
short / ushort |
short / — |
|
i32 / u32 |
int / uint |
int / — |
i32 es el entero por defecto |
i64 / u64 |
long / ulong |
long / — |
|
i128 / u128 |
Int128 / UInt128 |
— | |
isize / usize |
nint / nuint |
— | del tamaño de un puntero; se usan para índices y longitudes |
f32 / f64 |
float / double |
float / double |
f64 es el flotante por defecto |
bool |
bool |
boolean |
|
char |
Rune |
punto de código int |
4 bytes, un valor escalar Unicode — no una unidad UTF-16 como el char de C#/Java |
let note = '♪';println!("{note} is {} bytes in UTF-8, size_of::<char>() = {}", note.len_utf8(), std::mem::size_of::<char>());// ♪ is 3 bytes in UTF-8, size_of::<char>() = 4Ninguna conversión implícita
Sección titulada «Ninguna conversión implícita»C# y Java amplían en silencio un int a long. Rust nunca convierte números por ti:
let small: i32 = 10;let big: i64 = 20;let total = small + big;error[E0308]: mismatched types --> e_mismatch.rs:4:25 |4 | let total = small + big; | ^^^ expected `i32`, found `i64`Convierte explícitamente con as (o con i64::from(small), que solo existe para las conversiones sin pérdida):
let total = small as i64 + big; // 30El desbordamiento es un error, no una característica
Sección titulada «El desbordamiento es un error, no una característica»| C# | Java | Rust, compilación debug | Rust, compilación release | |
|---|---|---|---|---|
255u8 + 1 |
da la vuelta (salvo con checked) |
da la vuelta | entra en pánico | da la vuelta |
En una compilación debug, Rust detiene el programa:
thread 'main' panicked at e_overflow.rs:3:16:attempt to add with overflowCuando dar la vuelta o fallar es el comportamiento que buscas, dilo explícitamente:
let max = u8::MAX;println!("checked: {:?}, wrapping: {}", max.checked_add(1), max.wrapping_add(1));// checked: None, wrapping: 0Tuplas y arrays
Sección titulada «Tuplas y arrays»let point: (f64, f64) = (1.5, -2.0);let (x, y) = point; // desestructuración, como la deconstrucción de tuplas en C#let primes = [2, 3, 5, 7, 11]; // array de tamaño fijo: [i32; 5]println!("x = {x}, y = {y}, first prime = {}, count = {}", primes[0], primes.len());// x = 1.5, y = -2, first prime = 2, count = 5Una lista que puede crecer es un Vec<T> (como List<T> / ArrayList<T>) — la lección 8 trata las colecciones.
Todo es una expresión
Sección titulada «Todo es una expresión»if devuelve un valor, así que no hay operador ternario:
let parity = if answer % 2 == 0 { "even" } else { "odd" };Un bloque { … } se evalúa a su última expresión — sin punto y coma:
let area = { let width = 3; let height = 4; width * height // sin `;` → este es el valor del bloque};Las funciones funcionan igual; return solo hace falta para salir antes de tiempo:
fn square(x: i32) -> i32 { x * x}// loop + break con un valorlet mut n = 1;let first_power_over_100 = loop { n *= 2; if n > 100 { break n; }}; // 128
// for sobre rangos: 0..3 excluye el final, 1..=10 lo incluyefor i in 0..3 { print!("{i} "); // 0 1 2}let sum: i32 = (1..=10).sum(); // 55No existe el for (int i = 0; i < n; i++) al estilo de C: usa un rango. También existe while condition { … }.
Puntos clave
Sección titulada «Puntos clave»letes inmutable; añademutsolo cuando lo necesites.- Los tipos numéricos son explícitos, las conversiones son explícitas y el desbordamiento provoca un pánico en debug.
chares un valor escalar Unicode (4 bytes), no UTF-16.if, los bloques,loopy las funciones son expresiones; la última expresión sin;es el valor.
Ejercicios
Sección titulada «Ejercicios»- Escribe una función
clamp_percent(value: i32) -> u8que devuelva0para los valores negativos,100para los valores mayores que 100 y el propio valor en los demás casos — usandoifcomo expresión.
Solución
fn clamp_percent(value: i32) -> u8 { if value < 0 { 0 } else if value > 100 { 100 } else { value as u8 }}El as u8 es seguro aquí porque se sabe que el valor está dentro de 0..=100. Rust también ofrece value.clamp(0, 100) as u8.
- ¿Por qué esta función no compila y cuál es la corrección de un solo carácter?
fn double(x: i32) -> i32 { x * 2;}Solución
El punto y coma final convierte x * 2 en una sentencia, así que el cuerpo se evalúa a () en lugar de i32 (error E0308: mismatched types). Quita el ;.
- En C#,
byte b = 255; b++;da0. ¿Qué ocurre en Rust conlet mut b: u8 = 255; b += 1;?
Solución
En una compilación debug, el programa entra en pánico con attempt to add with overflow. En una compilación release, da la vuelta a 0. Si lo que quieres es dar la vuelta, escribe b = b.wrapping_add(1);; si quieres detectarlo, usa b.checked_add(1), que devuelve un Option<u8>.