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}ExtProto{Ser,De}ExtJson{Ser,De}ExtHashExt
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:
MeteredStorageMeteredItemMeteredMapMeteredIterator