Ámbito en Rust
El ámbito (scope) es la zona del código en la que una variable es válida. Es una idea sencilla, pero entenderla hace que muchas otras reglas de Rust (sobre todo el ownership, la propiedad) encajen solas.
Las llaves definen el ámbito
Sección titulada «Las llaves definen el ámbito»Una variable existe desde el punto en que se declara hasta la } que cierra el bloque en el que está:
fn main() { let x = 5; // x entra en el ámbito aquí
{ let y = 10; // y entra en el ámbito aquí println!("x = {x}, y = {y}"); // las dos son válidas } // y sale del ámbito aquí
println!("x = {x}"); // println!("{y}"); // ❌ error: y ya no está en el ámbito}Salida:
x = 5, y = 10x = 5y solo existe dentro del bloque interior { }. En cuanto ese bloque termina, y desaparece: intentar usarla después es un error de compilación, no una sorpresa en tiempo de ejecución.
El ámbito solo funciona en una dirección: un bloque interior puede ver las variables de los bloques que lo rodean (por eso el bloque interior puede imprimir x), pero un bloque exterior no puede ver lo que hay dentro de uno interior.
El cuerpo de una función también es un ámbito
Sección titulada «El cuerpo de una función también es un ámbito»Los parámetros y cualquier variable que crees dentro de una función solo existen mientras dura esa llamada a la función:
fn calculate() -> i32 { let result = 5 * 2; // result solo existe dentro de calculate result}
fn main() { let value = calculate(); println!("{value}"); // println!("{result}"); // ❌ error: result no existe aquí fuera}Salida:
10Lo mismo pasa en sentido contrario: una función no puede ver las variables locales de main. Si necesita un valor, se lo pasas como parámetro, como viste en la página de Funciones.
Los bloques también son expresiones
Sección titulada «Los bloques también son expresiones»Un bloque { } no solo limita dónde viven las variables. También puede producir un valor: la última expresión de su interior, escrita sin punto y coma. Es la misma regla que hace que una función devuelva su última línea.
Esto es muy útil cuando necesitas algunas variables auxiliares para calcular algo, pero no quieres que se queden por ahí después:
fn main() { let price = 40;
let total = { let tax = price / 10; // tax solo vive dentro de este bloque let shipping = 5; price + tax + shipping // sin punto y coma: este es el valor del bloque };
println!("total = {total}"); // println!("{tax}"); // ❌ error: tax está fuera de su ámbito}Salida:
total = 49Dos detalles a tener en cuenta:
- La última línea,
price + tax + shipping, no lleva punto y coma. Si se lo pones, el bloque devuelve()(nada) en lugar de un número, yprintln!no puede imprimirlo. - Todo el conjunto es una instrucción
let, así que termina con};después de la llave de cierre.
Cuando el bloque termina, tax y shipping desaparecen. Solo queda el resultado, total. Guardar las variables auxiliares en un bloque pequeño como este deja claro que solo hacen falta para un cálculo.
Por qué el ámbito importa para el ownership
Sección titulada «Por qué el ámbito importa para el ownership»Esto conecta directamente con el ownership (propiedad), que conocerás un poco más adelante: un valor se limpia automáticamente en el momento en que termina el ámbito de su dueño.
fn main() { { let text = String::from("hola"); println!("{text}"); } // aquí termina el ámbito de text: Rust lo libera (drop) automáticamente}Salida:
holaEse es todo el mecanismo que permite a Rust funcionar sin recolector de basura (garbage collector): ya sabe, al compilar, exactamente dónde termina cada ámbito.
Shadowing y ámbito
Sección titulada «Shadowing y ámbito»¿Recuerdas el shadowing (sombreado) de la página de Variables? Cada let con el mismo nombre dentro de un ámbito crea una variable distinta: no borra la de fuera, solo ocupa su lugar durante el resto de ese ámbito:
fn main() { let x = 5; { let x = x * 2; // una x nueva, solo dentro de este bloque println!("x interior = {x}"); // 10 } println!("x exterior = {x}"); // 5: no ha cambiado}Salida:
x interior = 10x exterior = 5Práctica
Sección titulada «Práctica»1. Arregla el error de ámbito
Sección titulada «1. Arregla el error de ámbito»Este programa no compila, porque area se usa después de que su bloque haya terminado:
fn main() { let width = 3; { let height = 4; let area = width * height; } println!("área = {area}");}Cámbialo para que imprima área = 12, sin que height deje de vivir solo dentro del bloque. Pista: haz que el bloque produzca el área y guarda el resultado en una variable declarada fuera de él.
Ver solución
fn main() { let width = 3; let area = { let height = 4; width * height }; println!("área = {area}");}Salida:
área = 12Ahora area vive en el ámbito de main, así que println! puede verla. height sigue haciendo falta solo dentro del bloque, y se libera cuando el bloque termina.
2. Niveles de anidamiento
Sección titulada «2. Niveles de anidamiento»Crea una variable level con el valor 1. Abre un bloque que la sombree con 2 y, dentro de él, otro bloque que la sombree con 3. Imprime level en cada bloque, y otra vez justo después de que se cierre cada bloque. Pista: deberías ver los números 1, 2, 3, y luego bajar de nuevo a 2 y 1.
Ver solución
fn main() { let level = 1; println!("nivel {level}"); { let level = 2; println!("nivel {level}"); { let level = 3; println!("nivel {level}"); } println!("de vuelta al nivel {level}"); } println!("de vuelta al nivel {level}");}Salida:
nivel 1nivel 2nivel 3de vuelta al nivel 2de vuelta al nivel 1Cada level interior oculta al exterior solo hasta que termina su bloque. Las variables exteriores nunca cambiaron, así que siguen ahí, esperando.
Resumen
Sección titulada «Resumen»- El ámbito es la zona (marcada por
{ }) en la que una variable es válida. - Una variable deja de existir en el instante en que se llega a la
}que cierra su ámbito. - El cuerpo de una función también es un ámbito: sus variables locales desaparecen cuando la función termina.
- Un bloque es una expresión: su última línea (sin punto y coma) es su valor, y sus variables auxiliares desaparecen cuando termina.
- El ámbito es lo que le dice a Rust exactamente cuándo liberar un valor, y por eso no necesita recolector de basura.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. Según esta página, ¿por qué Rust no necesita un recolector de basura?
2. ¿Qué pasa cuando haces shadowing de una variable con let x = x * 2; dentro de un bloque interior?
3. ¿Cuál es el valor de total después de let total = { let a = 2; a * 10 };?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- Ámbito de las variables — El libro de Rust (en inglés): cómo funciona el ámbito, explicado como primer paso hacia el ownership.
- Ámbito y shadowing — Rust by Example (en inglés): un ejemplo corto y ejecutable de bloques y shadowing.
- RAII — Rust by Example (en inglés): qué les pasa a los valores cuando termina su ámbito, para cuando estés listo para el siguiente paso.
- Expresiones de bloque — The Rust Reference (en inglés): las reglas exactas de los bloques y de los valores que producen.