1 Month Learning Rust - Generic and Traits in Rust

1 Month Learning Rust - Generic and Traits in Rust

Daily short news for you
  • A software that converts text to speech created by a Vietnamese programmer - J2TEAM - Text to Speech (Free). You can convert dozens of languages into dozens of different natural voices. The special thing is that it is free.

    In preliminary evaluation, the conversion of long texts or texts in pure Vietnamese is very good. However, when it includes English words, it sounds a bit funny 😅

    » Read more
  • How terrifying, Codeium - known as a competitor to Github Copilot, as it allows users to use it for free without limits. Recently, they introduced the Windsurf Editor - no longer just a VSCode Extension but a full Editor now - directly competing with Cursor. And the main point is that it... is completely free 🫣.

    » Read more
  • There is a rather interesting study that I came across: "Users never bother to read things they don't want to." (That's a bold statement, but it's more true than not. 😅)

    Don't believe it? I bet you've encountered situations where you've clicked on a button repeatedly and it doesn't respond, but in reality, it has displayed an error message somewhere. Or you've filled out everything and then when you hit the submit button, it doesn't go through. Frustrated, you scroll up or down to read and find out... oh, it turns out there's an extra step or two you need to take, right?

    It’s not far from the blog here. I thought that anyone who cares about the blog would click on the "Allow notifications" button just below the post. But the truth is, no one bothers to click it. Is it because they don't want to receive notifications? Probably not! I think it's because they just didn’t read that line.

    The evidence is that only when a notification pops up and takes up half the screen, or suddenly appears to grab attention, do they actually read it—and of course, it attracts a few more subscribers—something that was never achieved before.

    » Read more

The Problem

JavaScript is one of the languages that do not need to declare the type. The data type is automatically implied and can change flexibly. A variable initially assigned a value as a number, just after a few lines of code it can become a string or any other value. That's interesting and sometimes a disaster!

In Rust, as well as in many typed programming languages, we are forced to declare the type for almost everything like variables, functions... In short, the data type is mandatory and you cannot change from one type to another like in JavaScript.

This has both advantages and disadvantages. When everything is clear, we can avoid many unnecessary errors, and it's also easier for others to understand what an object holds. But on the other hand, you have to spend a lot of time declaring types and lose the flexibility like in JavaScript.

Let's put that aside for now, because the above is just a personal opinion. Whether you like it or not depends on each individual and each situation. But Rust is definitely a typed language, and you must know about Generic and Traits to know how to handle some common cases.

Generic

We use generic to create definitions for functions or structs, and then use them with specific data types.

Take a simple example, largest is a function that finds the largest number in a list with the type i32.

fn largest(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

largest takes a list with the type i32, and after finding the largest number, it returns that number. But in reality, besides i32, we have many other data types that can find the largest value like i64, f32, f64, char... Obviously, largest cannot take a list with a different data type than i32. Do we have to create another function with a new data type every time we want to find the largest number?

Generics in Rust help us solve this problem of code duplication. A Generic is declared with uppercase letters to represent a general value. For example:

fn largest<T>(list: &[T]) -> &T {
    ...
}

It's easy to see that T here represents a general value instead of a specific data type. If we read it, it would be: "the largest function takes a list with the data type T and returns a value also with the data type T".

The new function implementation will look like this:

fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

However, largest still cannot work because T is too general. Let's look at the function body, the comparison item > largest makes the function not work if we pass in data types that cannot be compared like struct, enum... To solve this, in Rust, we have the concept of traits - PartialOrd is a trait and we only need to declare T as PartialOrd.

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

So what is a trait?

Traits

Traits are a concept to group similar objects. Traits are similar to interfaces in object-oriented programming languages.

For example, you have two objects NewsArticle used to store a news article and Tweet to store a discussion topic. You realize that both objects need a summarize function to summarize the content instead of displaying a long content. Traits appear:

pub trait Summary {
    fn summarize(&self) -> String;
}

Summary is declared as a trait and inside there is a method summarize. Looking at it, we only see that it returns a String and does not see any implementation code. That's right, because this is just a trait declaration.

Assuming NewsArticle and Tweet have the following structures:

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

Then to implement the trait, we need to do:

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

As a result, whenever we call the summarize function from NewsArticle or Tweet, we will get the corresponding result.

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    };

    println!("1 new tweet: {}", tweet.summarize()); // 1 new tweet: horse_ebooks: of course, as you probably already know, people
}

Why not implement the summarize function directly in NewsArticle or Tweet but have to go through traits? It's to declare the type and use it to support parameters with multiple data types in a function.

Like the example above, when we didn't declare T as PartialOrd, the comparison inside would fail. On the other hand, T is PartialOrd, PartialOrd implements the comparison for T so Rust knows how to compare T with each other. Or like the example below:

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

notify takes an item parameter that is the type that implements the Summary trait. item can be any object that implements the Summary trait, then item.summarize() will work.

Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.
Author

Hello, my name is Hoai - a developer who tells stories through writing ✍️ and creating products 🚀. With many years of programming experience, I have contributed to various products that bring value to users at my workplace as well as to myself. My hobbies include reading, writing, and researching... I created this blog with the mission of delivering quality articles to the readers of 2coffee.dev.Follow me through these channels LinkedIn, Facebook, Instagram, Telegram.

Did you find this article helpful?
NoYes

Comments (0)

Leave a comment...