Rust development - variables, static variables and constants

1.Variables

In Rust, type safety is achieved through a static type system. Variable bindings are immutable by default.

When you declare a variable in Rust, it is immutable by default. For example:

fn main() {
    
    
    let x :i32 = 5; // 这是一个不可变绑定,默认情况下不可改变其值
    // x = 10; // 这将导致编译错误,因为 x 是不可变的
}

Type inference, i32 is optional

In the above example, x is declared as an immutable binding and initialized to 5. If you try to modify the value of x (for example, try to change the value of x to 10), it will cause a compilation error because the variable Immutable by default.

To allow variables to be mutable, you can use the mut keyword to explicitly declare a mutable binding:

fn main() {
    
    
    let mut y = 5; // 使用 mut 关键字声明可变绑定
    y = 10; // 这是合法的,因为 y 是可变的
    println!("The value of y is: {}", y); // 输出 y 的值
}

used the mut keyword to declare a variable bindingy, and then successfully changed the value of y Modify to 10.

2.Type inference

In Rust, the compiler infers the type of a variable based on how it is used.

fn main() {
    
    
    let x = 5; // 编译器根据赋值来推断 x 的类型为 i32
    let y = 3.14; // 编译器根据赋值来推断 y 的类型为 f64

    let sum = x + y; // 编译器根据 x 和 y 的使用来推断 sum 的类型
    println!("The sum is: {}", sum); // 输出 sum 的值
}

The compiler infers from the assignment that x has type i32 and y has type < a i=4>. Then, when and are added as operands, the compiler infers the type of based on its usage. In this case, the type of is inferred as because it is and Mixed operation result of type. Rust's type inference capabilities allow for writing more concise and flexible code, but also maintain type safety. f64xysumsumf64i32f64

It’s worth noting that the Rust compiler can infer types based on constraints on variable declarations and usage. Variables declared like this are not some dynamic "any type" that can hold any data. The machine code generated by such a declaration is exactly the same as the code for an explicitly declared type. It's just the compiler that completes the conversion.

The following code tells the compiler to copy the contents of a certain generic container. The code itself does not explicitly specify the containing type, but uses _ as a placeholder:

fn main() {
    
    
    let mut container = Vec::new(); // Rust 根据使用推断出 container 的类型为 Vec<_>
    
    container.push(5); // 向向量中添加一个 i32 类型的元素
    container.push(3.14); // 向向量中添加一个 f64 类型的元素

    for element in container {
    
    
        println!("Element: {}", element);
    }
}

container is initialized to an empty vector Vec<_>. The underscore _ is a placeholder that tells the compiler to infer the type based on the element being inserted into the vector. In this case, container is a vector that can hold different types of elements (i32 and f64).

Rust infers the type of a container based on its usage and allows it to hold elements of different types. However, once a specific type is inferred, the container's type is fixed and elements of different types will not be accepted unless otherwise specified.

3. Static variables

Static variables exist throughout the execution of the program and cannot be moved.

In Rust, static variables have a 'static lifetime. They are initialized when the program starts and remain unchanged throughout the lifetime of the program. These variables are stored in the program's static storage area, so they are not destroyed or moved. This means that the lifetime of static variables is the same as the lifetime of the entire program.

Since the lifetime of static variables is 'static, they last as long as the entire program is running, so they will not be moved. In Rust, references to static variables can be safely passed around and used throughout a program because their lifetime does not end or change.

static BANNER: &str = "Welcome to RustOS 3.14";

fn main() {
    
    
    println!("{BANNER}");
}

Static variables are not inlined when used and have an actual associated memory location. This is useful for unsafe programming and embedded code, where these variables exist throughout the execution of the program. When a globally scoped value does not require an object identifier, const is generally preferred.

Since static variables can be accessed from any thread, they must be Sync . Interior mutability can be achieved via Mutex, atomic or similar. You can also have mutable static variables, but they need to be synchronized manually, so any access to them requires using unsafe code. We’ll explore mutable static variables in the chapter on unsafe Rust.

4.Constant (const)

constants are evaluated at compile time and their values ​​are inlined where used.
When you declare a constant in Rust, the compiler calculates the value of the constant during compilation and inlines that value into the code wherever the constant is used. This means that the compiler replaces the actual value of the constant directly into the code rather than calculating it at runtime. This inlining helps improve the efficiency of your code and ensures that constants are immutable at runtime.

For example:

const VALUE: i32 = 42;

fn main() {
    
    
    let x = VALUE * 2; // 编译器会将 VALUE * 2 替换为 42 * 2,这个值会在编译期间被计算
    println!("The value of x is: {}", x);
}

VALUE * 2The evaluation result of will be replaced by 84 at compile time, and this value will be used directly at runtime. This behavior ensures that constants are evaluated during compilation and replaced directly in the machine code generated by the compilation.

  • Constants (declared with const) are inlined when used, and their values ​​are substituted directly into the code.
  • Functions marked const can be called at compile time to produce constant values. Although these functions can be called at runtime, their primary purpose is to perform calculations during compilation, producing constant values.

If you have used C++ programming, const and static in Rust can be understood as:

  • const is semantically similar to C++'s constexpr. It represents a compile-time constant whose value is determined at compile time and is immutable at runtime. Constants are typically used to represent fixed values ​​that do not change during the execution of a program.
  • In contrast, static is more similar to const or a mutable global variable in C++. static provides the object identity: its address in memory and, for types with internal mutability (such as Mutex<T>), the required state. It exists throughout the execution of the program and can be accessed by multiple threads.
  • In most cases there is little need to calculate constant values ​​at runtime, but sometimes it is safer than using static . Using const ensures that the value is determined at compile time, whereas static may introduce the possibility of runtime variation to a certain extent.
  • can create data via the std::thread_local macro. This data is unique to each thread, each thread has its own copy, so the data is not shared between threads. This is useful in multi-threaded environments when independent copies of data are required. thread_local

5. Variable scope and hidden variables

In Rust, you can use scope hiding to redefine a variable with the same name, either from an outer scope or from the same scope.

fn main() {
    
    
    let a = 5; // 创建变量 a

    {
    
    
        let a = "hello"; // 在新作用域中重新定义了同名变量 a,隐藏了外部作用域中的 a
        println!("Inner scope: {}", a); // 输出新作用域中的 a,值为 "hello"
    } // 这里的 a 超出了内部作用域的范围

    println!("Outer scope: {}", a); // 输出外部作用域中的 a,值为 5,因为内部作用域中的 a 不再可见
}

The redefined a variable in the inner scope hides the variable of the same name in the outer scope a. a redefined in the inner scope is only visible within the scope. After exceeding the scope, when accessing a again, the value in the outer scope will be obtained. . This approach allows you to redefine variables with the same name in the same scope without conflict.

In Rust, variable hiding and variable mutability are two different concepts. With variable hiding, redefined variables with the same name overwrite previously defined variables in the same scope, but their memory locations will exist at the same time, and both can use the same name, depending on their location in the code.

Variable hiding allows variables with the same name to be redefined, and the newly defined variables can have different types. Variable hiding may seem a little obscure at first, but it's very convenient for retaining values ​​after .unwrap() .

The following code demonstrates why the compiler cannot simply reuse a memory location when redefining an immutable variable within a scope, even if the type has not changed.

fn main() {
    
    
    let x: i32 = 5;
    println!("Original x: {}", x);

    {
    
    
        let x: i32 = 10; // 在新的作用域内重新定义了同名变量 x,类型仍为 i32
        println!("Inner x: {}", x); // 输出内部作用域的 x,值为 10
    } // 这里的 x 超出了内部作用域的范围

    println!("Outer x: {}", x); // 输出外部作用域的 x,值为 5,因为内部作用域中的 x 不再可见

    let x: &str = "hello"; // 在同一作用域内,将 x 重新定义为字符串类型
    println!("New x: {}", x); // 输出重新定义的 x,值为 "hello"
}

In the above example, the redefinedx variable hides the same name variable in the outer scope in the inner scopex, but their The memory locations are not the same. Even if the type does not change, the redefined x will still allocate new memory space. This illustrates that variable hiding is different from mutability. Although they both involve changing the value of a variable, variable hiding reallocates memory rather than simply changing the value.

Guess you like

Origin blog.csdn.net/matt45m/article/details/134524357