How to apply Generics in Rust

Keng Lim
4 min readSep 25, 2021
Photo by Chandler Cruttenden on Unsplash

One thing I like about Rust is that the learning experience is greatly enhanced by examples provided in the official documentation.

But sometimes, the examples are too contrived for me to fully grasp a useful programming concept in Rust. For instance, the example describing generics and traits bound didn’t click for me. How often when I am programming do I need to calculate the area for different shapes? Here is a story of a real-life use case for using generics in a current Rust project that I am working on. The key takeaway here is not about the actual implementation but hopefully a demonstration of how to think about a programming problem and formulate a solution in idiomatic Rust.

The problem: How do you process different Rust types in the same way?

In my current project, we are working on creating a marketplace where buyers can find, design, and buy new construction homes. One of the entities that we model and process in the backend microservices is a Builder. A Builder type represents a new construction home builder.

For data exchange between our microservices, we are using graphQL. We like the single endpoint approach of graphQL for communication in comparison to REST. By using graphQL, APIs can be implemented to return only the type attributes that are needed for each API. Since our microservices are implemented with Rust, we use the graphql-client crate when making graphQL API calls as a graphQL client.

Use graphQL for implementing data exchange between microservices

As illustrated in the diagram above, there are 2 graphQL queries that are of interest here. One query returns a single graphQL type named Builder, and the other query returns an array of type Builder.

However, the schema-first, code-generation feature in the graphql-client crate generates 2 very different and concrete Rust structs/types for those 2 graphQL queries. Since Rust is a strongly typed language, the strawman approach is to implement 2 processing functions with 2 different signatures like this:

fn process_builder(builder_data: &BuilderByIdQueryBuilderData) -> BuilderDocument {
...
}
fn process_builder_from_array(builder_data: &BuildersQueryBuilderData -> BuilderDocument {
...
}

However, even though the Rust types are different, they represent the same graphQL type. We end up with duplicate code processing the same kind of thing even though the functions are named differently.

What we really want to do here is to implement a single processing function that has the flexibility of accepting 2 (or more) concrete types as inputs but yet process them in the same way because the 2 types share the same kind of data.

From the Rust-by-example book,

Generics is the topic of generalizing types and functionalities to broader cases. This is extremely useful for reducing code duplication…

Here is where Generics applies!

How to approach the problem using Generics

The thinking that led to using Generics is as follows:

Problem: We want a single processing function that can handle 2 different types that shares the same kind of data.
Idiomatic Rust Solution: Sharing the same kind of data means you can expect shared behavior between the types which in Rust is implemented as traits. In surveying the topic of generics and Rust, this problem is a use case for implementing a function with traits bound to avoid writing duplicate code.

Here is a code snippet after consolidating the 2 functions mentioned earlier into a single function using traits bound:

trait HasBuilderDocument {
fn get_logo(&self) -> String;
}
impl HasBuilderDocument for BuilderByIdQueryBuilderData {
fn get_logo(&self) -> String {
self.logo
}
}
fn process_builder<T: HasBuilderDocument>(client_data: &T) -> BuilderDocument {
// get_logo is a function for the trait HasBuilderDocument
let logo = client_data.get_logo();
...
}

Problem: We have 2 different Rust types that represent the same kind of data. The implementation of the trait functions will be the same.

// code duplication!impl HasBuilderDocument for BuilderByIdQueryBuilderData {
fn get_logo(&self) -> String {
self.logo
}
}
impl HasBuilderDocument for BuildersQueryBuilderData {
fn get_logo(&self) -> String {
self.logo
}
}

Fortunately, we didn’t have to write this macro as there is already a duplicate macro in the Rust crate registry that meets our needs here.

Hopefully, this story has been helpful in showing you how to think about a Rust programming problem and formulate a solution in idiomatic Rust that uses concepts of generics, traits, and traits bound.

Happy programming in Rust.

--

--

Keng Lim

I love building software for the web. Full-stack Developer. Full-time dad, husband, and follower of Isa al-Masih.