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