ProgrammazioneSviluppatore Rust di sistema

Come è realizzata la valutazione delle costanti (const evaluation) in Rust? Qual è la differenza tra const, static e let, in quali casi è possibile (e necessario) utilizzare const fn, e quali sono le limitazioni nella scrittura di calcoli durante la fase di compilazione?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

La valutazione delle costanti in Rust consente di eseguire alcune operazioni di calcolo o inizializzazione durante la fase di compilazione, anziché durante l'esecuzione del programma.

  • const dichiara una costante immutabile, calcolata durante la fase di compilazione e priva di indirizzo in memoria:
const PI: f64 = 3.1415;
  • static dichiara una variabile globale, situata in un segmento di memoria specifico (solitamente .data o .bss), la mutabilità è possibile (richiede unsafe):
static mut GLOBAL_COUNTER: i32 = 0;
  • let è usato per variabili nello stack, il loro valore può essere calcolato a runtime, e devono essere inizializzate prima del loro primo utilizzo.

const fn è una funzione il cui risultato può essere utilizzato per impostare il valore di const o static. Queste funzioni possono essere chiamate in un contesto costante.

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

Limitazioni di const fn:

  • Possono essere utilizzate solo altre const fn,
  • Non è possibile utilizzare allocazione heap (Box::new e simili),
  • Non è possibile chiamare operazioni non sicure,
  • Non è possibile accedere a variabili e funzioni esterne (se non sono const fn).

Domanda ingannevole.

Domanda: È possibile utilizzare qualsiasi funzione in un contesto const, se il suo risultato non cambia? Ad esempio, in questo modo:

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

Risposta tipica errata: Sì, poiché la funzione è pura e il risultato è noto in anticipo.

Risposta corretta: No, la funzione deve essere esplicitamente dichiarata come const fn, solo allora può essere chiamata all'interno dell'inizializzazione const. Le funzioni normali vengono chiamate solo durante l'esecuzione!

Esempio:

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

Esempi di errori reali dovuti alla mancanza di conoscenza delle complessità dell'argomento.


Storia

In un progetto di calcoli di geometria tridimensionale, uno sviluppatore ha tentato di dichiarare una tabella di valori tramite il risultato di una chiamata a una funzione normale, invece che a una const fn. Di conseguenza, sono emersi errori di compilazione e il vantaggio del calcolo a compile-time è stato perso.


Storia

L'uso di static mut per una cache globale ha portato a condizioni di gara durante l'accesso da più thread (static mut non è sicuro!). Era necessario utilizzare Atomic o Mutex per sincronizzare l'accesso alla risorsa globale.


Storia

Nel tentativo di accelerare l'inizializzazione di grandi array, sono stati definiti come statici, ma si è dimenticato che static ha sempre un indirizzo fisso, il che ha portato a una mancata memorizzazione nella cache della CPU come locale, rallentando le operazioni sul percorso caldo della logica del server. Era necessario utilizzare espressioni let locali con calcoli a runtime.