diff --git a/corelib/src/dict.cairo b/corelib/src/dict.cairo index 119884c356e..57212d92139 100644 --- a/corelib/src/dict.cairo +++ b/corelib/src/dict.cairo @@ -1,10 +1,68 @@ +//! A dictionary-like data structure that maps `felt252` keys to values of any type. +//! +//! The `Felt252Dict` provides efficient key-value storage with operations for inserting, +//! retrieving, and updating values. Each operation creates a new entry that can be validated +//! through a process called squashing. +//! +//! # Examples +//! +//! One can create a new dictionary using the [`Default::default`] method: +//! +//! ``` +//! use core::dict::Felt252Dict; +//! +//! let mut dict: Felt252Dict = Default::default(); +//! ``` +//! +//! ... then insert new values corresponding to a given key with the [`Felt252DictTrait::insert`] +//! method, and retrieve any value given a key with the [`Felt252DictTrait::get`] method. +//! +//! ``` +//! dict.insert(0, 10); +//! dict.insert(1, 20); +//! assert!(dict.get(0) == 10); +//! assert!(dict.get(1) == 20); +//! +//! dict.insert(0, 20); +//! assert!(dict.get(0) == 20 +//! ``` +//! +//! It is also possible to use the [`Felt252DictTrait::entry`] method to retrieve the last entry +//! given a certain key. +//! In this case, the method takes ownership of the dictionary and returns the entry to update. +//! After that, using the [`Felt252DictEntryTrait::finalize`] allows to create a new entry in the +//! dictionary. +//! Using `entry` and `finalize` methods can be very useful given that it does not require the type +//! in the dictionary to be copyable, meaning that we can use non-copyable types like arrays as +//! dictionary values. +//! +//! ``` +//! use core::dict::Felt252Dict; +//! +//! let mut dict: Felt252Dict = Default::default(); +//! dict.insert(0, 10); +//! +//! let (entry, prev_value) = dict.entry(0); +//! let new_value: u8 = 20; +//! dict = entry.finalize(new_value); +//! ``` + #[feature("deprecated-index-traits")] use crate::traits::{Index, Default, Felt252DictValue}; +/// A dictionary that maps `felt252` keys to a value of any type. pub extern type Felt252Dict; + +/// A dictionary in a squashed state. It cannot be mutated anymore. pub extern type SquashedFelt252Dict; + +/// An intermediate type that is returned after calling the `entry` method that consumes ownership +/// of the dictionary. This ensures that the dictionary cannot be mutated until the entry is +/// finalized, which restores ownership of the dictionary. pub extern type Felt252DictEntry; + impl SquashedFelt252DictDrop> of Drop>; + use crate::{RangeCheck, SegmentArena}; use crate::gas::GasBuiltin; @@ -18,28 +76,75 @@ extern fn felt252_dict_entry_finalize( dict_entry: Felt252DictEntry, new_value: T, ) -> Felt252Dict nopanic; -/// Squashes the dictionary and returns SquashedFelt252Dict. -/// -/// NOTE: Never use this libfunc directly. Use Felt252DictTrait::squash() instead. Using this -/// libfunc directly will result in multiple unnecessary copies of the libfunc in the compiled CASM -/// code. +// Squashes the dictionary and returns a `SquashedFelt252Dict`. +// +// NOTE: Never use this libfunc directly. Use Felt252DictTrait::squash() instead. Using this +// libfunc directly will result in multiple unnecessary copies of the libfunc in the compiled CASM +// code. pub(crate) extern fn felt252_dict_squash( dict: Felt252Dict, ) -> SquashedFelt252Dict implicits(RangeCheck, GasBuiltin, SegmentArena) nopanic; +/// Basic trait for the `Felt252Dict` type. pub trait Felt252DictTrait { /// Inserts the given value for the given key. /// - /// Requires the `Destruct` trait, as the previous value is dropped. + /// # Examples + /// + /// ``` + /// use core::dict::Felt252Dict; + /// + /// let mut dict: Felt252Dict = Default::default(); + /// dict.insert(0, 10); + /// ``` fn insert<+Destruct>(ref self: Felt252Dict, key: felt252, value: T); - /// Returns a copy of the value at the given key. + + /// Returns the value stored at the given key. If no value was previously inserted at this key, + /// returns the default value for type T. /// - /// Requires the `Copy` trait. + /// # Examples + /// + /// ``` + /// use core::dict::Felt252Dict; + /// + /// let mut dict: Felt252Dict = Default::default(); + /// dict.insert(0, 10); + /// let value = dict.get(0); + /// assert!(value == 10); + /// ``` fn get<+Copy>(ref self: Felt252Dict, key: felt252) -> T; + + /// Squashes a dictionary and returns the associated `SquashedFelt252Dict`. + /// + /// # Examples + /// + /// ``` + /// use core::dict::Felt252Dict; + /// + /// let mut dict: Felt252Dict = Default::default(); + /// dict.insert(0, 10); + /// let squashed_dict = dict.squash(); + /// ``` fn squash(self: Felt252Dict) -> SquashedFelt252Dict nopanic; + + /// Retrieves the last entry for a certain key. + /// This method takes ownership of the dictionary and returns the entry to update, + /// as well as the previous value at the given key. + /// + /// # Examples + /// + /// ``` + /// use core::dict::Felt252Dict; + /// + /// let mut dict: Felt252Dict = Default::default(); + /// dict.insert(0, 10); + /// let (entry, prev_value) = dict.entry(0); + /// assert!(prev_value == 10); + /// ``` #[must_use] fn entry(self: Felt252Dict, key: felt252) -> (Felt252DictEntry, T) nopanic; } + impl Felt252DictImpl> of Felt252DictTrait { #[inline] fn insert<+Destruct>(ref self: Felt252Dict, key: felt252, value: T) { @@ -66,7 +171,28 @@ impl Felt252DictImpl> of Felt252DictTrait { } } +/// Basic trait for the `Felt252DictEntryTrait` type. pub trait Felt252DictEntryTrait { + /// Finalizes the changes made to a dictionary entry and gives back the ownership of the + /// dictionary. + /// + /// # Examples + /// + /// ``` + /// use core::dict::Felt252DictEntryTrait; + /// + /// // Create a dictionary that stores arrays + /// let mut dict: Felt252Dict>> = Default::default(); + /// + /// let a = array![1, 2, 3]; + /// dict.insert(0, NullableTrait::new(a)); + /// + /// let (entry, prev_value) = dict.entry(0); + /// let new_value = NullableTrait::new(array![4, 5, 6]); + /// dict = entry.finalize(new_value); + /// assert!(prev_value == a); + /// assert!(dict.get(0) == new_value); + /// ``` fn finalize(self: Felt252DictEntry, new_value: T) -> Felt252Dict; } @@ -78,6 +204,15 @@ impl Felt252DictEntryImpl> of Felt252DictEntryTrait { } impl Felt252DictDefault of Default> { + /// Returns a new empty dictionary. + /// + /// # Examples + /// + /// ``` + /// use core::dict::Felt252Dict; + /// + /// let dict: Felt252Dict = Default::default(); + /// ``` #[inline] fn default() -> Felt252Dict { felt252_dict_new() @@ -85,6 +220,12 @@ impl Felt252DictDefault of Default> { } impl Felt252DictDestruct, +Felt252DictValue> of Destruct> { + /// Allows the dictionary to go out of scope safely by ensuring it is squashed before going out + /// of scope. + /// A `Felt252Dict` cannot be "dropped" trivially because we need to ensure it is squashed + /// before the end of a program for soundness purposes. As such, `destruct` squashes the + /// dictionary, and the returned `SquashedFelt252Dict` is dropped trivially. + /// `destruct` is automatically called when a dictionary goes out of scope. #[inline] fn destruct(self: Felt252Dict) nopanic { self.squash(); @@ -92,15 +233,33 @@ impl Felt252DictDestruct, +Felt252DictValue> of Destruct, +Felt252DictValue> of Destruct> { + /// Allows the `Felt252DictEntry` to go out of scope safely by ensuring the dictionary it is + /// related to is squashed before going out of scope. + /// `destruct` is automatically called when a dictionary entry goes out of scope. #[inline] fn destruct(self: Felt252DictEntry::) nopanic { felt252_dict_entry_finalize(self, Felt252DictValue::zero_default()); } } +/// Implementation of the `Index` trait for `Felt252Dict`. +/// Allows accessing dictionary elements using the index operator `[]`. impl Felt252DictIndex< T, +Felt252DictTrait, +Copy, +Destruct>, > of Index, felt252, T> { + /// Takes a `felt252` index and returns the corresponding value. + /// + /// # Examples + /// + /// ``` + /// use core::dict::Felt252Dict; + /// + /// let mut dict: Felt252Dict = Default::default(); + /// dict.insert(0, 10); + /// + /// let value = dict[0]; + /// assert!(value == 10); + /// ``` #[inline] fn index(ref self: Felt252Dict, index: felt252) -> T { self.get(index)