The pit is getting deeper and deeper. Let me see your hands in the pit!
Earlier we talked about the most basic data types of Rust. I don’t know if you remember it, but if you don’t, you can review it. After digging a pit, a classmate’s private letter I said the pit was too deep, almost sprained his feet when he came down. I can only say sorry to him, and it will be worse next time. This article won’t go that far, but I’ll take you through Structs and Enums. Isn’t that a surprise? All right, back to the point. Let’s start with Structs.
Structs
Structs are found in many languages and are a custom type that is analogous to Java classes. Structs are used in Rust using the struct keyword. For example, we define a user type.
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,}Copy the code
The corresponding data type above can be directly replaced with the correct value during initialization.
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,}}Copy the code
Email: email; username: username; If all attribute values of User are passed in from function parameters, then we repeat each parameter name. Fortunately Rust provides syntactic sugar that saves some code.
Omit variable names when initializing structs
We can simplify the initialization code above.
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,}}Copy the code
You can think of this as a syntactic sugar of Rust, which initializes a Struct by omitting the variable name when it is the same as the field name. Saves the developer from doing a lot of pointless rework (writing emails twice).
Create structs from other instances
In addition to the syntax sugar above, Rust provides another syntax sugar when creating structs. For example, if we create a new user2 that differs only from user1 with the mailbox and username, and all other attributes are the same, we can use the following code:
#! [allow(unused_variables)]
fn main() {
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,}let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1};let user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
..user1
};
}
Copy the code
Here.. User1 indicates that the remaining fields have the same value as user1.
Tuple Struct
There are two special structs. One is a Tuple Struct, which is defined like a Tuple
struct Color(i32.i32.i32);
struct Point(i32.i32.i32);
Copy the code
It differs from a Tuple in that you can give a Tuple Struct a meaningful name instead of just a bunch of meaningless values. Note that Color and Point are two different types and cannot be assigned to each other. Also, if you want to get the value of a field in a Tuple Struct, just like a Tuple, use. Can.
Empty fields Struct
There is also a special kind of Struct that has no fields. It’s called unit-like structs. This structure is typically used to implement some feature but has no data to store.
Struct method
Methods are very similar to functions, except that when a method is defined, it must have a Struct associated with it, and the first argument to the method must be self. Let’s first look at how to define a method:
struct Rectangle {
width: u32,
height: u32,}impl Rectangle {
fn area(&self) - >u32 {
self.width * self.height
}
}
Copy the code
We mentioned that a method must be associated with a Struct. Here we use the IMPL keyword to define an implementation code that specifies a Struct, and then define struct-related methods in this code block. Note that our area method is in line with the rule, and the first argument is self. You just need to call it. You can.
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
rect1.area();
}
Copy the code
The &self is a rectangle instead of a rectangle: &, and we’ve already explained why this is used. Of course, self doesn’t have to be an ampersand, you can think of it as a normal argument and use it as needed.
Some of you might be a little confused, why do we need methods when we already have functions? This is really about the structure of the code. We need to put everything that the Struct instance can do into the IMPL implementation block for easy modification and lookup. Using functions, however, can be awkward for developers to find a random place to define, which will be a disaster for developers who maintain the code later.
Now we know that methods must be defined in an IMPL code block and the first argument must be self, but sometimes you’ll see that the first argument in an IMPL code block is not self, and Rust allows this behavior.
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
Copy the code
What’s going on here? Is that wrong? They are not; they are called associated functions. It is still a function, not a method, and is directly related to structs, similar to static methods in Java. It is called directly with a double colon (::), and String::from(“Hi”), which we have seen many times before, is the function associated with String.
Finally, Rust supports defining multiple implementation blocks for a Struct. But we don’t recommend it.
At this point, the first pit Struct is dug, then the second pit Enum.
Enum
Many programming languages support enumerated types, and Rust is no exception. Enumerations are therefore familiar to most developers, and here’s a quick look at some of their usage methods and features.
Let’s take a look at how to define enumerations and get their values in Rust.
enum IpAddrKind {
V4,
V6,
}
let six = IpAddrKind::V6;
let four = IpAddrKind::V4;
Copy the code
The example here is just the simplest way to define an enumeration, and the values of each enumeration can be associated with values of other types. For example,
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32.i32.i32),}Copy the code
In addition, enums can also have impL blocks like structs, and you can define methods in them.
Option to enumerate
Option is an enumeration defined in the Rust standard library. If you’ve used Java8, you know of an Optional class that handles null values. Null values do not exist in Rust because they are too buggy. But what if you do need it? That’s where the Option enumeration comes in. Let’s take a look at its definition:
enum Option<T> {
Some(T),
None,}Copy the code
It’s easy, isn’t it? It is an enumeration with only two values, Some and None, and Some is associated with a value of type T, which is similar to generics in Java in that it can be of any type.
You can use Some or None without adding Option::. When you use None, you must specify the specific type of T.
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
Copy the code
Note that Option
is not of the same type as T. You can see how to extract T from Option
in the official documentation.
Match Process Control
Rust has a powerful process control operation called Match, which is somewhat similar to Switch in Java. First match a series of patterns, then execute the corresponding code. Unlike switch in Java, which supports only numeric/enumerated types (and now strings), match can support any type.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,}}Copy the code
In addition, Match can support binding values in patterns.
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:? }!", state);
25}}},Copy the code
Match with Option < T >
Earlier we talked about extracting the value of T from Option<T>, let’s introduce a method of extracting T through match.
fn plus_one(x: Option<i32- > >)Option<i32> {
match x {
None= >None.Some(i) => Some(i + 1),}}let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Copy the code
This method must declare the specific type of T in the argument. Here’s another question: If we are certain that x cannot be None, can we drop the condition for None?
_
A placeholder
The answer is no. Rust requires match to list all possible conditions. For example, if it is of type U8, the conditions 0 through 255 need to be enumerated. You probably won’t be able to write more than a few match statements a day. So Rust has another grammar candy for us.
For the above situation, it can be written as follows:
let some_u8_value = 0u8;
match some_u8_value {
1= >println!("one"),
3= >println!("three"),
5= >println!("five"),
7= >println!("seven"), _ => (),}Copy the code
We just need to list a few of the cases we care about and use placeholder _ to represent the rest. See this I just want to say, this sugar is really sweet ah.
if let
For match, where we only care about one condition, there is a more concise syntax, which is if let.
For example, we only want to print the information if Option<u8> has a median value of 3, using what we already know.
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) = >println!("three"), _ => (),}Copy the code
If we use if let, it’s a little bit more concise.
if let Some(3) = some_u8_value {
println!("three");
}
Copy the code
Note here that if let is used instead when match has only one condition.
Some of you might say, well, if I call it if let, is there an else condition? The answer is yes. For the following case
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:? }!", state),
_ => count += 1,}Copy the code
If replaced by an if let statement, it should be
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:? }!", state);
} else {
count += 1;
}
Copy the code
conclusion
The second hole has been dug, so let’s sum it up. This article starts with structs, which are similar to classes in Java and allow developers to customize their types. Then it introduces two ways to simplify code when initializing Struct. The next step is to define struct-related methods. Structs are followed by the familiar Enum Enum type. It focuses on the special enumerated options in Rust, and then introduces match and if let, two process-control grammars.
Finally, in accordance with international practice, I would like to sincerely invite you into the pit as soon as possible. The pit is really warm in winter and cool in summer