Ir al contenido

2. Tipos, mutabilidad y expresiones

Ejemplo completo: examples/l02_types.rs — ejecútalo con cargo run --example l02_types.

let answer = 42; // inferido como i32, no puede cambiar
let mut counter: u32 = 0; // explícitamente mutable
counter += 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;
| +++

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 = 8

Esto 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.

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>() = 4

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; // 30

El 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 overflow

Cuando 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: 0
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 = 5

Una lista que puede crecer es un Vec<T> (como List<T> / ArrayList<T>) — la lección 8 trata las colecciones.

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 valor
let 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 incluye
for i in 0..3 {
print!("{i} "); // 0 1 2
}
let sum: i32 = (1..=10).sum(); // 55

No existe el for (int i = 0; i < n; i++) al estilo de C: usa un rango. También existe while condition { … }.

  • let es inmutable; añade mut solo cuando lo necesites.
  • Los tipos numéricos son explícitos, las conversiones son explícitas y el desbordamiento provoca un pánico en debug.
  • char es un valor escalar Unicode (4 bytes), no UTF-16.
  • if, los bloques, loop y las funciones son expresiones; la última expresión sin ; es el valor.
  1. Escribe una función clamp_percent(value: i32) -> u8 que devuelva 0 para los valores negativos, 100 para los valores mayores que 100 y el propio valor en los demás casos — usando if como 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.

  1. ¿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 ;.

  1. En C#, byte b = 255; b++; da 0. ¿Qué ocurre en Rust con let 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>.