Entry::or_insert_with în Rust

Ca or_insert, dar valoarea de inserat e calculată de un closure, apelat doar dacă cheia lipsește.

Sintaxă
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V

Ce face Entry::or_insert_with

or_insert_with(|| calcul) apelează closure-ul numai când cheia nu există. Este varianta potrivită când valoarea implicită costă: o alocare, citirea unui fișier, un calcul lung, sau când are efecte (un contor, un mesaj).

Ca și or_insert, returnează &mut V la valoarea din dicționar. Dacă valoarea depinde de cheie, or_insert_with_key(|k| ...) primește cheia ca parametru. Pentru valoarea implicită a tipului (0, Vec::new(), String::new()), cel mai scurt este or_default().

Parametri

Parametru Descriere
default Closure fără parametri care produce valoarea (FnOnce() -> V).

Ce returnează

&mut V.

Exemple

Exemplul 1: or_insert calculează mereu, or_insert_with doar la nevoie

Pentru trei apeluri cu două chei distincte, or_insert apelează funcția de 3 ori, iar or_insert_with de 2 ori.

use std::collections::HashMap;

fn valoare_initiala() -> u32 {
    println!("  (calculez valoarea inițială)");
    0
}

fn main() {
    let chei = ["Ana", "Mihai", "Ana"];

    println!("or_insert:");
    let mut a: HashMap<&str, u32> = HashMap::new();
    for k in chei {
        *a.entry(k).or_insert(valoare_initiala()) += 1;
    }

    println!("or_insert_with:");
    let mut b: HashMap<&str, u32> = HashMap::new();
    for k in chei {
        *b.entry(k).or_insert_with(valoare_initiala) += 1;
    }
    println!("Ana: {} și {}", a["Ana"], b["Ana"]);
}

Rezultat

or_insert:
  (calculez valoarea inițială)
  (calculez valoarea inițială)
  (calculez valoarea inițială)
or_insert_with:
  (calculez valoarea inițială)
  (calculez valoarea inițială)
Ana: 2 și 2

Exemplul 2: Cache: fiecare număr este verificat o singură dată

Rezultatul lui este_prim este păstrat în dicționar. Cererile repetate nu mai calculează nimic.

use std::collections::HashMap;

fn este_prim(n: u64) -> bool {
    n >= 2 && (2..).take_while(|d| d * d <= n).all(|d| n % d != 0)
}

fn main() {
    let mut cache: HashMap<u64, bool> = HashMap::new();
    let mut calcule = 0;
    for n in [97, 100, 97, 7919, 100, 97] {
        let prim = *cache.entry(n).or_insert_with(|| {
            calcule += 1;
            este_prim(n)
        });
        println!("{n}: {}", if prim { "prim" } else { "compus" });
    }
    println!("calcule făcute: {calcule} pentru 6 cereri");
}

Rezultat

97: prim
100: compus
97: prim
7919: prim
100: compus
97: prim
calcule făcute: 3 pentru 6 cereri

Exemplul 3: or_insert_with_key: valoarea depinde de cheie

use std::collections::HashMap;

fn main() {
    let mut lungimi: HashMap<&str, usize> = HashMap::new();
    for oras in ["Iași", "Cluj", "Iași", "Timișoara"] {
        let n = lungimi.entry(oras).or_insert_with_key(|k| k.chars().count());
        println!("{oras}: {n} litere");
    }
}

Rezultat

Iași: 4 litere
Cluj: 4 litere
Iași: 4 litere
Timișoara: 9 litere

Greșeli frecvente

  • Scrii or_insert_with(Vec::new()): aceea este o valoare, nu un closure (expected an `FnOnce()` closure). Scrie or_insert_with(Vec::new) (fără paranteze) sau or_insert_with(|| Vec::new()); pentru acest caz, or_default().
  • Folosești or_insert_with pentru o constantă simplă (|| 0). Merge, dar or_insert(0) sau or_default() sunt mai clare.

Comenzi înrudite

  • Entry::or_insert: Dacă cheia lipsește, inserează valoarea dată; în ambele cazuri returnează o referință mutabilă la valoarea din dicționar.
  • Entry::or_default: Dacă cheia lipsește, inserează valoarea implicită a tipului (0, vector gol, text gol) și returnează &mut V.
  • HashMap::entry: Caută o cheie o singură dată și îți dă „locul” ei (Entry), ca să inserezi, să citești sau să modifici fără o a doua căutare.
  • Fn, FnMut și FnOnce: Cele trei trait-uri ale closure-urilor: Fn doar citește ce a capturat, FnMut modifică, FnOnce consumă și poate fi apelat o singură dată.