Skip to content

Commit

Permalink
Core Lib Documentation: Dict module (#6753)
Browse files Browse the repository at this point in the history
Co-authored-by: Mathieu <[email protected]>
Co-authored-by: enitrat <[email protected]>
  • Loading branch information
3 people authored Nov 28, 2024
1 parent 0d9de39 commit a55979d
Showing 1 changed file with 167 additions and 8 deletions.
175 changes: 167 additions & 8 deletions corelib/src/dict.cairo
Original file line number Diff line number Diff line change
@@ -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<u8> = 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<u8> = 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<T>;

/// A dictionary in a squashed state. It cannot be mutated anymore.
pub extern type SquashedFelt252Dict<T>;

/// 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<T>;

impl SquashedFelt252DictDrop<T, +Drop<T>> of Drop<SquashedFelt252Dict<T>>;

use crate::{RangeCheck, SegmentArena};
use crate::gas::GasBuiltin;

Expand All @@ -18,28 +76,75 @@ extern fn felt252_dict_entry_finalize<T>(
dict_entry: Felt252DictEntry<T>, new_value: T,
) -> Felt252Dict<T> 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<T>(
dict: Felt252Dict<T>,
) -> SquashedFelt252Dict<T> implicits(RangeCheck, GasBuiltin, SegmentArena) nopanic;

/// Basic trait for the `Felt252Dict` type.
pub trait Felt252DictTrait<T> {
/// 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<u8> = Default::default();
/// dict.insert(0, 10);
/// ```
fn insert<+Destruct<T>>(ref self: Felt252Dict<T>, 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<u8> = Default::default();
/// dict.insert(0, 10);
/// let value = dict.get(0);
/// assert!(value == 10);
/// ```
fn get<+Copy<T>>(ref self: Felt252Dict<T>, key: felt252) -> T;

/// Squashes a dictionary and returns the associated `SquashedFelt252Dict`.
///
/// # Examples
///
/// ```
/// use core::dict::Felt252Dict;
///
/// let mut dict: Felt252Dict<u8> = Default::default();
/// dict.insert(0, 10);
/// let squashed_dict = dict.squash();
/// ```
fn squash(self: Felt252Dict<T>) -> SquashedFelt252Dict<T> 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<u8> = Default::default();
/// dict.insert(0, 10);
/// let (entry, prev_value) = dict.entry(0);
/// assert!(prev_value == 10);
/// ```
#[must_use]
fn entry(self: Felt252Dict<T>, key: felt252) -> (Felt252DictEntry<T>, T) nopanic;
}

impl Felt252DictImpl<T, +Felt252DictValue<T>> of Felt252DictTrait<T> {
#[inline]
fn insert<+Destruct<T>>(ref self: Felt252Dict<T>, key: felt252, value: T) {
Expand All @@ -66,7 +171,28 @@ impl Felt252DictImpl<T, +Felt252DictValue<T>> of Felt252DictTrait<T> {
}
}

/// Basic trait for the `Felt252DictEntryTrait` type.
pub trait Felt252DictEntryTrait<T> {
/// 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<Nullable<Array<felt252>>> = 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<T>, new_value: T) -> Felt252Dict<T>;
}

Expand All @@ -78,29 +204,62 @@ impl Felt252DictEntryImpl<T, +Felt252DictValue<T>> of Felt252DictEntryTrait<T> {
}

impl Felt252DictDefault<T> of Default<Felt252Dict<T>> {
/// Returns a new empty dictionary.
///
/// # Examples
///
/// ```
/// use core::dict::Felt252Dict;
///
/// let dict: Felt252Dict<u8> = Default::default();
/// ```
#[inline]
fn default() -> Felt252Dict<T> {
felt252_dict_new()
}
}

impl Felt252DictDestruct<T, +Drop<T>, +Felt252DictValue<T>> of Destruct<Felt252Dict<T>> {
/// 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<T>) nopanic {
self.squash();
}
}

impl Felt252DictEntryDestruct<T, +Drop<T>, +Felt252DictValue<T>> of Destruct<Felt252DictEntry<T>> {
/// 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::<T>) nopanic {
felt252_dict_entry_finalize(self, Felt252DictValue::zero_default());
}
}

/// Implementation of the `Index` trait for `Felt252Dict<T>`.
/// Allows accessing dictionary elements using the index operator `[]`.
impl Felt252DictIndex<
T, +Felt252DictTrait<T>, +Copy<T>, +Destruct<Felt252DictEntry<T>>,
> of Index<Felt252Dict<T>, felt252, T> {
/// Takes a `felt252` index and returns the corresponding value.
///
/// # Examples
///
/// ```
/// use core::dict::Felt252Dict;
///
/// let mut dict: Felt252Dict<u8> = Default::default();
/// dict.insert(0, 10);
///
/// let value = dict[0];
/// assert!(value == 10);
/// ```
#[inline]
fn index(ref self: Felt252Dict<T>, index: felt252) -> T {
self.get(index)
Expand Down

0 comments on commit a55979d

Please sign in to comment.