如何快速学习 Rust 编程?


这篇博文的目标是帮助 Rust 初学者克服 Rust 是一门困难语言的观念。

首先也是最重要的,推荐布朗大学的 Rust 书: https: //rust-book.cs.brown.edu/(它要求您一直向下滚动并接受参与)。
它提供了对 Rust 的更全面的解释,并包括简单的测验来测试您新获得的知识。原书不包含测验,因此许多人认为他们了解 Rust,但实际上完全错误。

除了这本书之外,您还需要使用本地编程环境或在线环境https://play.rust-lang.org/来测试示例

如何设置本地工作区(如果您喜欢在线 Rust Playground,请跳过)
我假设 Rust 已经安装在您的系统上。

创建一个新目录

mkdir project && cd project

为工作区手动创建 Cargo.toml

vim Cargo.toml
[workspace]

members = [
    "app",
    
"applib",
]

初始化二进制目录和库目录

cargo init --bin app && cargo init --lib applib

通过编辑使 applib 在应用程序内部可用app/Cargo.toml

[package]
name = "app"
version =
"0.1.0"
edition =
"2021"

[dependencies]
add this
applib = {path =
"../applib"}

现在 applib 的函数可以导入到应用程序二进制文件中。编辑app/src/main.rs

use applib;

fn main() {
   // applib::add() is located in applib/src/lib.rs
   println!(
"100 + 100 = {}", applib::add(100, 100)); 
}
并运行它:

cargo run --bin app

# 200 

内存安全
我相信 Rust 的内存安全特性让大多数人对这种语言感到恐惧。然而,这使得它成为一种不需要垃圾收集器的安全语言。掌握这部分语言对于编写内存安全代码至关重要。

好消息是,即使您编写不安全的代码,它也无法编译。

所有权
由于不存在垃圾收集器,拥有的变量一旦超出作用域范围就会被破坏。本质上,一旦函数或块表达式返回。除非返回变量或通过借用传递变量(也称为按引用传递)。

以下变量在程序的整个持续时间内都是不可变的。

fn main() {
    let num = 14;
    println!("{}", num); // prints 14
}

注意:注意 num 的类型是如何隐式赋值的;const 变量需要显式分配类型,如下所示:

const NUM: i32 = 10;

为了使其可变,我们必须添加关键字 mut:

fn main() {
    let mut num = 14;
    num = 100;
    println!("{}", num); // prints 100
}

假设我们将此变量传递给函数

fn plusOne(num: i32) {
    println!("{}", num + 1); // prints 101


fn main() {
   let num = 14; 
   plusOne(num);
   println!(
"{}", num); // prints 100
}

请注意代码如何调用 println!是在调用函数 plusOne 后。

通常 Rust 不会编译这个程序,因为传递到函数中的任何变量(没有与号 &)都会破坏该变量。
然而,Rust 原语(例如 u64)实现了Copy特征。函数 plusOne 隐式接收了变量 num 的副本,因此我们没有转移所有权。

让我们看看 Rust 如何转移未实现 Copy 的结构的所有权

struct Person{
    name: String
}

fn getName(pers: Person) {
    println!("name is {}", pers.name); // drops the variable
}

fn main() {
    let Carl = Person{name: String::from(
"Carl")};
    getName(Carl);
// prints name is Carl
   
// but we can no longer user Carl as it was dropped 
   
// println!("{}", Carl.name); would not work here as it did with num above
}

为了使用 Carl,在调用 getName 后,我们需要使用 & 将其作为引用传递

... 

fn getName(pers: &Person) {
    println!("name is {}", pers.name); // does not drop the variable
}

fn main() {
    let Carl = Person{name: String::from(
"Carl")};
   
// placing an & before the variable passes it as borrowed
    getName(&Carl);
// prints name is Carl
    println!(
"{}", Carl.name); // prints Carl 
}

我们还可以将 Carl 的所有权转移到另一个变量,就像我们可以转移到函数中一样。

...

fn main() {
    let Carl = Person{name: String::from("Carl")};            
   
// move ownership
    let Carl2 = Carl;

   
// Carl is no longer available 
   
// Carl2 is available
    println!(
"{}", Carl2.name); // prints Carl
}

移动变量时也可以将其转换为可变变量

...

fn main() {
    let Carl = Person{name: String::from("Carl")};            
   
// move to mutable ownership
    let mut Carl2 = Carl;
    Carl2.name =
"Carl2".to_string();

    println!(
"{}", Carl2.name); // prints Carl2
}

或者,函数可以采用可变借用并更改值而不删除变量。

...

// changes the borrowed variable without dropping it 
fn changeName(pers: &mut Person) {
    pers.name =
"Carl2".to_string();
}

fn main() {
    let Carl = Person{name: String::from(
"Carl")};            
    let mut Carl2 = Carl;

    changeName(&mut Carl2);
    println!(
"{}", Carl2.name); // prints Carl2
}

但是,如果变量是原始类型,则所有权不会转移。

fn main() {
    let a = 10;
    // a is cloned and thus is not dropped
    let b = a; 

    println!(
"{a} and {b} are clones"); // prints 10 and 10
}

虽然变量可以可变地借用,但它也不能一成不变地借用。只有当该变量不再被借用者引用时,才可以再次借用。

struct Person {
    name: String
}

fn main() {
    let mut Carl = Person{name: "Carl".to_string()}; 
   
// borrow Carl mutably
    let borrowCarlMutably = &mut Carl;
   
// Carl's name is indirectly changed to Carl2
    borrowCarlMutably.name =
"Carl2".to_string();
    
   
// Carl cannot be assigned to an immutable variable as so:
   
// let c = &Carl;
    println!(
"{}", borrowCarlMutably.name); // prints Carl2

   
// Carl can now again be borrowed immutably because 
   
// borrowCarlMutably is no longer referenced

    let borrowCarlImmutably = &Carl;
    println!(
"{}", borrowCarlImmutably.name); // prints Carl2

   
// Carl is still the owner as we only borrowed it above 
   
// Now that the mutable borrow is dropped we can use it again
    println!(
"{}", Carl.name); // prints Carl2
}

存储为Option类型的变量将在 match 语句中删除,除非删除的变量带有前缀ref。

fn main(){
    let name = Some(String::from("Carl"));
    match name {
       
// notice the ref keyword
       
// Using Some(n) .. will not compile 
        Some(ref n) => println!(
"Hello {}", n),
        _ => println!(
"no value"),
    }
   
// if ref is not added, this would cause the program to not compile 
   
// since name would have been dropped in the match statement
    println!(
"Hello again {}", name.unwrap());
}


生命周期
Rust 还要求借用变量的数据具有生命周期。很简单,因为您不希望借用的变量在使用完毕之前被删除。

事实上,我们在上面的代码中声明的可变变量正在实现生命周期。

这段代码不会编译,因为当我们使用 Carl 时,borrowCarlMutically 的生命周期就结束了。

...

fn main() {
    let mut Carl = Person{name: "Carl".to_string()}; 
   
// borrow Carl mutably
    let borrowCarlMutably = &mut Carl;

   
// using Carl means borrowCarlMutably can no longer be used
   
// because its lifetime has gone out of scope 
    println!(
"{}", Carl.name); 
    
   
// WRONG! 
   
// this should be moved above println before using Carl 
    borrowCarlMutably.name =
"john".to_string();
}

到目前为止,我们还没有看到生命周期的语法,尽管我已经将借用的变量传递给上面的函数。
原因是 Rust 忽略(省略)它们是为了简单的函数,不会导致编译器决定返回哪个。

struct Person {
    name: String
}

// Elided 
fn changeName(pers: &mut Person) {
    pers.name =
"joe".to_string();
}

// Expanded
fn changeName<'a>(pers: &'a mut Person) {
    pers.name =
"joe".to_string();
}

fn main() {
    let mut Carl = Person{name:
"Carl".to_string()}; 
    changeName(&mut Carl);
    println!(
"{}", Carl.name);
}

不过,如果函数比较复杂,需要借用不同生命周期的变量,则需要明确告知编译器。
需要注意的是,生命周期的语法中需要使用省略号,但名称可以是任何名称。使用不同的字母表示不同的生命周期(如 "a"、"b")是一种常见的惯例。

struct Person {
    name: String
}

fn changeName<'a, 'b>(pers: &'a mut Person, newName: &'b str) {
    pers.name = newName.to_string();
}

fn main() {
    let mut Carl = Person{name: "Carl".to_string()}; 
    
    {
       
// variables in this scope have different lifetimes 
       
// than those outside of {}
        let newName =
"Mario";
        changeName(&mut Carl, newName);
    }
    println!(
"{}", Carl.name); // prints Mario
}

本质上,Rust 确保从函数返回的任何借用值的生命周期至少与输入之一的生命周期一样长。然后编译器可以决定您的代码是否有效。

struct Person {
    name: String
}

// now we return a borrowed variable with a lifetime 
// of newName
fn changeName<'a, 'b>(pers: &'a mut Person, newName: &'b str) -> &'b str {
    pers.name = newName.to_string();
    &newName
}

fn main() {
    let mut Carl = Person{name:
"Carl".to_string()}; 
    {
       let newName =
"Mario";
       let _ =  changeName(&mut Carl, newName);
    }
    println!(
"{}", Carl.name);
}

存在一个名为 的保留生命周期'static,它向编译器表明该变量将在程序的整个生命周期内存活。该变量将嵌入到二进制文件中。

static GLOBAL: &'static str = "global static variable";

fn main() {
    println!(
"{}", GLOBAL);
}

在我看来,人们与借用检查器的斗争很大程度上源于与所有权和生命周期相关的知识差距。