atribute (#[derive], #[cfg], #[allow]) în Rust

Module, macro-uri, atribute și teste Rust 1.0 (#[expect]: 1.81)

Informații pentru compilator scrise cu #[...] deasupra unui element: generează cod (derive), aleg codul compilat (cfg) sau schimbă avertismentele.

Sintaxă
#[derive(Debug, Clone, PartialEq)]
struct S { ... }
#[cfg(target_os = "linux")]
fn doar_pe_linux() { }
#[allow(dead_code)]   #[expect(unused)]   #[must_use]
#![allow(...)]        // atribut interior: se aplică întregului fișier/modul

Ce face atribute (#[derive], #[cfg], #[allow])

Un atribut #[...] se aplică elementului de sub el (funcție, structură, câmp, modul, instrucțiune). Forma #![...], pusă la începutul unui fișier sau modul, se aplică întregului conținut. Cele mai folosite:

#[derive(...)] generează automat implementări de trait-uri: Debug (afișare cu {:?}), Clone, Copy, PartialEq/Eq (comparare cu ==), PartialOrd/Ord, Hash (chei în HashMap), Default. #[cfg(...)] compilează elementul doar dacă o condiție este adevărată: sistemul de operare (target_os = "windows"), modul test (test), modul debug (debug_assertions) sau o funcționalitate Cargo (feature = "x"). Macro-ul cfg! face aceeași verificare, dar ca valoare bool.

Atributele de lint schimbă avertismentele: #[allow(dead_code)] le ascunde, #[expect(dead_code)] le ascunde, dar avertizează dacă nu mai apar, #[deny(...)] le face erori. #[must_use] avertizează când rezultatul unei funcții este ignorat, #[test] marchează un test, #[inline] sugerează compilatorului să insereze funcția în locul apelului.

Exemple

Exemplul 1: derive: trait-uri generate automat

Fără nicio linie scrisă de mână, Nota poate fi afișată, clonată, comparată, sortată și folosită ca cheie. Default dă valori zero/goale. Compararea generată de derive se face câmp cu câmp, în ordinea declarării: întâi materie („Fizică” < „Mate”), apoi valoare; de aceea a < c este fals.

use std::collections::HashSet;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Nota {
    materie: String,
    valoare: u8,
}

fn main() {
    let a = Nota { materie: "Mate".to_string(), valoare: 9 };
    let b = a.clone();
    let c = Nota { materie: "Fizică".to_string(), valoare: 10 };
    println!("{a:?}");
    println!("a == b: {}, a < c: {}", a == b, a < c);

    let mut note = vec![a, c, b];
    note.sort();
    let unice: HashSet<Nota> = note.iter().cloned().collect();
    println!("sortate: {:?}", note.iter().map(|n| n.valoare).collect::<Vec<_>>());
    println!("unice: {}", unice.len());
    println!("implicit: {:?}", Nota::default());
}

Rezultat

Nota { materie: "Mate", valoare: 9 }
a == b: true, a < c: false
sortate: [10, 9, 9]
unice: 2
implicit: Nota { materie: "", valoare: 0 }

Exemplul 2: cfg: cod diferit pe sisteme diferite

Se compilează doar una dintre cele trei variante ale lui separator_cale. Exemplul a fost compilat și rulat pe macOS, care este un sistem Unix. debug_assertions este activ la compilarea fără optimizări (cargo build), dar nu și la cargo build --release.

#[cfg(windows)]
fn separator_cale() -> char {
    '\\'
}

#[cfg(unix)]
fn separator_cale() -> char {
    '/'
}

#[cfg(not(any(unix, windows)))]
fn separator_cale() -> char {
    '/'
}

fn main() {
    println!("Separatorul de cale: {}", separator_cale());
    #[cfg(debug_assertions)]
    println!("Compilat în modul debug");
    #[cfg(not(debug_assertions))]
    println!("Compilat în modul release");
}

Rezultat

Separatorul de cale: /
Compilat în modul debug

Exemplul 3: allow, expect și must_use

Fără #[allow(dead_code)], funcția nefolosită pentru_mai_tarziu ar produce un avertisment (aici, eroare). #[must_use] pe aplica_reducere avertizează dacă cineva o apelează și ignoră rezultatul.

#[allow(dead_code)]
fn pentru_mai_tarziu() {}

#[must_use = "funcția nu modifică prețul, returnează unul nou"]
fn aplica_reducere(pret: f64, procent: f64) -> f64 {
    pret * (1.0 - procent / 100.0)
}

fn main() {
    #[expect(unused_variables)]
    let neterminat = 42;

    let pret = aplica_reducere(250.0, 20.0);
    println!("Preț redus: {pret} lei");
}

Rezultat

Preț redus: 200 lei

Greșeli frecvente

  • Faci #[derive(PartialEq)] pe o structură care conține un câmp fără PartialEq: derive cere ca toate câmpurile să implementeze trait-ul.
  • Folosești #[derive(Copy)] fără Clone sau pe o structură cu String: Copy cere Clone și doar câmpuri Copy.
  • Pui #![allow(warnings)] la începutul proiectului ca să scapi de avertismente. Ascunzi astfel și probleme reale; dezactivează doar avertismentul exact, doar unde este nevoie, de preferat cu #[expect(...)].

Comenzi înrudite

  • struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.
  • teste (#[test]): O funcție marcată cu #[test] este un test automat: cargo test o rulează și raportează dacă a trecut sau a dat panică.
  • cfg!: Evaluează la compilare o condiție de configurare (sistem de operare, mod debug, feature Cargo) și o transformă în true sau false.
  • comentarii și documentație (//, ///, //!): // și /* */ sunt comentarii obișnuite; /// și //! scriu documentație Markdown, transformată în HTML de cargo doc și testată de cargo test.
  • trait: Definește un set de metode pe care un tip promite să le aibă. Seamănă cu interfețele din alte limbaje și stă la baza generics.