安装 rust

无论是 windows 还是 linux、mac,推荐使用 rustup 安装

https://rust-lang.org/tools/install/

通过下方命令测试是否安装

rustc --version

hello world

hello.rust

fn main() {
    println!("Hello, world!");
}

rust 是编译型语言,编译命令

rustc ./hello.rust

运行编译后的二进制文件

./hello

包管理工具 cargo

作为现代化编程语言,管理各种依赖必不可少,如果有了解过 javascript,可与 javascript 的 npm 类比。

使用下方命令测试是否安装 cargo

cargo --version

cargo 的常用命令

可以使用 cargo new 创建项目

可以使用 cargo build 构建项目

可以使用 cargo run 一步构建并运行项目

可以使用 cargo check 在不生成二进制文件的情况下构建项目来检查错误

cargo build —release 发布版本构建项目

写一个猜数字游戏

变量和输入

use std::io;
 
fn main() {
    println!("Guess the number!");
 
    println!("Please input your guess.");
 
    let mut guess = String::new();
 
    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");
 
    println!("You guessed: {guess}");
}

Rust 的变量设计非常独特

在 Rust 中,变量默认是不可变的,这意味着一旦我们给变量赋值,这个值就不可以再修改了。若想要变量可变,需要在定义时加关键字 mut。

String::new() 创建一个空的字符串实例,用于存储用户输入,=把该实例绑定在变量 guess 上。

::new 那一行的 :: 语法表明 new 是 String 类型的一个 关联函数associated function)。关联函数是针对某个类型实现的函数,在这个例子中是 String。这个 new 函数创建了一个新的空字符串。你会发现许多类型上都有一个 new 函数,因为这是为某种类型创建新值的常用函数名。

io::stdin() 产生一个  std::io::Stdin 的实例,调用其 read_line 方法,以获取用户输入,将 &mut guess 作为参数传递给 read_line 函数,让其将用户输入储存到这个字符串中。

read_line 方法返回一个类型为 Result 的值。Result 的成员是 Ok 和 ErrOk 成员表示操作成功,内部包含成功时产生的值。Err 成员则意味着操作失败,并且 Err 中包含有关操作失败的原因或方式的信息。Result 类型的值,像其他类型一样,拥有定义于其实例上的方法。Result 的实例拥有 expect 方法。如果 io::Result 实例的值是 Errexpect 会导致程序崩溃,并输出当做参数传递给 expect 的信息。所以当 read_line 方法返回 Err,则可能是来源于底层操作系统错误的结果。如果 Result 实例的值是 Okexpect 会获取 Ok 中的值并原样返回。在本例中,这个值是用户输入到标准输入中的字节数。

crate

修改 Cargo.toml 为项目引入依赖

[dependencies]
rand = "0.8.5"
use std::io;
 
use rand::Rng;
 
fn main() {
    println!("Guess the number!");
 
    let secret_number = rand::thread_rng().gen_range(1..=100);
 
    println!("The secret number is: {secret_number}");
 
    println!("Please input your guess.");
 
    let mut guess = String::new();
 
    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");
 
    println!("You guessed: {guess}");
}

use rand::Rng;Rng 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。

语法 1..=100 表示范围表达式(range expression),start..=end 这样的形式,它对上下边界均为闭区间

match

use std::cmp::Ordering;
use std::io;
 
use rand::Rng;
 
fn main() {
    // --snip--
    println!("Guess the number!");
 
    let secret_number = rand::thread_rng().gen_range(1..=100);
 
    println!("The secret number is: {secret_number}");
 
    println!("Please input your guess.");
 
    let mut guess = String::new();
 
    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");
 
    let guess: u32 = guess.trim().parse().expect("Please type a number!");
 
    println!("You guessed: {guess}");
 
    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Equal => println!("You win!"),
        Ordering::Greater => println!("Too big!"),
    }
}

match 表达式是 rust 的一大亮点, 它允许将一个值与一系列模式进行比较,并根据匹配的模式执行相应的代码。

guess.cmp(&secret_number) 两者比较的结果有三种情况,<、=、>,进行比较前,一定要将字符串类型的输入转为与 secret 相同的类型,转换为什么类型,是由 let guess: u32 后置类型注解决定的。

Rust 允许用一个新值来 遮蔽 (Shadowing) guess 之前的值。这个功能允许我们复用 guess 变量的名字

循环流程控制

    // --snip--
 
    println!("The secret number is: {secret_number}");
 
    loop {
        println!("Please input your guess.");
 
        // --snip--
 
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => { println!("You win!"); break; }
        }
    }
}

loop 关键字创建一个无限循环。break语句允许在猜测结果正确后退出循环。

异常输入处理

当我们的输入不是一个合法的数字时,让程序跳过当前的循环进入下一次循环

仍然利用了 parse() 的返回结果是一个枚举类型,对于正确的输入直接得到转换后的数字,对于错误的输入使用 continue 语句跳过当前循环。

        // --snip--
 
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");
 
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
 
        println!("You guessed: {guess}");
 
        // --snip--
 

下面是最终代码

use std::cmp::Ordering;
use std::io;
 
use rand::Rng;
 
fn main() {
    println!("Guess the number!");
 
    let secret_number = rand::thread_rng().gen_range(1..=100);
 
    loop {
        println!("Please input your guess.");
 
        let mut guess = String::new();
 
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");
 
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
 
        println!("You guessed: {guess}");
 
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

编程基本概念

变量 let

可变变量 let mut

常量 const

遮蔽 变量名复用

数据类型

分为标量和复合类型

标量:整形、浮点型、布尔型、字符型

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
架构相关isizeusize
数字字面值例子
Decimal (十进制)98_222
Hex (十六进制)0xff
Octal (八进制)0o77
Binary (二进制)0b1111_0000
Byte (单字节字符)(仅限于u8)b'A'

两个原生的复合类型:数组和元组

    let tup1: (i32, f64, u8) = (500, 6.4, 1);
    let tup2 = (500, 6.4, 1);
    let (x, y, z) = tup2;
 
    println!("{x}, {y}, {z}");
    println!("{0}, {1}, {2}", tup2.0, tup2.1, tup2.2);
 
    let a = [1, 2, 3, 4, 5];
    let b: [i32; 5] = [1, 2, 3, 4, 5];
    let c = [3; 5];
    println!("{:?}", a);
    println!("{:?}", b);
    println!("{:?}", c);

函数

Rust 是一门基于表达式(expression-based)的语言

一个算式不加分号就是表达式,可以作为函数的返回

控制流

数字不能隐式转化为 bool 类型

if

fn main() {
    let number = 6;
 
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

行内 if

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };
 
    println!("The value of number is: {number}");
}

循环标签

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;
 
        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }
 
        count += 1;
    }
    println!("End count = {count}");
}

while

for

fn main() {
    let a = [10, 20, 30, 40, 50];
 
    for element in a {
        println!("the value is: {element}");
    }
}
fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

所有权

所有权

fn ownership() {
    let s1 = String::from("hello");
    let s2 = s1;
    let s3 = s2.clone();
    // println!("s1 is: {s1}"); error
    println!("s2 is: {s2}");
    println!("s3 is: {s3}");
}
  1. Rust 中的每一个值都有一个 所有者owner)。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者离开作用域,这个值将被丢弃。

变量并非拥有值,而是拥有对值的管理权

复合数据类型,默认按照引用操作,变量赋值时默认进行移动语义,若要深拷贝使用 clone 方法

标量数据类型,按照值操作,变量赋值时进行复制

Copy trait :将该数据类型作为值类型操作,实现了 Copy 的数据类型执行拷贝操作不进行所有权的转移

drop // to do

作用域

与其他编程语言类似

引用和借用

fn ref_and_borrow() {
    let s1 = String::from("hello");
    let mut s2 = String::from("hello");
    change_string(&mut s2);
    let len = calculate_length(&s1);
    println!("The length of '{s1}' is {len}.");
    println!("s2 is: {s2}");
}
 
fn change_string(s: &mut String) {
    s.push_str(", world");
}
fn calculate_length(s: &String) -> usize {
    println!("s is: {}", s);
    s.len()
}
 

calculate_length 函数参数 s 是一个只读引用,change_string 函数参数 s 是一个可变引用。

在同一的作用域下,可以存在多个只读引用,在没有只读引用时只能有一个可变引用

在这一点上可以参考操作系统读者-写者问题

悬垂引用(空指针)

fn main() {
    let reference_to_nothing = dangle();
}
 
fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

引用的生命周期不允许超过所有者的生命周期

这是一段错误的代码,函数 dangle 返回了一个悬垂引用,这个引用的生命周期超过了 s 所有者的生命周期, rust 语言不允许并触发编译错误。

silce

    let my_string = String::from("hello world");
 
    // `first_word` 适用于 `String`(的 slice),部分或全部
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    // `first_word` 也适用于 `String` 的引用,
    // 这等价于整个 `String` 的 slice
    let word = first_word(&my_string);
 
    let my_string_literal = "hello world";
 
    // `first_word` 适用于字符串字面值,部分或全部
    let word = first_word(&my_string_literal[0..6]);
    let word = first_word(&my_string_literal[..]);
 
    // 因为字符串字面值已经 **是** 字符串 slice 了,
    // 这也是适用的,无需 slice 语法!
    let word = first_word(my_string_literal);

结构体

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