⚠️ 本文通过说明
本文通过阅读 Rust 程序设计语言 中文版 学习总结而来。
Rust 程序设计语言
Hello 系列
Hello World
执行命令:
mkdir hello_world
cd hello_world
vim main.rs
输入内容:
fn main() {
println!("Hello, World!");
}
执行命令:
rustc .\main.rs
.\main
将会输出
Hello, World!
Hello Cargo
执行命令:
cargo new hello_cargo
cd hello_cargo
文件夹中将会存在(忽略 git 相关文件):
src
: Rust 源代码目录Cargo.toml
src 文件夹会有一个简单的输出 Hello World 的 main.rs 文件。
Cargo.toml 文件
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
[dependencies]
package
: 表示配置包信息name
: 项目的名称version
: 版本edition
: Rust 大版本号(The Rust Edition Guide)
dependencies
: 配置项目的依赖
执行命令:
cargo build
这个项目会在
.\target\debug
下创建可执行文件hello_cargo
。可通过添加 --release 选项还优化编译,那么生成的二进制文件将在
.\target\release
文件夹中。
执行命令:
cargo run
这个命令会构建完成后自动执行二进制文件。
执行命令:
cargo check
检查代码是否可以编译,并不生成可执行文件。
rustc
与 Cargo
对于简单项目,使用 Cargo 和直接使用 rustc
相比并没有太大的优势, 但拥有多个 crate 的复杂项目,交给 Cargo 来协调构建将简单得多。
猜数字游戏
游戏说明
程序会随机生成一个 1 到 100 之间的整数。接着它会提示玩家猜一个数并输入,然后指出猜测是大了还是小了。如果猜对了,它会打印祝贺信息并退出。
实现步骤
创建空项目
cargo new guessing_game
cd guessing_game
修改 main.rs
文件,实现一个读取一次输入值
use std::io;
fn main() {
println!("猜数字游戏启动!");
println!("请输入你的猜测:");
// 创建可变变量
// 并绑定到空的 String 实例
let mut guess = String::new();
// 从标准输入流中读取数据
io::stdin()
.read_line(&mut guess)
.expect("读取输入失败");
println!("你的猜测为:{}", guess);
}
执行
cargo run
将会提示输入一个数字,输入后将输出这个数字
添加 rand crate
cargo add rand
会在
Cargo.toml
的[dependencies]
部分添加rand
crate该 crate 用来生成随机数
修改 src/main.rs
文件,生成一个随机答案
use std::io;
use rand::Rng;
fn main() {
println!("猜数字游戏启动!");
// 创建答案变量,并绑定随机数
let secret_number = rand::thread_rng().gen_range(1..101);
// 输出答案,不调试时注释掉
println!("答案值:{}", secret_number);
println!("请输入你的猜测:");
// 创建可变变量
// 并绑定到空的 String 实例
let mut guess = String::new();
// 从标准输入流中读取数据
io::stdin()
.read_line(&mut guess)
.expect("读取输入失败");
println!("你的猜测为:{}", guess);
}
use rand::Rng;
的作用是导入随机数相关实现方法的 trait
1..101
即[1, 100]
也可以写成1..=100
执行
cargo run
将会输出一个随机数
继续修改 src/main.rs
文件,实现判断猜的数和答案的关系
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("猜数字游戏启动!");
// 创建答案变量,并绑定随机数
let secret_number = rand::thread_rng().gen_range(1..101);
// 输出答案,不调试时注释掉
println!("答案值:{}", secret_number);
println!("请输入你的猜测:");
// 创建可变变量
// 并绑定到空的 String 实例
let mut guess = String::new();
// 从标准输入流中读取数据
io::stdin()
.read_line(&mut guess)
.expect("读取输入失败");
let guess: u32 = guess.trim().parse().expect("请输入一个正确的数字");
println!("你的猜测为:{}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("太小了"),
Ordering::Greater => println!("太大了"),
Ordering::Equal => println!("你赢了"),
}
}
第 2 行导入 Ordering 枚举
第 25 行将 guess 字符串转化为 u32 类型
Rust 允许定义相同的变量名称,新的变量将会遮蔽(shadow)旧的变量
继续修改 src/main.rs
文件,实现允许循环输入
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("猜数字游戏启动!");
// 创建答案变量,并绑定随机数
let secret_number = rand::thread_rng().gen_range(1..101);
// 输出答案,不调试时注释掉
println!("答案值:{}", secret_number);
loop {
println!("请输入你的猜测:");
// 创建可变变量
// 并绑定到空的 String 实例
let mut guess = String::new();
// 从标准输入流中读取数据
io::stdin()
.read_line(&mut guess)
.expect("读取输入失败");
let guess: u32 = guess.trim().parse().expect("请输入一个正确的数字");
println!("你的猜测为:{}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("太小了"),
Ordering::Greater => println!("太大了"),
Ordering::Equal => println!("你赢了"),
Ordering::Equal => {
println!("你赢了");
break;
}
}
}
}
所有权
所有权(ownership)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收器(garbage collector)即可保证内存安全。
Rust 的内存管理方式是通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。
所有权规则
- Rust 的每一个值都有一个被称为所有者(owner)的变量。
- 值在任何时候有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
移动
对于大小固定的简单值,Rust 会将其存储在栈内存中并且实现了 Copy trait
,所以如下代码两个变量皆有效。
fn main() {
let x = 5;
let y = x;
println!("x = {}", x);
println!("y = {}", y);
}
实现了 Copy trait
的类型
- 所有整数类型
- 布尔类型
- 所有浮点数类型
- 字符类型
- 元组(但是需要元组内部的类型也都实现了
Copy trait
)
而对于没有实现 Copy trait
的类型,将变量赋值给其它变量将会移交所有权。
fn main() {
let s1 = String::from("Hello");
let s2 = s1;
println!("s1 = {}", s1); // 这行代码会编译失败: value borrowed here after move
println!("s2 = {}", s2);
}
因为
s1
的所有权通过移动交给了s2
导致s1
不在有效。
克隆
对于存储在堆内容的值,可通过使用 .clone()
方法进行深拷贝,重新创建一个变量,而不是移交所有权。
fn main() {
let s1 = String::from("Hello");
let s2 = s1.clone();
println!("s1 = {}", s1);
println!("s2 = {}", s2);
}
上面的 移动 和 克隆 对函数调用也同样有效,没有实现 Copy trait
的类型,调用函数后也会移交所有权,从而导致调用函数后无法在使用。
Rust 支持通过返回值移动所有权
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("'{}' 的长度为 {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length)
}
借用和引用
上面通过函数移动授权,需要重新 let 一个字符串变量,有点麻烦, Rust 通过借用和引用来解决这个问题,通过将值的借给调用的函数,函数只有拥有这个值的引用,可直接对值进行读取。
默认情况下,引用只能读取值,不能通过修改,就像声明变量一样,需要显示声明为要修改的可变引用。
引用 和 可变引用 示例
fn main() {
let mut s = String::form("hello");
{
let r1 = &s; // r1 的类型为 &String
println!("r1 = {}", r1);
}
// 因为上面只是将 s 的值借用给了 r1,所以 s 还拥有值的所有权
{
let r2 = &mut s; // 因为 Rust 限制,同一作用域只能存在一个可变引用(不可变可以存在多个,但是存在可变,就不能存在不可变),所以通过 {} 代码块隔离作用域。
r2.push_str(", world!");
println!("r2 = {}", r2);
}
pringln!("s = {}", s);
}
引用的规则:
- 同一作用域可以存在多个引用,但是如果存在可变引用,那么只能存在可变引用,且只能存在一个。
- 引用必须总是有效的,引用不能超过所有权的作用范围。
结构体
定义一个 Rectangle
结构体,并定义 area
方法计算矩形的面积,和 new
关联方法创建矩形实例。
#[derive(Debug)]
struct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn area(&self) -> i32 {
self.width * self.height
}
fn new(width: i32, height: i32) -> Rectangle {
Rectangle {
width: width,
height: height,
}
}
}
fn main() {
let rect = Rectangle::new();
println!("area = {}", rect.area());
println!("rect = {:?}", rect);
}