Variables y Mutabilidad
Como se mencionó en la sección “Almacenando valores con variables” , por defecto, las variables son inmutables. Este es uno de los muchos empujes que Rust le da para que escriba su código de una manera que aproveche la seguridad y la fácil concurrencia que ofrece Rust. Sin embargo, todavía tiene la opción de hacer sus variables mutables. Exploremos cómo y por qué Rust le anima a favorecer la inmutabilidad y por qué a veces podría querer optar por no hacerlo.
Cuando una variable es inmutable, una vez que un valor está vinculado a un
nombre, no puede cambiar ese valor. Para ilustrar esto, genere un nuevo
proyecto llamado variables en su directorio proyectos usando cargo new variables
.
Luego, en su nuevo directorio variables, abra src/main.rs y reemplace su código con el siguiente código, que aún no se compilará:
Nombre de archivo: src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
Guarde y ejecute el programa usando cargo run
. Debería recibir un mensaje de
error relacionado con un error de inmutabilidad, como se muestra en esta
salida:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
Este ejemplo muestra cómo el compilador le ayuda a encontrar errores en sus programas. Los errores de compilación pueden ser frustrantes, pero realmente solo significa que su programa aún no está realizando de manera segura lo que desea que haga; no significa que no es un buen programador! Los Rustaceans experimentados aún reciben errores de compilación.
Recibió el mensaje de error cannot assign twice to immutable variable `x`
porque intentó asignar un segundo valor a la variable inmutable x
.
Es importante que obtengamos errores en tiempo de compilación cuando intentamos cambiar un valor que está designado como inmutable, porque esta situación puede conducir a errores. Si una parte de nuestro código opera bajo la suposición de que un valor nunca cambiará y otra parte de nuestro código cambia ese valor, es posible que la primera parte del código no haga lo que estaba diseñado para hacer. La causa de este tipo de error puede ser difícil de rastrear después del hecho, especialmente cuando la segunda pieza de código cambia el valor solo algunas veces. El compilador de Rust garantiza que cuando afirma que un valor no cambiará, realmente no cambiará, por lo que no tiene que rastrearlo usted mismo. Su código es, por lo tanto, más fácil de razonar.
Pero la mutabilidad puede ser muy útil y puede hacer que el código sea más
conveniente de escribir. Aunque las variables son inmutables por defecto, puede
hacerlas mutables agregando mut
delante del nombre de la variable como lo
hizo en el Capitulo 2.
Agregando mut
también comunica la intención a los lectores futuros del código
indicando que otras partes del código cambiarán el valor de esta variable.
Por ejemplo, cambiemos src/main.rs a lo siguiente:
Nombre de archivo: src/main.rs
fn main() { let mut x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }
Cuando ejecutamos el programa ahora, obtenemos esto:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
Se nos permite cambiar el valor vinculado a x
de 5
a 6
cuando se usa
mut
. En última instancia, decidir si usar o no la mutabilidad depende de
usted y depende de lo que crea que es más claro en esa situación particular.
Constantes
Al igual que las variables inmutables, las constantes son valores que están vinculados a un nombre y no se les permite cambiar, pero hay algunas diferencias entre las constantes y las variables.
Primero, no se le permite usar mut
con constantes. Las constantes no son solo
inmutables por defecto, siempre son inmutables. Declara constantes usando la
palabra clave const
en lugar de la palabra clave let
, y el tipo del valor
debe estar anotado. Cubriremos los tipos y las anotaciones de tipo en la
siguiente sección, “Tipos de datos”, por lo que no se
preocupe por los detalles ahora. Solo sepa que siempre debe anotar el tipo.
Las constantes se pueden declarar en cualquier ámbito, incluido el ámbito global, lo que las hace útiles para valores que muchas partes del código necesitan conocer.
La última diferencia es que las constantes solo se pueden establecer en una expresión constante, no en el resultado de un valor que solo se podría calcular en tiempo de ejecución.
Aquí hay un ejemplo de una declaración constante:
#![allow(unused)] fn main() { const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; }
El nombre de la constante es THREE_HOURS_IN_SECONDS
y su valor se establece
en el resultado de multiplicar 60 (el número de segundos en un minuto) por 60
(el número de minutos en una hora) por 3 (el número de horas que queremos
contar en este programa). La convención de nombramiento de Rust para constantes
es usar mayúsculas con guiones bajos entre palabras. El compilador es capaz de
evaluar un conjunto limitado de operaciones en tiempo de compilación, lo que
nos permite elegir escribir este valor de una manera que sea más fácil de
entender y verificar, en lugar de establecer esta constante en el valor 10,800.
Vea la "sección de la Referencia de Rust sobre la evaluación constante"
para más información sobre qué operaciones se pueden usar al declarar constantes.
Las constantes son válidas durante todo el tiempo que se ejecuta un programa, dentro del ámbito en el que se declararon. Esta propiedad hace que las constantes sean útiles para los valores en el dominio de su aplicación que varias partes del programa podrían necesitar conocer, como el número máximo de puntos que cualquier jugador de un juego puede obtener o la velocidad de la luz.
Nombrar valores codificados en su programa como constantes es útil para transmitir el significado de ese valor a los futuros mantenedores del código. También ayuda a tener solo un lugar en su código en el que necesitaría cambiar si el valor codificado tuviera que actualizarse en el futuro.
Shadowing
Como vio en el tutorial del juego de adivinanzas en Capítulo
2, puede declarar una
nueva variable con el mismo nombre que una variable anterior. Los Rustaceans
dicen que la primera variable es ocultada por la segunda, lo que significa
que la segunda variable es lo que el compilador verá cuando use el nombre de la
variable. En efecto, la segunda variable oculta la primera, tomando
cualquier uso del nombre de la variable para sí misma hasta que se haga
shadowing sobre la misma variable o el ámbito finalice.
Podemos ocultar una variable usando el mismo nombre de variable y repitiendo
el uso de la palabra clave let
de la siguiente manera:
Nombre de archivo: src/main.rs
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); } println!("The value of x is: {x}"); }
Este programa primero vincula a x
el valor de 5
. Luego crea una nueva
variable x
repitiendo let x =
, tomando el valor original y agregando 1
para que el valor de x
sea entonces 6
. Luego, dentro de un ámbito interno
creado con las llaves, la tercera declaración let
también proyecta x
y
crea una nueva variable, multiplicando el valor anterior por 2
para darle a
x
un valor de 12
. Cuando ese ámbito finaliza, la proyección interna finaliza
y x
vuelve a ser 6
. Cuando ejecutamos este programa, se mostrará lo
siguiente:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
El Shadowing es diferente de marcar una variable como mut
porque obtendremos
un error de tiempo de compilación si accidentalmente intentamos volver a
asignar esta variable sin usar la palabra clave let
. Al usar let
, podemos
realizar algunas transformaciones en un valor, pero la variable debe ser
inmutable después de que se hayan completado esas transformaciones.
La otra diferencia entre mut
y el shadowing es que, debido a que
efectivamente estamos creando una nueva variable cuando usamos la palabra clave
let
nuevamente, podemos cambiar el tipo de valor pero reutilizar el mismo
nombre. Por ejemplo, digamos que nuestro programa le pide al usuario que muestre
cuántos espacios desea entre algún texto ingresando caracteres de espacio, y
luego queremos almacenar esa entrada como un número:
fn main() { let spaces = " "; let spaces = spaces.len(); }
La primera variable spaces
es de tipo string y la segunda variable spaces
es de tipo numérico. El shadowing nos ahorra tener que pensar en nombres
diferentes, como spaces_str
y spaces_num
; en su lugar, podemos reutilizar
el nombre más simple spaces
. Sin embargo, si intentamos usar mut
para esto,
como se muestra aquí, obtendremos un error de tiempo de compilación:
fn main() {
let mut spaces = " ";
spaces = spaces.len();
}
El error dice que no se permite mutar el tipo de una variable:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error
Ahora que hemos explorado cómo funcionan las variables, veamos más tipos de datos que pueden tener.