ProgramaciónDesarrollador de Rust Sistémico

¿Cómo funciona la evaluación constante (const evaluation) en Rust? ¿En qué se diferencia const de static y let, en qué casos se puede (y se debe) usar const fn, y cuáles son las limitaciones al escribir cálculos en tiempo de compilación?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

La evaluación constante en Rust permite llevar a cabo parte de los cálculos o la inicialización en tiempo de compilación, en lugar de en tiempo de ejecución del programa.

  • const declara una constante inmutable, calculada en tiempo de compilación y que no tiene dirección en memoria:
const PI: f64 = 3.1415;
  • static declara una variable global, ubicada en un segmento de memoria específico (generalmente .data o .bss), puede ser mutable (requiere unsafe):
static mut GLOBAL_COUNTER: i32 = 0;
  • let se usa para variables en la pila, su valor puede calcularse en runtime, y deben inicializarse antes de su primer uso.

const fn es una función cuyo resultado puede ser utilizado para asignar un valor a const o static. Estas funciones pueden ser llamadas en un contexto constante.

const fn factorial(n: usize) -> usize { if n == 0 { 1 } else { n * factorial(n - 1) } } const FACT_5: usize = factorial(5); // ¡Se compila!

Limitaciones de const fn:

  • Solo se pueden usar otras const fn,
  • No se puede usar asignación en heap (Box::new y otros),
  • No se pueden realizar operaciones inseguras,
  • No hay acceso a variables externas y funciones (a menos que sean const fn).

Pregunta capciosa.

Pregunta: ¿Se puede usar cualquier función en un contexto constant, si su resultado no cambia? Por ejemplo, ¿así?

fn add(a: i32, b: i32) -> i32 { a + b } const RES: i32 = add(1, 2);

Respuesta incorrecta típica: Sí, ya que la función es pura y el resultado se conoce de antemano.

Respuesta correcta: No, la función debe estar declarada explícitamente como const fn, solo entonces se puede llamar dentro de una inicialización const. Las funciones normales solo se llaman en tiempo de ejecución.

Ejemplo:

const fn add(a: i32, b: i32) -> i32 { a + b } const RES: i32 = add(1, 2); // ¡Se compila!

Ejemplos de errores reales debido al desconocimiento de los matices del tema.


Historia

En un proyecto de cálculos de geometría tridimensional, un desarrollador intentó declarar una tabla de valores a través del resultado de una llamada a una función normal, y no a const fn. Como resultado, aparecieron errores de compilación, y se perdió la ventaja de los cálculos en tiempo de compilación.


Historia

El uso de static mut para un caché global condujo a una carrera de datos al acceder desde múltiples threads (static mut no es seguro!). Se debía usar Atomic o Mutex para sincronizar el acceso al recurso global.


Historia

En un intento de acelerar la inicialización de grandes arreglos, se definieron como static, pero olvidaron que static siempre tiene una dirección fija, por lo que los datos no caían en la caché del procesador como locales, y las operaciones se ralentizaban en el camino caliente de la lógica del servidor. Se debió usar expresiones let locales con cálculos en runtime.