24 days from Node.js to Rust

preface

Rust designed Traits for code reuse. This is very similar to JavaScript mixins, which are a pattern of adding methods to objects, usually using object.assign (), for example:

const utilityMixin = {
    prettyPrint() {
        console.log(JSON.stringify(this.null.2)); }};class Person {
    constructor(first, last) {
        this.firstName = first;
        this.lastName = last; }}function mixin(base, mixer) {
    Object.assign(base.prototype, mixer);
}

mixin(Person, utilityMixin);

const author = new Person("Jarrod"."Overson");
author.prettyPrint();
Copy the code

You can also use mixins in TypeScript, but it’s a little more complicated

Rust Traits are very similar to JavaScript mixins in that they are collections of methods, and there are plenty of documents out there that compare structs and Traits to object inheritance. Ignore those, they just complicate things

You just have to remember,TraitsIt’s just a bunch of methods

The body of the

Perfect the TrafficLight

In the previous article, we added the get_state() method to the TrafficLight structure, and now it’s time to add functionality to each type of light. The first light we’ll add is the home light. Do not need to consider too many functions, as long as can turn on and off

Not surprisingly, we can do the following:

#[derive(Debug)]
struct HouseLight {
    on: bool,}impl Display for HouseLight {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Houselight is {}".if self.on { "on" } else { "off"}}})impl HouseLight {
    pub fn new() - >Self {
        Self { on: false}}pub fn get_state(&self) - >bool {
        self.on
    }
}
Copy the code

Next, we implement a generic print_state() function that we want to print the states of all lights:

fn print_state(light: ???) {}Copy the code

How do we do that? We can’t list all types like we do in TypeScript:

function print(light: TrafficLight | HouseLight) {... }Copy the code

In this example, we don’t care what type of data is being passed in, we just want its name and state, and Traits come in handy. Okay

Traits

Traits are defined starting with a trait keyword and have a similar structure to impL, containing several methods, except that methods in Traits can have no function body

Note: The function body is optional; if it is written, it corresponds to the default implementation, and others can choose to override the default implementation

Now implement a Traits called Light and add a get_name() method:

trait Light {
    fn get_name(&self) - > &str;
}
Copy the code

When we implement Traits, we use the impl keyword, just as we did with struct. Now we write it in the format impl [trait] for [struct] :

impl Light for HouseLight {
    fn get_name(&self) - > &str {
        "House light"}}impl Light for TrafficLight {
    fn get_name(&self) - > &str {
        "Traffic light"}}Copy the code

Now we can implement print_state() as an impl [trait] :

fn print_state(light: &impl Light) {
    println!("{}", light.get_name());
}
Copy the code

If you wanted to add the get_state() method to a trait, we ran into a problem because each light has a different type of state, so we printed them in debug format. When you hear this, your first reaction might be something like this:

trait Light {
    fn get_name(&self) - > &str;
    fn get_state(&self) - >impl std::fmt::Debug;
}
Copy the code

Rust will declare impl traits not allowed outside of function and method return types:

error[E0562]: `impl Trait` not allowed outside of function and method return types
  --> crates/day-10/traits/src/main.rs:17:27
   |
17 |   fn get_state(&self) -> impl std::fmt::Debug;
   |                           ^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0562`.
Copy the code

But we’re just going to treat it as a return value, so how do we do that?

impl vs dyn

The key to using Traits here is whether Rust knows the specific type at compile time, whether it should use dyn or impl if you want to use Traits using dyn. The reason we can’t use IMPL STD :: FMT ::Debug here is that each specific implementation returns a different type, whereas using DYn sacrifices performance for flexibility. Once a value is described as dyN, it loses type information and is essentially binary data pointing to a trait method

So we changed the code as follows:

trait Light {
    fn get_name(&self) - > &str;
    fn get_state(&self) - > &dyn std::fmt::Debug;
}

impl Light for HouseLight {
    fn get_name(&self) - > &str {
        "House light"
    }

    fn get_state(&self) - > &dyn std::fmt::Debug{&self.on
    }
}

impl Light for TrafficLight {
    fn get_name(&self) - > &str {
        "Traffic light"
    }

    fn get_state(&self) - > &dyn std::fmt::Debug{&self.color
    }
}
Copy the code

Note: Rust must know the size of each variable at compile time, but dyn [trait] is an exception because it is not any concrete type and does not have a known size

Now our complete code looks like this:

use std::fmt::Display;

fn main() {
    let traffic_light = TrafficLight::new();
    let house_light = HouseLight::new();

    print_state(&traffic_light);
    print_state(&house_light);
}

fn print_state(light: &impl Light) {
    println!("{}'s state is : {:? }", light.get_name(), light.get_state());
}

trait Light {
    fn get_name(&self) - > &str;
    fn get_state(&self) - > &dyn std::fmt::Debug;
}

impl Light for HouseLight {
    fn get_name(&self) - > &str {
        "House light"
    }

    fn get_state(&self) - > &dyn std::fmt::Debug{&self.on
    }
}

impl Light for TrafficLight {
    fn get_name(&self) - > &str {
        "Traffic light"
    }

    fn get_state(&self) - > &dyn std::fmt::Debug{&self.color
    }
}

impl std::fmt::Display for TrafficLight {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Traffic light is {}".self.color)
    }
}

#[derive(Debug)]
struct TrafficLight {
    color: TrafficLightColor,
}

impl TrafficLight {
    pub fn new() - >Self {
        Self {
            color: TrafficLightColor::Red,
        }
    }

    pub fn turn_green(&mut self) {
        self.color = TrafficLightColor::Green
    }
}

#[derive(Debug)]
enum TrafficLightColor {
    Red,
    Yellow,
    Green,
}

impl Display for TrafficLightColor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let color_string = match self {
            TrafficLightColor::Green => "green",
            TrafficLightColor::Red => "red",
            TrafficLightColor::Yellow => "yellow"};write!(f, "{}", color_string)
    }
}

#[derive(Debug)]
struct HouseLight {
    on: bool,}impl Display for HouseLight {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Houselight is {}".if self.on { "on" } else { "off"}}})impl HouseLight {
    pub fn new() - >Self {
        Self { on: false}}}Copy the code

The printed result is:

[snipped]
Traffic light's state is : Red
House light's state is : false
Copy the code

Now that our code is getting bigger and bigger, is it time to learn how to split code

read

  • The Rust Book: ch 10.02
  • Rust by Example: Traits
  • Common Rust Traits
  • The Rust Reference: Traits

conclusion

Traits are so ubiquitous in the Rust world that it’s worth digging into them. You can read Rust code on Github, or take a look at the entire library implementation. While there may be a single correct way to implement a feature in some languages, there is no equivalent in Rust, where there are 1000 different ways to implement a feature. It is very important to read other people’s code and not be a closed book when learning Rust

Note: The fact that there are 1,000 ways to implement a feature makes Rust a spiritual successor to Perl

In the next article, we’ll cover the module system. You can pick it up pretty quickly, but since you’re probably moving from Node.js, and node.js has the simplest module system I’ve ever seen, you’ll probably have some bumps in the beginning, but you’ll get the hang of it pretty quickly after overcoming a few bumps

More and more

  • Rust tutorial (1) From NVM to Rust
  • Rust tutorial (2) from NPM to Cargo for the front end
  • Configure Visual Studio Code in Rust (3)
  • Rust tutorial (4) Hello World
  • Rust Tutorial for the front end (5) Borrowing & Ownership
  • Part 1 of Rust Tutorial (6) String
  • Rust Tutorial (7)
  • Rust Tutorial (8)
  • Rust Tutorial (9)
  • Rust tutorial for the front end (10) from Mixins to Traits
  • Rust Tutorial (11) Module for the front-end