安装 rust
无论是 windows 还是 linux、mac,推荐使用 rustup 安装
https://rust-lang.org/tools/install/
通过下方命令测试是否安装
rustc --versionhello world
hello.rust
fn main() {
println!("Hello, world!");
}rust 是编译型语言,编译命令
rustc ./hello.rust运行编译后的二进制文件
./hello包管理工具 cargo
作为现代化编程语言,管理各种依赖必不可少,如果有了解过 javascript,可与 javascript 的 npm 类比。
使用下方命令测试是否安装 cargo
cargo --versioncargo 的常用命令
可以使用 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和Err,Ok成员表示操作成功,内部包含成功时产生的值。Err成员则意味着操作失败,并且Err中包含有关操作失败的原因或方式的信息。Result类型的值,像其他类型一样,拥有定义于其实例上的方法。Result的实例拥有expect方法。如果io::Result实例的值是Err,expect会导致程序崩溃,并输出当做参数传递给expect的信息。所以当read_line方法返回Err,则可能是来源于底层操作系统错误的结果。如果Result实例的值是Ok,expect会获取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-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| 架构相关 | isize | usize |
| 数字字面值 | 例子 |
|---|---|
| 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}");
}- Rust 中的每一个值都有一个 所有者(owner)。
- 值在任一时刻有且只有一个所有者。
- 当所有者离开作用域,这个值将被丢弃。
变量并非拥有值,而是拥有对值的管理权
复合数据类型,默认按照引用操作,变量赋值时默认进行移动语义,若要深拷贝使用 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,
}