referrerpolicy=no-referrer-when-downgrade

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