00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Costanti in Rust

In Rust esistono due modi principali per dichiarare valori che non cambiano durante l’esecuzione del programma: le costanti con const e le variabili statiche con static. Sebbene possano sembrare simili, hanno comportamenti e casi d’uso distinti. In questa guida esploreremo entrambi i meccanismi e le loro differenze.

Costanti con const

La parola chiave const dichiara un valore che viene calcolato a tempo di compilazione e non occupa una posizione fissa in memoria. Il compilatore può “inline” il valore ovunque venga utilizzato:

const PI_GRECO: f64 = 3.141592653589793;
const MAX_TENTATIVI: u32 = 5;
const NOME_APP: &str = "La Mia Applicazione";

fn main() {
    println!("Pi greco: {}", PI_GRECO);
    println!("Tentativi massimi: {}", MAX_TENTATIVI);
    println!("Nome: {}", NOME_APP);
}

Regole per le Costanti

Le costanti in Rust hanno alcune regole specifiche:

  1. L’annotazione del tipo è obbligatoria: Non è possibile omettere il tipo.
  2. Il valore deve essere un’espressione costante: Deve essere calcolabile a tempo di compilazione.
  3. Non è possibile usare mut: Le costanti sono sempre immutabili.
  4. Possono essere dichiarate in qualsiasi scope: Incluso lo scope globale.
const SECONDI_PER_ORA: u32 = 60 * 60;           // Espressione costante valida
const SECONDI_PER_GIORNO: u32 = SECONDI_PER_ORA * 24; // Usa un'altra costante

fn main() {
    println!("Secondi in un'ora: {}", SECONDI_PER_ORA);
    println!("Secondi in un giorno: {}", SECONDI_PER_GIORNO);

    // Le costanti possono essere dichiarate anche dentro una funzione
    const LIMITE_LOCALE: i32 = 100;
    println!("Limite: {}", LIMITE_LOCALE);
}

Cosa NON Può Essere una Costante

Le costanti non possono contenere valori che richiedono computazioni a runtime:

// Errori di compilazione:
// const TEMPO: std::time::Instant = std::time::Instant::now(); // Non è const
// const INPUT: String = String::new(); // L'allocazione non è disponibile a compile time

Variabili Statiche con static

La parola chiave static dichiara una variabile globale con una posizione fissa in memoria e un lifetime che dura per tutta l’esecuzione del programma ('static):

static VERSIONE: &str = "1.0.0";
static CONTATORE_GLOBALE: i32 = 0;

fn main() {
    println!("Versione: {}", VERSIONE);
    println!("Contatore: {}", CONTATORE_GLOBALE);
}

Variabili Statiche Mutabili

Le variabili statiche possono essere dichiarate come mutabili con static mut, ma l’accesso è considerato unsafe perché potrebbe causare data race in programmi concorrenti:

static mut CONTATORE: u32 = 0;

fn incrementa() {
    unsafe {
        CONTATORE += 1;
    }
}

fn main() {
    incrementa();
    incrementa();
    incrementa();

    unsafe {
        println!("Contatore: {}", CONTATORE); // Stampa: 3
    }
}

L’uso di static mut è sconsigliato nella programmazione Rust moderna. Per condividere stato mutabile globale, è preferibile utilizzare tipi thread-safe come Mutex o AtomicU32:

use std::sync::atomic::{AtomicU32, Ordering};

static CONTATORE_SICURO: AtomicU32 = AtomicU32::new(0);

fn incrementa() {
    CONTATORE_SICURO.fetch_add(1, Ordering::SeqCst);
}

fn main() {
    incrementa();
    incrementa();
    incrementa();

    println!("Contatore: {}", CONTATORE_SICURO.load(Ordering::SeqCst));
}

Differenze tra const e static

Ecco le differenze principali tra const e static:

Caratteristica const static
Posizione in memoria Può essere inlined Posizione fissa
Lifetime Nessun lifetime specifico 'static
Mutabilità Mai mutabile Può essere mut (unsafe)
Indirizzo di memoria Può variare ad ogni uso Sempre lo stesso
Uso tipico Valori costanti Dati globali condivisi
const COSTANTE: i32 = 42;
static STATICA: i32 = 42;

fn main() {
    // La costante potrebbe non avere un indirizzo fisso
    // La statica ha sempre lo stesso indirizzo
    let ref_statica1 = &STATICA;
    let ref_statica2 = &STATICA;

    // Stesso indirizzo: true
    println!("Stesso indirizzo: {}", std::ptr::eq(ref_statica1, ref_statica2));
}

Convenzione di Denominazione: SCREAMING_SNAKE_CASE

Sia le costanti che le variabili statiche seguono la convenzione SCREAMING_SNAKE_CASE (tutte maiuscole con underscore come separatore):

const TEMPERATURA_MASSIMA: f64 = 100.0;
const NUMERO_MASSIMO_UTENTI: u32 = 1_000_000;
const URL_BASE_API: &str = "https://api.esempio.com";

static CONFIGURAZIONE_GLOBALE: &str = "produzione";
static VERSIONE_PROTOCOLLO: u8 = 2;

fn main() {
    println!("Temperatura massima: {}°C", TEMPERATURA_MASSIMA);
    println!("Max utenti: {}", NUMERO_MASSIMO_UTENTI);
    println!("Configurazione: {}", CONFIGURAZIONE_GLOBALE);
}

Il compilatore Rust emette un warning se non si rispetta questa convenzione:

// warning: constant `maxValore` should have an upper case name
// const maxValore: i32 = 100; // Non idiomatico!
const MAX_VALORE: i32 = 100;   // Corretto

Espressioni Costanti

Rust supporta un insieme crescente di operazioni nelle espressioni costanti. Con l’edizione 2024, molte più funzioni possono essere marcate come const fn e utilizzate nelle costanti:

const fn fattoriale(n: u64) -> u64 {
    if n == 0 {
        1
    } else {
        n * fattoriale(n - 1)
    }
}

const FATT_10: u64 = fattoriale(10);

const fn massimo(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}

const VALORE_MAX: i32 = massimo(42, 17);

fn main() {
    println!("10! = {}", FATT_10);       // Stampa: 10! = 3628800
    println!("Max = {}", VALORE_MAX);    // Stampa: Max = 42
}

Le const fn sono funzioni che possono essere valutate a tempo di compilazione, permettendo di creare espressioni costanti complesse in modo leggibile e manutenibile.

Conclusione

Le costanti e le variabili statiche sono strumenti fondamentali per gestire valori fissi e dati globali in Rust. Le costanti con const sono ideali per valori noti a tempo di compilazione che vengono utilizzati frequentemente, mentre static è appropriato quando serve un indirizzo di memoria fisso o uno stato globale. La convenzione SCREAMING_SNAKE_CASE e l’obbligo dell’annotazione del tipo rendono il codice Rust esplicito e facilmente leggibile, contribuendo alla cultura di chiarezza e sicurezza che caratterizza il linguaggio.