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}