Features introduced

The previous chapters you’ve finished, you have mastered the basic use of rust, but haven’t talked about rust programming paradigm, in this section, we come to understand object-oriented features of rust, we all know that has three features: object-oriented encapsulation, inheritance, polymorphism, below is from the three point view rust is how to design of object-oriented programming

encapsulation

Here we use the encapsulation feature to implement a structure that exposes several simple methods for automatically calculating averages:

// src/average.rs
// Define a structure
pub struct AveragedCollection {
  // An array for averaging
  list: Vec<i32>,
  // The average of the current list array
  average: f64,}impl AveragedCollection {
  // Provide the user with a method to create a new collection
  pub fn new() - >Self {
    Self {
      list: vec![],
      average: 0 as f64,}}// Provide the user with a way to add a number
  pub fn add(&mut self, value: i32) {
    self.list.push(value);
    // Calculate the average immediately after adding
    self.update_average();
  }

  // Provide the user with a way to delete the last item
  pub fn remove(&mut self) - >Option<i32> {
    let result = self.list.pop();
    match result {
      Some(value) => {
        // Calculate the average immediately after deletion
        self.update_average();
        Some(value)
      }
      None= >None,}}// Provide the user with a method to obtain the current average
  pub fn average(&self) - >f64 {
    self.average
  }

  // The internal method used to update the average
  fn update_average(&mut self) {
    let total: i32 = self.list.iter().sum();
    self.average = total as f64 / self.list.len() as f64; }}Copy the code

Simple use of the AveragedCollection structure:

// src/main.rs
mod average;
use average::AveragedCollection;

let mut ac = AveragedCollection::new();

// Add some values
ac.add(1);
ac.add(2);
ac.add(3);
ac.add(4);
println!("average(): {}", ac.average());
/ / 2.5

// Delete a value
ac.remove();
println!("average(): {}", ac.average());
/ / 2

println!("field average: {}", ac.average); // An error has been reported. Private fields cannot be accessed
println!("field list: {:? }", ac.list); // An error has been reported. Private fields cannot be accessed
Copy the code

The above code uses the pub keyword to encapsulate the details, exposing only the new, Add, remove and average methods. Since the list field is not public, the user does not need to change the code if we change the implementation of the collection in the future. For example, we can use HashSet instead of Vec on the list field.

inheritance

Rust does not provide inheritance in the traditional sense. As an alternative, we can use the default trait approach in Rust for code sharing:

trait Dog {
  // The default implementation of the method
  fn say(&self) {
    println!("Wang wang!")}}struct Husky {}
impl Dog for Husky {}

let husky = Husky {};
// Use the default implementation of the method
husky.say() / / wang wang ~
Copy the code

polymorphism

We can use generics in Rust to build different types of abstractions and use trait constraints to determine the specific characteristics that types must provide. This is also known as a bounded parametric polymorphism.


The following example creates a Graphical User Interface (GUI) package. The tool iterates through a list of elements and draws them onto the screen by calling the draw method of the elements in turn, exposing many built-in components such as Button and Text. In addition, package users can create custom types, for example, some developers might add Image, while others might add Select.

Define the Draw trait

Each graph must implement the Draw trait:

pub trait Draw {
  fn draw(&self); // will be called in Screen's run method
}
Copy the code

Define the Screen

Let’s define a Screen structure that contains the Components property to store the graph to be drawn:

pub struct Screen<T: Draw> {
  // Contains the graph to Draw, requiring the Draw trait to be implemented
  pub components: Vec<T>
}

impl<T: Draw> Screen<T> {
  pub fn run(&self) {
    // Call the draw method of each component in turn
    for item in self.components.iter() {
      item.draw()
    }
  }
}
Copy the code

Define the components

To implement Screen, let’s implement two graphics components, Button and Text, and Draw traits for them respectively:

// Button component
pub struct Button {
  pub width: u32.pub height: u32.pub label: String
}
// Implement the Draw trait
impl Draw for Button {
  fn draw(&self) {
    println!("Draw a Button.")}}// Text component
pub struct Text {
  pub width: u32.pub height: u32.pub placeholder: String
}
// Implement the Draw trait
impl Draw for Text {
  fn draw(&self) {
    println!("Draw a text"); }}Copy the code

Implement drawing logic

Next initialize the canvas and define the graphic component:

let screen = Screen {
  components: vec![
    Text {
      width: 100,
      height: 100,
      placeholder: String::from("Please enter text"),
    },
    Button { // An error was reported expecting a Text structure, but a Button was found
      width: 100,
      height: 100,
      label: String::from("Confirm"),},]}; screen.run()Copy the code

In the components member, the second member, Button, has a compilation error. The reason is that generic parameters can only be substituted for one specific type at a time. In the above code, a compilation error is generated when the first type is Text.

Using trait objects

To solve this problem, we can use trait objects, which allow many different concrete types to be filled in at run time:

pub struct Screen {
  // The element type of this dynamic array uses the new syntax Box
      
        to define trait objects
      
  // It is used to represent everything that is placed in a Box and implements the Draw trait
  pub components: Vec<Box<dyn Draw>>
}
Copy the code

Reimplement the draw logic:

let screen = Screen {
  components: vec![
    // Use Box to match the Draw trait
    Box::new(Text {
      width: 100,
      height: 100,
      placeholder: String::from("Please enter text"),}),Box::new(Button {
      width: 100,
      height: 100,
      label: String::from("Confirm"),})]}; screen.run()// Draw a text
// Draw a Button
Copy the code

The polymorphism of Rust is reflected in the fact that implementing the RUN method does not require knowing the specific type of each component. It simply calls the component’s DRAW method without checking whether a component is a Button or Text instance. By specifying the Box element type when defining dynamic array components, Screen instances receive only those values that can call the draw method.

Cover: Follow Tina to draw America