Rust ability development (4): user freedom

image

 

Preface

 

The last article introduced the Rust language

  • Conditions and decisions

  • Match expression

  • loop statement

 

This article introduces the user-defined data types of the Rust language

  • Structure

  • enumerate

 

As the name suggests, user-defined types are defined by users themselves. These data types are either wrappers of basic data types or a further combination of existing user-defined types. There are usually three forms. Students who have studied C must have an impression, namely the so-called structures (structures->structs), enumerations->enums and unions->union .

It is undeniable that these data types have improved the expressiveness of the data. In Rust, the first two user-defined types mentioned are more powerful than the structures and enumerations in C; and the third item---union, which is similar to C, will be detailed in Talk about it in the subsequent chapters. As mentioned above, this article mainly introduces structures and enumerations.

 

Structure (Structs)

 

In Rust, we can declare three forms of structure. The simplest one is unit struct, which can be called unit structure for the time being . Syntactically, it is composed of the struct keyword, the corresponding name and a semicolon. The following code example defines a unit structure.

 

Unit struct

// unit_struct.rs
struct DummyStruct;fn main() {
   
       let value = DummyStruct;}

Very simple, the code defines a unit structure called Dummy. In the main function, you can use only its name to initialize the type. The variable value now contains an instance of Dummy and has a size of 0 in terms of occupying system resources.

 

At runtime, if there is no related data, the unit structure will not claim any space. In terms of its use, there are mainly three types:

  • Used to model entities that have no data or state associations.

  • Indicates the type of error, because the structure itself is sufficient to refer to the error content, and it is no longer necessary to describe it

  • Characterize the state in the implementation of a state machine

 

Tuple struct

 

Let me talk about the second structure, the so-called tuple structure, which is associated with data. Furthermore, there is no need to name a single field, it can be referenced by its position in its definition. For example, you are writing a color conversion/calculation library for your graphics application, and you want to represent the value of RGB color in the code.

Let's look at the following code:​​​​​​​

// tuple_struct.rs struct Color(u8, u8, u8);
fn main() {
   
       let white = Color(255, 255, 255);        // You can pull them out by index    let red = white.0;    let green = white.1;    let blue = white.2;
    println!("Red value: {}", red);    println!("Green value: {}", green);    println!("Blue value: {}\n", blue);
    let orange = Color(255, 165, 0);
    // You can also destructure the fields directly    let Color(r, g, b) = orange;    println!("R: {}, G: {}, B: {} (orange)", r, g, b);
    // Can also ignore fields while destructuring    let Color(r, _, b) = orange;    println!("R: {}, G: {}, B: {} (orange)", r, g, b);    let Color(_, _, _) = orange;    println!("R: {}, G: {}, B: {} (orange)", r, g, b);}

 

visible,

  • Line 2, define Color as the tuple structure here

  • Line 5, assign a value to the white variable

  • Lines 8-10, through variable.

    Way, visit the reference position in the structure variable domain, assign the corresponding variable
  • Line 12-14, print the corresponding value

  • Line 16, assign a value to the orange variable

  • Lines 19-20, use deconstruction to access the individual data in the structure and print the result

    • This is obviously a way to access individual data

  • 22-26, if you are too lazy to write individual parameters one by one, you can use the underscore _ to deal with the respective positions, and the corresponding results can still be correctly inferred and printed out.

 

The code results are as follows:

image

 

 

Common Struct

 

When modeling data with attributes within four or five, tuple structure is an ideal choice; once the data range is exceeded, the readability and semantic reasoning of the code will be affected. Therefore, for data types with more than three fields, it is recommended to use a structure similar to c , which is the third form to be introduced and the most commonly used one.

Let's look at the following code:​​​​​​​

// structs.rs
struct Player {
   
       name: String,    iq: u8,    friends: u8,    score: u16}
fn bump_player_score(mut player: Player, score: u16) {
   
       player.score += 120;    println!("Updated player stats:");    println!("Name: {}", player.name);    println!("IQ: {}", player.iq);    println!("Friends: {}", player.friends);    println!("Score: {}", player.score);}
fn main() {
   
       let name = "Alice".to_string();    let player = Player { name,                          iq: 171,                          friends: 134,                          score: 1129 };
   bump_player_score(player, 120);}

 

Read this code, you can see,

  • In lines 3-8, the structure is defined in the same way as the tuple structure. The difference is that the more formal type declaration and naming are carried out in braces

  • Lines 10-17 define the function that operates on the structure data. You can see that the first parameter is a variable binding, and the reader can think about what it means.

  • Line 20, initialize the variable name

  • Lines 21-24, initialize the structure variable player, take a closer look at the structure inside the braces, the last three are name + colon + assignment, and the wording of name is a way of omission, which is called field init shorthand

 

The code results are as follows:

image

 

Conventional structure, or structure for short, has an advantage over tuple structure in that it can assign values to structure objects in any order . In addition, the size of the space occupied by the structure is generally the sum of all individual attributes, and there will be data alignment in this case. In addition, there is no additional space cost.

 

 

Enumeration (enums)

 

When you need to model different types of objects, you can use enumerations. In the definition process, each possibility or attribute is called variants . These variants can be defined as whether they contain data or not. The data can be any basic type, structure, tuple structure, or even enumeration.

However, if it is recursive, for example, there is an enumeration Foo, and a variant that holds Foo, the variant needs to be after a pointer type to avoid recursive infinite type definitions. Because enumerations can also be created on the stack, a predetermined space is required, and the infinite type definition cannot determine the value at compile time.

 

Let's look at an example. ​​​​​​​

// enums.rs
#[derive(Debug)]enum Direction {     N,     E,     S,     W}
enum PlayerAction {
   
       Move {
   
           direction: Direction,        speed: u8    },    Wait,     Attack(Direction)   }
fn main() {
   
       let simulated_player_action = PlayerAction::Move {
   
           direction: Direction::N,        speed: 2,    };    match simulated_player_action {
   
           PlayerAction::Wait => println!("Player wants to wait"),        PlayerAction::Move { direction, speed } => {
   
             println!("Player wants to move in direction {:?} with speed {}",                direction, speed)        }        PlayerAction::Attack(direction) => {
   
               println!("Player wants to attack direction {:?}", direction)        }    };}

 

First of all, please pay attention to the format of enumeration definition. Furthermore, it is not difficult to see that there are two enumerations defined here, Direction  and  PlayerAction . Based on this, instances can be created. These are already reflected in the main function, such as lines 21-23

Note that the enumeration must be initialized, but you can choose any variant. According to the given enumeration value, match and execute the corresponding result, it is the content of the 25th-34th line, it is not difficult to see the three behaviors, Wait, Move and Attack.

Finally, look at #[derive(Debug)] in line 3. This attribute is implemented by the feature generation method of Debug in order to ensure that println!() uses {:?} format string .

 

The code results are as follows:

image

 

 

Conclusion

 

From the perspective of functional programming, structures and enumerations are also called algebraic data types (Algebraic Data Types, ADTs) , because the possible range of representable values ​​can be expressed using algebraic rules. For example, the enumeration of addition and multiplication is the range of the operation value of each variant. We will discuss this later.

This article introduces two user-defined data types: structure and enumeration , which have already reflected part of the design focus of the Rust language. In the next article, I will talk about functions and methods related to types .

 

 

Main references and documents recommended for readers to read further

https://doc.rust-lang.org/book

1. The Way of Rust Programming, 2019, Zhang Handong

2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
3.Hands-On Data Structures and Algorithms with 

4.Rest , 2018 , Claus Matzinger
5.Beginning Rest , 2018 , Carlo Milanesi

6.Rust Cookbook , 2017 , Vigneshwer Dhinakaran

Guess you like

Origin blog.csdn.net/qq_40433634/article/details/112401301