#
pezkuwi_sdk_docs/reference_docs/
trait_based_programming.rs

    1//! # Trait-based Programming
    2//!
    3//! This document walks you over a peculiar way of using Rust's `trait` items. This pattern is
    4//! abundantly used within [`frame`] and is therefore paramount important for a smooth transition
    5//! into it.
    6//!
    7//! The rest of this document assumes familiarity with the
    8//! [Rust book's Advanced Traits](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html)
    9//! section.
    10//! Moreover, we use the [`pezframe::traits::Get`].
    11//!
    12//! First, imagine we are writing a FRAME pezpallet. We represent this pezpallet with a `struct
    13//! Pezpallet`, and this pezpallet wants to implement the functionalities of that pezpallet, for
    14//! example a simple `transfer` function. For the sake of education, we are interested in having a
    15//! `MinTransfer` amount, expressed as a [`pezframe::traits::Get`], which will dictate what is the
    16//! minimum amount that can be transferred.
    17//!
    18//! We can foremost write this as simple as the following snippet:
    19#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", basic)]
    20//!
    21//!
    22//! In this example, we use arbitrary choices for `AccountId`, `Balance` and the `MinTransfer` type.
    23//! This works great for **one team's purposes** but we have to remember that Bizinikiwi and FRAME
    24//! are written as generic frameworks, intended to be highly configurable.
    25//!
    26//! In a broad sense, there are two avenues in exposing configurability:
    27//!
    28//! 1. For *values* that need to be generic, for example `MinTransfer`, we attach them to the
    29//!    `Pezpallet` struct as fields:
    30//!
    31//! ```
    32//! struct Pezpallet {
    33//! 	min_transfer: u128,
    34//! }
    35//! ```
    36//!
    37//! 2. For *types* that need to be generic, we would have to use generic or associated types, such
    38//!    as:
    39//!
    40//! ```
    41//! struct Pezpallet<AccountId> {
    42//! 	min_transfer: u128,
    43//!     _marker: std::marker::PhantomData<AccountId>,
    44//! }
    45//! ```
    46//!
    47//! Bizinikiwi and FRAME, for various reasons (performance, correctness, type safety) has opted to
    48//! use *types* to declare both *values* and *types* as generic. This is the essence of why the
    49//! `Get` trait exists.
    50//!
    51//! This would bring us to the second iteration of the pezpallet, which would look like:
    52#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", generic)]
    53//!
    54//! In this example, we managed to make all 3 of our types generic. Taking the example of the
    55//! `AccountId`, one should read the above as following:
    56//!
    57//! > The `Pezpallet` does not know what type `AccountId` concretely is, but it knows that it is
    58//! > something that adheres to being `From<[u8; 32]>`.
    59//!
    60//! This method would work, but it suffers from two downsides:
    61//!
    62//! 1. It is verbose, each `impl` block would have to reiterate all of the trait bounds.
    63//! 2. It cannot easily share/inherit generic types. Imagine multiple pallets wanting to be generic
    64//!    over a single `AccountId`. There is no easy way to express that in this model.
    65//!
    66//! Finally, this brings us to using traits and associated types on traits to express the above.
    67//! Trait associated types have the benefit of:
    68//!
    69//! 1. Being less verbose, as in effect they can *group multiple `type`s together*.
    70//! 2. Can inherit from one another by declaring
    71//! [supertraits](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html).
    72//!
    73//! > Interestingly, one downside of associated types is that declaring defaults on them is not
    74//! > stable yet. In the meantime, we have built our own custom mechanics around declaring defaults
    75//! for associated types, see [`pezpallet_default_config_example`].
    76//!
    77//! The last iteration of our code would look like this:
    78#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", trait_based)]
    79//!
    80//! Notice how instead of having multiple generics, everything is generic over a single `<T:
    81//! Config>`, and all types are fetched through `T`, for example `T::AccountId`, `T::MinTransfer`.
    82//!
    83//! Finally, imagine all pallets wanting to be generic over `AccountId`. This can be achieved by
    84//! having individual `trait Configs` declare a shared `trait SystemConfig` as their
    85//! [supertrait](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html).
    86#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", with_system)]
    87//! In FRAME, this shared supertrait is [`pezframe::prelude::pezframe_system`].
    88//!
    89//! Notice how this made no difference in the syntax of the rest of the code. `T::AccountId` is
    90//! still a valid type, since `T` implements `Config` and `Config` implies `SystemConfig`, which
    91//! has a `type AccountId`.
    92//!
    93//! Note, in some instances one would need to use what is known as the fully-qualified-syntax to
    94//! access a type to help the Rust compiler disambiguate.
    95#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", fully_qualified)]
    96//!
    97//! This syntax can sometimes become more complicated when you are dealing with nested traits.
    98//! Consider the following example, in which we fetch the `type Balance` from another trait
    99//! `CurrencyTrait`.
    100#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", fully_qualified_complicated)]
    101//!
    102//! Notice the final `type BalanceOf` and how it is defined. Using such aliases to shorten the
    103//! length of fully qualified syntax is a common pattern in FRAME.
    104//!
    105//! The above example is almost identical to the well-known (and somewhat notorious) `type
    106//! BalanceOf` that is often used in the context of [`pezframe::traits::fungible`].
    107#![doc = docify::embed!("../../bizinikiwi/pezframe/fast-unstake/src/types.rs", BalanceOf)]
    108//!
    109//! ## Additional Resources
    110//!
    111//! - <https://github.com/pezkuwichain/pezkuwi-sdk/issues/326>
    112//! - [Bizinikiwi Seminar - Traits and Generic Types](https://www.youtube.com/watch?v=6cp10jVWNl4)
    113//! - <https://exchange.pezkuwichain.app/questions/2228/type-casting-to-trait-t-as-config>
    114//!
    115//! [`frame`]: crate::pezkuwi_sdk::frame_runtime
    116#![allow(unused)]
    117
    118use pezframe::traits::Get;
    119
    120#[docify::export]
    121mod basic {
    122	struct Pezpallet;
    123
    124	type AccountId = pezframe::deps::pezsp_runtime::AccountId32;
    125	type Balance = u128;
    126	type MinTransfer = pezframe::traits::ConstU128<10>;
    127
    128	impl Pezpallet {
    129		fn transfer(_from: AccountId, _to: AccountId, _amount: Balance) {
    130			todo!()
    131		}
    132	}
    133}
    134
    135#[docify::export]
    136mod generic {
    137	use super::*;
    138
    139	struct Pezpallet<AccountId, Balance, MinTransfer> {
    140		_marker: std::marker::PhantomData<(AccountId, Balance, MinTransfer)>,
    141	}
    142
    143	impl<AccountId, Balance, MinTransfer> Pezpallet<AccountId, Balance, MinTransfer>
    144	where
    145		Balance: pezframe::traits::AtLeast32BitUnsigned,
    146		MinTransfer: pezframe::traits::Get<Balance>,
    147		AccountId: From<[u8; 32]>,
    148	{
    149		fn transfer(_from: AccountId, _to: AccountId, amount: Balance) {
    150			assert!(amount >= MinTransfer::get());
    151			unimplemented!();
    152		}
    153	}
    154}
    155
    156#[docify::export]
    157mod trait_based {
    158	use super::*;
    159
    160	trait Config {
    161		type AccountId: From<[u8; 32]>;
    162		type Balance: pezframe::traits::AtLeast32BitUnsigned;
    163		type MinTransfer: pezframe::traits::Get<Self::Balance>;
    164	}
    165
    166	struct Pezpallet<T: Config>(std::marker::PhantomData<T>);
    167	impl<T: Config> Pezpallet<T> {
    168		fn transfer(_from: T::AccountId, _to: T::AccountId, amount: T::Balance) {
    169			assert!(amount >= T::MinTransfer::get());
    170			unimplemented!();
    171		}
    172	}
    173}
    174
    175#[docify::export]
    176mod with_system {
    177	use super::*;
    178
    179	pub trait SystemConfig {
    180		type AccountId: From<[u8; 32]>;
    181	}
    182
    183	pub trait Config: SystemConfig {
    184		type Balance: pezframe::traits::AtLeast32BitUnsigned;
    185		type MinTransfer: pezframe::traits::Get<Self::Balance>;
    186	}
    187
    188	pub struct Pezpallet<T: Config>(std::marker::PhantomData<T>);
    189	impl<T: Config> Pezpallet<T> {
    190		fn transfer(_from: T::AccountId, _to: T::AccountId, amount: T::Balance) {
    191			assert!(amount >= T::MinTransfer::get());
    192			unimplemented!();
    193		}
    194	}
    195}
    196
    197#[docify::export]
    198mod fully_qualified {
    199	use super::with_system::*;
    200
    201	// Example of using fully qualified syntax.
    202	type AccountIdOf<T> = <T as SystemConfig>::AccountId;
    203}
    204
    205#[docify::export]
    206mod fully_qualified_complicated {
    207	use super::with_system::*;
    208
    209	trait CurrencyTrait {
    210		type Balance: pezframe::traits::AtLeast32BitUnsigned;
    211		fn more_stuff() {}
    212	}
    213
    214	trait Config: SystemConfig {
    215		type Currency: CurrencyTrait;
    216	}
    217
    218	struct Pezpallet<T: Config>(std::marker::PhantomData<T>);
    219	impl<T: Config> Pezpallet<T> {
    220		fn transfer(
    221			_from: T::AccountId,
    222			_to: T::AccountId,
    223			_amount: <<T as Config>::Currency as CurrencyTrait>::Balance,
    224		) {
    225			unimplemented!();
    226		}
    227	}
    228
    229	/// A common pattern in FRAME.
    230	type BalanceOf<T> = <<T as Config>::Currency as CurrencyTrait>::Balance;
    231}