Extension traits
In Grug, we make use of the extension trait pattern, which is well explained by this video.
To put it simply, a Rust library has two options on how to ship a functionality: to ship a function, or to ship a trait.
For instance, suppose our library needs to ship the functionality of converting Rust values to strings.
Shipping a function
The library exports a function:
#![allow(unused)] fn main() { pub fn to_json_string<T>(data: &T) -> String where T: serde::Serialize, { serde_json::to_string(data).unwrap_or_else(|err| { panic!("failed to serialize to JSON string: {err}"); }) } }
The consumer imports the function:
#![allow(unused)] fn main() { use grug::to_json_string; let my_string = to_json_string(&my_data)?; }
Shipping a trait
The library exports a trait, and implements the trait for all eligible types.
The trait is typically named {...}Ext
where "Ext" stands for extension, because the effectively extends the functionality of types that implement it.
#![allow(unused)] fn main() { pub trait JsonSerExt { fn to_json_string(&self) -> String; } impl<T> JsonSerExt for T where T: serde::Serialize, { fn to_json_string(&self) -> String { serde_json::to_string(data).unwrap_or_else(|err| { panic!("failed to serialize to JSON string: {err}"); }) } } }
The consumer imports the trait:
#![allow(unused)] fn main() { use grug::JsonSerExt; let my_string = my_data.to_json_string()?; }
Extension traits in Grug
We think the consumer's syntax with extension traits is often more readable than with functions. Therefore we use this pattern extensively in Grug.
In grug-types, we define functionalities related to hashing and serialization with following traits:
Borsh{Ser,De}Ext
Proto{Ser,De}Ext
Json{Ser,De}Ext
HashExt
Additionally, there are the following in grug-apps, which provides gas metering capability to storage primitives including Item
and Map
, but they are only for internal use and not exported:
MeteredStorage
MeteredItem
MeteredMap
MeteredIterator