This article was originally posted by me on November 30, 2021, at thiscoindaily


Hello guys, in this part of the series, I’ll be introducing a rust technique that’s widely used in Substrate – generics. If you haven’t, check out the first part of this series where I took a deep dive into rust/substrate macros.

Generics are quite regularly used in Substrate. Throughout the development process, there’ll be a lot of occasions where you’ll need to use substrate generics that exhibit certain traits. Therefore, understanding how generics work will help you in understanding what’s going on with your code, as well as make it easier for you to debug your code when your run into errors.

In this guide we’ll first start with the basics of generics in rust, then we’ll look at sample implementations of generic types in Substrate. I’ll be using the substrate playground’s node template for demonstrations.

Note: if you’re just starting out with substrate/rust, you might want to check out this guide,

Let’s get started!

What are generics?

Basically, generics help reduce the redundancy of your code, by allowing you to use components of your codes on multiple types, without having to rewrite those components for each type.

Generics work by applying certain traits to types and function definitions, which automatically makes them compatible with other components that implement similar traits.

In its simplest form generics are created by adding an <T> in front of the components you’d like to make a generic (this could be function names/parameters, struct (and its implementations), enums, etc. The “T” signifies that any component which implements any form of a trait will be compatible with this type. You could also use multiple generic types with the same type if needed.

The snippets below show some common usage of generic types you are likely to come across

 // single generic type
 struct StructName<T> {
     property1<T>
     propertry2<T>
 }

 // multiple generic types in functions
 struct StructName<T, U> {
     property1<T>
     propertry2<U>
 }

 //generic types in struct implementations
 impl<T> StructName<T> {
     //write your functions here
     }

 //Option enum, an example of generic types in enums
 fn main() {
 enum Option<T> {
     Some(T),
     None,
 }
 }

However, there are situations in which you’ll need to make your generics more specific. This is because there’re some operations that work on types that exhibit specific sets of traits. To make a generic more specific, you’ll have to specify the traits you want your type to exhibit by adding a “:” and specifying the traits (each separated by a plus sign).

For example, the code below specifies two traits (trait1 and trait2) during the declaration of the fn_name function. this means that this function will only be compatible with types that exhibit both the trait1 and trait2 traits.

// using trait-specific generic types for functions and parameters
fn fn_name<T: trait1 + trait2>(var: T) -> T {
    //
}

I hope by now, you know how generics work and how to recognize them.

Let’s now have a look at a substrate node to explore an example of how generics are used on Substrate.

Substrate’s Config trait for generics

The config trait is probably the most important trait you’ll need to understand when it comes to creating pallets. The Config trait is written inside of your pallet, and its job is to specify the parameters and types which you’ll be implementing in the runtime. We’ll dive more into traits in the next part of this series. But for now, let’s understand how the Config trait works with the runtime

When you’re building your node using FRAMES, most of your types and parameters would be specified in your pallet in the following form:

  #[pallet::config]
     pub trait Config: frame_system::Config {

         //add your non-constant types and parameters here.

         #[pallet::constant]
         add your constant types here
 }

Note that if you’re creating type aliases for use within the pallet, you don’t need to specify them inside the Config trait

The #[pallet::config] attribute helps to provide constants that the runtime would depend on. Some types that you’ll be defining will be non-constant types (eg, currency type, event type, etc).

however, you might need to add constant types in some cases. For example, if you want to add a type that represents a fixed fee, or maybe the maximum number of NFTs that can be owned by an individual, you’ll have to make this a constant number.

If you’re defining a constant type, it has to be preceded by the #[pallet::constant] attribute of the pallet macro. if you don’t know what macros are or how substrate uses macros, you might want to check out the first part of this series

The Config trait is always used as a trait bound to create generics for all pallet implementations. Therefore, to create any implementations for your pallet, you must follow the pattern below:


impl<T:Config> Pallet<T> {
    //your functions here
}

To implement the Config trait in your runtime, you have to go into your runtime and write your code in the following format:

parameter_types! {  
    //define your constant types here
}

impl pallet_palletname::Config for Runtime {
    //add your types here
}

If you added any constant type in your Config trait, you have to define those constants inside parameter_types! before adding them to your runtime implementation.

Knowing what traits are and how to use them to create generics is a very important technique to master. In the case of this example, the Config trait was used to create a generic for pallet implementations.

Conclusion

In this part of the series, I’ve introduced you to generics their types, and their importance. I also dived into how they’re used in Substrate by using the Config trait and trait bound as an example. In the last part of this series, we’ll take a closer look at what these traits and trait bounds are.

Ciao!

Share.

Comments are closed.