Rust Basic Syntax

Variables, basic types, functions, comments, and control flow are programming concepts found in almost every programming language.

These fundamental concepts will exist in every Rust program. Learning them early will allow you to learn Rust usage as quickly as possible.

Variables

First of all, it must be noted that Rust is a strongly typed language, but it has the ability to automatically infer variable types. This can easily be confused with weakly typed languages.

By default, variables in Rust are immutable, unless they are declared as mutable using the `mut` keyword.

let a = 123;       // 不可变变量
let mut b = 10;  // 可变变量

To declare a variable, you need to use theletkeyword. For example:

let a = 123;

Developers who have only learned JavaScript are very sensitive to this statement, while developers who have only learned C find it hard to understand.

After this declaration, the following three lines of code are all forbidden:

a = "abc";
a = 4.56; 
a = 456;

The error in the first line is that after declaring `a` as 123, `a` is determined to be an integer, and you cannot assign a string value to it.

The error in the second line is that automatic conversion would lose numeric precision, and Rust does not allow automatic type conversions that lose precision.

The error in the third line is that `a` is not a mutable variable.

The first two errors are easy to understand, but what does the third one mean? Isn't `a` a variable?

This relates to Rust's design for high-concurrency safety: at the language level, it tries to minimize the situations where variable values can be changed. So the value of `a` is immutable. But this does not mean that `a` is not a "variable" (the English word `variable`); the official documentation calls variables like `a` "immutable variables".

If part of our program runs under the assumption that a value will never change, while another part of our code changes that value, then the first part may not behave as intended. Errors caused by this are hard to find afterwards. This is the reason Rust designed this mechanism.

Of course, making a variable "mutable" only requires themutkeyword.

let mut a = 123;
a = 456;

This program is correct.

Difference between Constants and Immutable Variables

Since immutable variables are immutable, aren't they just constants? Why are they called variables?

There is still a difference between variables and constants. In Rust, the following program is valid:

let a = 123;   // 可以编译,但可能有警告,因为该变量没有被使用
let a = 456;

But if `a` is a constant, it is invalid:

const a: i32 = 123;
let a = 456;

A variable's value can be "rebound", but it cannot be changed on its own before being "rebound". This ensures that within each region after a "binding", the compiler can fully reason about the program logic. Although Rust has automatic type inference, in some cases declaring the type is more convenient:

let a: u64 = 123;

Here `a` is declared as an unsigned 64-bit integer variable. If the type were not declared, `a` would automatically be inferred as a signed 32-bit integer variable, which has a significant impact on the range of values `a` can hold.

Data Types

Rust is a statically typed language. You can explicitly specify types when declaring variables, but usually you can rely on type inference.

Basic types:i32 (32-bit signed integer), u32 (32-bit unsigned integer), f64 (64-bit floating point), bool (boolean), char (character)

Example

let x: i32 = 42;
let y: f64 = 3.14;
let is_true: bool = true;
let letter: char = 'A';

Functions

Rust functions are defined using thefnkeyword, and the return type is specified using the arrow symbol->.

Example

fn add(a: i32, b: i32) -> i32 {
    a + b
}

If a function has no return value, the type defaults to()(i.e., the empty tuple).

Control Flow

`if` expression

Example

let number = 7;
if number < 5 {
    println!("less than 5");
} else {
    println!("greater than or equal to 5");
}

`loop` loop:`loop` is an infinite loop in Rust; you can use `break` to exit the loop.

Example

let mut counter = 0;
loop {
    counter += 1;
    if counter == 10 {
        break;
    }
}

`while` loop

Example

let mut number = 3;
while number != 0 {
    println!("{}!", number);
    number -= 1;
}

`for` loop

Example

for number in 1..4 {
    println!("{}!", number);
}

Ownership

Ownership in Rust is a unique memory management mechanism. Its core concepts include ownership, borrowing, and references.

Ownership rules:

  • Every value in Rust has a single owner.
  • Each value can have only one owner at any given time.
  • When the owner goes out of scope, the value will be dropped.
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被转移给了 s2
// println!("{}", s1); // 此处编译会报错,因为 s1 已不再拥有该值

Borrowing and references:Borrowing allows you to reference data without taking ownership, implemented via the&symbol.

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);  // 借用
    println!("The length of '{}' is {}.", s, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Structs

Structs are used to create custom types, and their fields can contain multiple data types.

Example

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

let user1 = User {
    username: String::from("someusername"),
    email: String::from("[email protected]"),
    sign_in_count: 1,
    active: true,
};

Enums

Enums allow you to define one of several possible data types.

Example

enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

Pattern Matching (match)

`match` is a powerful control flow tool in Rust, similar to a `switch` statement.

Example

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,
    }
}

Error Handling

Rust has two main ways of error handling:Result<T, E>andOption<T>。

Result:

Example

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

Option:

Example

fn get_element(index: usize, vec: &Vec<i32>) -> Option<i32> {
    if index < vec.len() {
        Some(vec[index])
    } else {
        None
    }
}

Lifetimes of Ownership and Borrowing

Rust uses lifetimes to ensure references are valid. Lifetime annotations are denoted by'aetc., but in common cases, the compiler will automatically infer them.

Example

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Shadowing

The concept of shadowing is different from "override" or "overload" in other object-oriented languages. Shadowing is what was called "rebinding" earlier; the quotes were used to substitute the concept before it was introduced.

Shadowing refers to the mechanism by which a variable's name can be reused:

Example

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;
    println!("The value of x is: {}", x);
}

The output of this program is:

The value of x is: 12

Shadowing is not the same concept as assignment to a mutable variable. Shadowing means using the same name to represent another variable entity, and its type, mutability, and value can all change. However, assignment to a mutable variable can only change its value.

let mut s = "123";
s = s.len();

This program will cause an error: you cannot assign an integer value to a string variable.

Other Extensions