Struct of Rust: defining and instantiating structs

Posted by daveoliveruk on Mon, 29 Nov 2021 10:27:34 +0100

development environment

  • Windows 10
  • Rust 1.56.1

 

  •   VS Code 1.62.3

  Project engineering

The last project trust demo will continue here

structural morphology

  A structure is similar to a tuple. Like tuples, fragments of structures can be of different types. Unlike tuples, we will name each data segment so that we can clearly understand the meaning of the value. Because of these names, structs are more flexible than tuples: we don't have to rely on the order of data to specify or access the values of instances.

To define the structure, we enter the keyword struct and name the entire structure. The name of the structure should describe the importance of grouping data blocks together. Then, the name and type of the data fragment are defined in curly braces, which we call fields. Examples are as follows:

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

In order to use the structure after it is defined, we create an instance of the structure by specifying a specific value for each field. We create an instance by declaring the name of the structure, and then add curly braces containing the key: value pair, where key is the name of the field and value is the data we want to store in these fields. We do not have to specify fields in the order in which they are declared in the structure. In other words, a structure defines a generic template similar to a type, and the instance populates the template with specific data to create the value of the type. Examples are as follows:

let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

To get a specific value from the structure, we can use point representation. If we only want the email address of this user, we can use user1.email as long as we want to use this value. If the instance is mutable, we can change the value by using the point representation and assigning it to a specific field. As follows:

   let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // Use the email member in the structure User
    user1.email = String::from("anotheremail@example.com");

Note that the entire instance must be mutable; Rust does not allow us to mark only certain fields as variable. Like any expression, we can construct a new instance of the structure as the last expression in the function body to implicitly return the new instance. As follows:

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

// Building structure User
fn build_user(email: String, username: String) -> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}

It makes sense to name the function parameters the same as the structure field, but it's a little cumbersome to have to repeat the email and username field names and variables. If the structure has more fields, repeating each name becomes more annoying. Fortunately, there is a convenient shorthand!

Use field Init

Since the parameter name and structure field name are exactly the same as the above example, we can rewrite build using the field init shorthand syntax_ User to make it behave exactly the same, but email and username will not be repeated. As follows:

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

Here, we are creating a new user structure instance with a field named email. We want to set the value of the email field to build_ The value in the email parameter of the user function. Because the "email" field and the "email" parameter have the same name, we only need to write email, not email: email.

Create an instance from another instance

It is often useful to create a new instance of a structure, which uses the values of most old instances, but changes some values. You can do this using structure update syntax. As follows:

  let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };

Using structure update syntax, we can achieve the same effect with less code. Grammar. Specifies that the remaining fields that are not explicitly set should have the same values as the fields in the given instance. As follows:

// Use update syntax.. to build struct instances  
let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };

In the above code, an instance is also created in user2, which has different email, but is different from username, active and sign in user 1_ The value of the count field is the same User1 must finally specify that any remaining fields should get values from the corresponding fields in user1, but we can choose to specify values for any number of fields in any order, regardless of the order of the fields in the structure definition.

Use tuple structure

You can also define structures that look like tuples, called tuple structures. Tuple structure has the meaning of adding, that is, the structure name is provided, but does not have the name associated with its field; Instead, they only have the type of field. Tuple structures are useful when you want to name the entire tuple and make it a different type from other tuples, and naming each field a regular structure would be verbose or redundant.

To define a tuple structure, start with the struct keyword and structure name, followed by the type in the tuple. As follows:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Note that black and origin are different types because they are instances of different tuple structures. Each structure we define is of its own type, even if the fields in the structure have the same type. For example, a function that accepts a Color type argument cannot take Point as an argument, even if both types consist of three i32 values. Otherwise, tuple structure instances behave like tuples: we can decompose them into their parts, we can use a. Followed by an index to access a single value, and so on.

Unit structure without field

You can also define structures without any fields, which are called unit like structures because they behave like (). The unit like structure is useful when you need to implement a feature on a type but do not want any data stored in the type itself. As follows:

fn main() {
    struct AlwaysEqual;

    let subject = AlwaysEqual;
}

To define AlwaysEquate, we use the struct keyword, followed by the name of the structure, followed by a semicolon. Curly braces or parentheses are not required! Then, we can use the variable subject to get an instance of AlwaysEquate in a similar way: use the name we defined without any curly braces or parentheses. Imagine that we will implement behavior for this type, that is, each instance is always equal to each instance of each other type, possibly to obtain a known result for testing purposes. We don't need any data to achieve this behavior!

Ownership of structure data

In the previous User structure definition, we used our own string type instead of & STR string string string slice type. This is a deliberate choice, because we want the instance of the structure to have all its data, and as long as the whole structure is valid, the data will be valid.

A structure can store references to data owned by other things, but to do so, you need to use lifetime s to ensure that the data referenced by the structure is valid within the validity period of the structure. Suppose you try to store references in a structure without specifying a lifecycle, as in the following example, it doesn't work.

struct User {
    username: &str,
    email: &str,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

Run the above code

cargo run

 

Key points of this chapter

  • Structure concept
  • Define structure
  • Instantiate structure

Topics: Rust Visual Studio Code