Rust borrows the let keyword from functional languages to create variables. The variables created by lets are commonly referred to as bindings, which indicate an association between identifiers and values.

Rust positional and value expressions

Expressions in Rust can generally be divided into Place Expression and Value Expression.

Position expression

Location expressions are expressions that represent memory locations. Divided into

  • The local variable
  • A static variable
  • Dereference (*expr)
  • Array index (expr[expr])
  • Field reference (expr. Field)
  • Positional expression combination

A location expression can be used to read or write to the memory of a data unit. It’s basically writing, which is why positional expressions can be assigned.

Value expressions

Any expression other than a positional expression is a value expression. Value expressions General values refer to data in a storage unit address. Equivalent to data value, can only be read.

Semantically, location expressions represent data persistence data and value expressions represent temporary data. Positional expressions usually have persistent state, and value expressions are usually literal or temporary values created for overevaluation of the expression.

The evaluation process of an expression can have different results in different contexts. The evaluation Context is also divided into Place Context and Value Context

Location context

  • The operand to the left of an assignment or compound assignment statementa = b + cWhere a is the location context
  • The independent operands of unary expressions.
  • Contains operands that implicitly borrow (reference).
  • The right side of the match discriminant or let binding is also a location context when ref pattern matching is used.

Everything else is a value context. Value expressions cannot appear in positional contexts. The following is an example:

pub fn temp() -> i32 { return 1; } fn main () { let x = &temp(); // The temp function call is an invalid positional expression temp() = *x; // Error}Copy the code

The call to temp is placed in the position context to the left of the assignment statement. The compiler reports an error because temp is calling an invalid position expression, which is a value expression.

Non-binding versus variable binding

Positional expressions declared with the let keyword are immutable by default, immutable bindings. In simple terms, let variables are immutable, similar to the const keyword in JavaScript.

fn main () { let a = 1; // a = 2; immutable and error let mut b = 2; // add variable statement b = 3; }Copy the code

With the MUT keyword, you can declare mutable positional expressions, mutable bindings, and mutable bindings can be modified and assigned normally.

Semantically, the immutable binding declared by the let default can only read the corresponding storage unit, while the mutable binding declared by the LET MUt can write to the corresponding storage unit.

Ownership and Reference

When a location expression appears in a value context, it transfers the memory address to another location expression, which is essentially a transfer of ownership.

fn main () { let place1 = "hello"; let place2 = "hello".to_string(); let other = place1; println! (" {:? }", other); let other = place2; println("{:? }", other) // error }Copy the code

Two bindings, place1 and place2, are declared using let in the code above. Place1 is then assigned to the new variable other. Because Place1 is a positional expression that appears on the right side of the assignment operator, in a value context, Place1 transfers memory addresses to other. Similarly, if place2 is copied to the newly declared Other, the memory address of Place2 is also transferred to other.

The second declaration that other assigns the place2 address to other raises an error indicating that a value has been moved. This difference is due to underlying memory security management. Although there are differences between these two behaviors, Rust deliberately performed them for safety.

Semantically, each variable binding actually owns the OwnerShip of the storage unit. Such behavior of transferring memory address is the transfer of OwnerShip. In Rust, Move semantics, while non-transfer semantics are actually a kind of Copy semantics. Rust has no GC, so memory management is entirely proprietary.

In development, there is generally no need to transfer ownership. Rust provides the reference operator (&), which directly retrieves the address of the storage unit, or memory, of an expression. Memory can be read from this memory location.

Fn main () {let a = [1,2,3]; let b = &a; // Get memory address, equivalent to println! ("{:p}", b); let mut c = vec! [1, 2, 3]; let d = &mut c; d.push(4); println! (" {:? }", d); }Copy the code

In the above code, B gets the memory address of A by &. This method does not cause a transfer of ownership, because the reference operator has changed the right side of the assignment expression into a location context, which shares the memory location with println! The macro specifies the {:p} format to print the pointer address of B, which is the memory address.

Let mut is used to declare the dynamic constant array C. &mut is used to obtain a mutable reference to C and assign it to D. For D’s push operation, a new element 4 is inserted. To get a mutable reference, you must first declare a mutable binding.

Either &a or &mut c is equivalent to borrowing the ownership of A and C, because A and C still retain their ownership, so a reference is also called borrowing.