Published on

Rust 语言概述

Immutable Variables (不可变变量)Constants (常量) 的区别:

1. 声明本质:定义方式与强制类型

  • 变量:使用 let 声明,默认不可变,但可选 mut 变为可变。
  • 常量:必须使用 const 关键字,不允许使用 mut
  • 要求:常量必须手动标注类型(如 const MAX: u32 = 100),而变量通常可以依赖类型推导。

2. 作用域与生命周期:全局可见性

  • 变量:通常存在于函数内部(局部作用域)。
  • 常量:可以在任何作用域声明,包括全局作用域(在所有函数之外)。它们在整个程序运行期间都有效,并在编译时直接内联到使用位置。

3. 赋值时机:编译时 vs 运行时

  • 变量:可以通过运行时计算的结果赋值(例如调用一个函数的返回值)。
  • 常量:只能被设置为 “常量表达式”。这意味着它的值必须在编译阶段就能确定(如:10 * 1024),不能是任何只能在运行时产生的值。

Rust 中的 Shadowing (变量遮蔽)mut (可变性),根据你提供的逻辑,可以总结为以下核心三点:

1. 重新赋值的安全性

  • mut:允许你对变量进行多次赋值,但如果意外地在非预期的地方修改了它,编译器不会报错。
  • Shadowing:必须通过 let 关键字才能修改。这意味着你不能“不小心”修改值,每次修改都是一次显式的声明,能够有效防止逻辑错误。

2. 变换后的状态控制

  • mut:变量在整个生命周期内都是可变的。
  • Shadowing:允许你对值进行一系列转换(Transformations),但转换完成后,如果不加 let,该变量依然保持不可变属性。这能确保数据在经过初始化逻辑后处于“只读”状态。

3. 类型的灵活性

  • mut:变量的类型在声明时就已固定,不能更改(例如不能把 String 改为 usize)。
  • Shadowing:因为本质上是创建了一个全新的变量(只是复用了名字),所以你可以改变值的类型。这在处理诸如字符串转数字(let spaces = " "; let spaces = spaces.len();)时非常优雅。

针对 Rust 中的 Integer Overflow(整数溢出) 处理机制,根据你的描述总结为以下三点:

1. 编译模式决定处理方式

  • Debug 模式:Rust 会包含溢出检查,一旦发生溢出,程序会直接 Panic(崩溃退出)
  • Release 模式:为了性能,Rust 不包含 溢出检查。程序不会 Panic,而是继续运行。

2. 补码截断(Two's Complement Wrapping)

在 Release 模式下,溢出会发生“环绕”现象:

  • 超出最大值的数值会回滚到该类型能表示的最小值。
  • 例如对于 u8 类型(范围 0-255):数值 256 会变成 0257 变成 1

3. 行为定义与风险

  • 逻辑错误:虽然 Release 模式下程序不崩溃,但变量会得到一个非预期的值,这在 Rust 中被视为一种错误行为
  • 最佳实践:开发者不应依赖这种环绕行为。如果确实需要环绕、饱和运算或检查溢出,应显式使用标准库提供的方法(如 wrapping_*, saturating_*, checked_* 等)。

Rust 的内存安全本质上是通过所有权(Ownership)、借用(Borrowing)和生命周期(Lifetimes)这三大基石,将内存管理的决策从运行时(Runtime)提前到了编译时(Compile-time)。

Rust doesn’t care where you define your functions, only that they’re defined somewhere in a scope that can be seen by the caller. Rust 编译器会进行全域扫描,只要函数在当前的编译单元内存在,且符合权限规则,就能成功调用,不受代码行号先后顺序的限制。

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rust’s design: Requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what type you mean. The compiler is also able to give more-helpful error messages if it knows what types the function expects.

Returning Values from Loops

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

Rust 作为**基于表达式(Expression-based)**语言的核心特征,可以总结为以下三点:

1. 语句(Statements):只执行,不返回

  • 定义:语句是执行某种操作的指令,其本身不产生(不返回)任何值
  • 表现:通常以分号 ; 结尾。
  • 限制:你不能把语句赋值给变量(例如 let x = (let y = 6); 在 Rust 中是非法的,因为 let y = 6 是语句)。

2. 表达式(Expressions):计算并返回值

  • 定义:表达式通过计算得到一个结果值
  • 表现:大多数 Rust 代码都是表达式。例如 5 + 6、调用函数、甚至一个大括号包裹的代码块 {}
  • 关键点:表达式不以分号结尾。如果你在表达式末尾加上分号,它就会变成一条语句,不再返回值。

3. 函数体逻辑:语句序列 + 可选表达式

  • 结构:Rust 函数体由一系列语句组成,最后可以紧跟一个表达式。
  • 隐式返回:如果函数末尾是一个表达式(没有分号),该表达式的值将自动作为函数的返回值。这使得代码比显式写 return 更加简洁。

    let x = 5;
    let y = x;

    println!("x = {x}, y = {y}");

We don’t have a call to clone, but x is still valid and wasn’t moved into y.? Types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make.

We call the action of creating a reference borrowing. As in real life, if a person owns something, you can borrow it from them. When you’re done, you have to give it back. You don’t own it.

Mutable references have one big restriction: If you have a mutable reference to a value, you can have no other references to that value.

A data race is similar to a race condition and happens when these three behaviors occur:

Two or more pointers access the same data at the same time. At least one of the pointers is being used to write to the data. There’s no mechanism being used to synchronize access to the data.

Slices let you reference a contiguous sequence of elements in a collection. A slice is a kind of reference, so it does not have ownership.

Defining a function to take a string slice instead of a reference to a String makes our API more general and useful without losing any functionality

!!! The concepts of ownership, borrowing, and slices ensure memory safety in Rust programs at compile time.

Rust accomplishes this by performing monomorphization of the code using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.

Using Traits as Parameters

use the impl Trait syntax

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

Trait Bound Syntax

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

Multiple Trait Bounds with the + Syntax

pub fn notify<T: Summary + Display>(item: &T) {}

Clearer Trait Bounds with where Clauses

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{}

Returning Types That Implement Traits

fn returns_summarizable() -> impl Summary {
    SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    }
}

Closures Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions. Unlike functions, closures can capture values from the scope in which they’re defined.

Closures don’t usually require you to annotate the types of the parameters or the return value like fn functions do.

Closures are typically short and relevant only within a narrow context rather than in any arbitrary scenario. Within these limited contexts, the compiler can infer the types of the parameters and the return type,

Closures can capture values from their environment in three ways

borrowing immutably, borrowing mutably, and taking ownership.

FnOnce applies to closures that can be called once. All closures implement at least this trait because all closures can be called. A closure that moves captured values out of its body will only implement FnOnce and none of the other Fn traits because it can only be called once. FnMut applies to closures that don’t move captured values out of their body but might mutate the captured values. These closures can be called more than once. Fn applies to closures that don’t move captured values out of their body and don’t mutate captured values, as well as closures that capture nothing from their environment. These closures can be called more than once without mutating their environment, which is important in cases such as calling a closure multiple times concurrently.


Smart pointers, on the other hand, are data structures that act like a pointer but also have additional metadata and capabilities.

there is an additional difference between references and smart pointers: While references only borrow data, in many cases smart pointers own the data they point to.

Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the Deref and Drop traits. The Deref trait allows an instance of the smart pointer struct to behave like a reference so that you can write your code to work with either references or smart pointers. The Drop trait allows you to customize the code that’s run when an instance of the smart pointer goes out of scope.

Deref coercion converts a reference to a type that implements the Deref trait into a reference to another type.

Imagine Rc<T> as a TV in a family room.

We use the Rc<T> type when we want to allocate some data on the heap for multiple parts of our program to read and we can’t determine at compile time which part will finish using the data last.

Note that Rc<T> is only for use in single-threaded scenarios.

Via immutable references, Rc<T> allows you to share data between multiple parts of your program for reading only. If Rc<T> allowed you to have multiple mutable references too, you might violate one of the borrowing rules

Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data;


Concurrency:

Thread with move closure Transfer Data Between Threads with Message Passing mpsc stands for multiple producer, single consumer. a channel can have multiple sending ends that produce values but only one receiving end that consumes those values.

The mpsc::channel function returns a tuple, the first element of which is the sending end—the transmitter—and the second element of which is the receiving end—the receiver. The abbreviations tx and rx are traditionally used in many fields for transmitter and receiver, respectively

Transferring Ownership Through Channels

Shared-State Concurrency Message passing is a fine way to handle concurrency, but it’s not the only way. Another method would be for multiple threads to access the same shared data.

Mutex is an abbreviation for mutual exclusion, as in a mutex allows only one thread to access some data at any given time.

You might have noticed that counter is immutable but that we could get a mutable reference to the value inside it; this means Mutex<T> provides interior mutability

Extensible Concurrency with Send and Sync

The Send marker trait indicates that ownership of values of the type implementing Send can be transferred between threads. The Sync marker trait indicates that it is safe for the type implementing Sync to be referenced from multiple threads.

In other words, any type T implements Sync if &T (an immutable reference to T) implements Send, meaning the reference can be sent safely to another thread.

Asynchronous : Futures await<>async`


Trait Object

. A trait object points to both an instance of a type implementing our specified trait and a table used to look up trait methods on that type at runtime.

We create a trait object by specifying some sort of pointer, such as a reference or a Box<T> smart pointer, then the dyn keyword, and then specifying the relevant trait.

When we use trait objects, Rust must use dynamic dispatch.

Dynamic dispatch also prevents the compiler from choosing to inline a method’s code, which in turn prevents some optimizations, and Rust has some rules about where you can and cannot use dynamic dispatch, called dyn compatibility.

简单规则:

如果每个类型只有一个"正确"的实现 → 用关联类型

如果类型可以有多种实现方式 → 用泛型参数

Function Pointers

Functions coerce to the type fn (with a lowercase f), not to be confused with the Fn closure trait. The fn type is called a function pointer. Closures are represented by traits, which means you can’t return closures directly.

Returning a closure from a function using the impl Trait syntax

fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

Using the fn type to accept a function pointer as an argument

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

THE END