变量
在 rust 中,变量分为可变变量和不可变变量
不可变变量和常量的区别是:
常量是在编译时就确定的,而不可变变量可以在运行时确定,常量后面可以跟上常量表达式
// 常量
const MAX_POINTS = 1 + 2 + 3;
// 不可变变量
let x = calculate();
隐藏
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x); // 12
溢出处理
在 rust 中如果两个数相加产生了溢出,在编译时会报错:attempt to add with overflow
如果想要忽略溢出,可以使用 wrapping_add 方法,如果想要检查溢出,可以使用 overflowing_add 方法
fn main() {
let a: u32 = 4294967295;
let b: u32 = 1;
let sum = a.wrapping_add(b);
println!("The sum of {} and {} is {}", a, b, sum);
let (sum, is_overflow) = a.overflowing_add(b);
println!(
"The sum of {} and {} is {}, is_overflow: {}",
a, b, sum, is_overflow
);
}
元组
元组可以由不同类型的元素组成
let a = 10;
let b = true;
let tuple = (a, b);
获取元组中的元素有两种方式:
- 使用模式匹配:
let (c, d) = tuple; - 使用索引:
let one = tuple.0;
使用索引访问不存在的元素会报错no field 2 on type ({integer}, bool)
let (c, d) = tuple;
println!("c = {}, d = {}", c, d);
let one = tuple.0;
let two = tuple.1;
println!("one = {}, two = {}", one, two);
获取元组的长度可以使用标准库 std::mem::size_of_val
let size = std::mem::size_of_val(&tuple);
println!("size = {}", size);
数组
数组中的元素必须是相同类型的
let array = [1, 2, 3, 4, 5];
访问数组中的元素:array[0],如果访问越界
在编译时报错:this operation will panic at runtime
在运行时报错:
thread 'main' panicked at src/main.rs:5:35:
index out of bounds: the len is 5 but the index is 5
如果数组中元素都是同一个,可以这样声明:
let array: [i32; 5] = [1; 5];
println!("{:?}", array); // [1, 1, 1, 1, 1]
切片
切片是对一个数组(包括固定大小数组和动态数组)的引用片段,有利于安全有效地访问数组的一部分,而不需要拷贝数组或者数组中的内容
切片在编译的时候长度是未知的,在底层实现上,一个切片保存着两个 usize 成员,第一个 usize 成员指向切片起始位置,第二个 usize 成员表示切片长度
let arr = [1, 2, 3, 4, 5];
let slice = &arr[0..3];
println!("{:?}", slice); // [1, 2, 3]
println!("len: {:?}", slice.len()); // 3
println!("slice[0]: {:?}", slice[0]); // 1
如果是一个可变的 slice,使用 &mut 声明,当改了 slice 中的元素,arr 相对应的元素也会被修改
let mut arr = [1, 2, 3, 4, 5];
let slice = &mut arr[..];
slice[0] = 10;
println!("slice[0]: {:?}", slice[0]); // 10
println!("arr[0]: {:?}", arr[0]); // 10
结构体
元组结构:
struct Pair(i32, f32);
let pair = Pair(1, 1.0);
println!("{}", pair.0); // 1
常规结构:
struct Point {
x: i32,
y: i32,
}
let point = Point { x: 1, y: 1 };
println!("{}", point.x); // 1
println!("{}", point.y); // 1
单元结构(无字段,通常在泛型里使用较多):
struct Unit;
let unit = Unit;
在开发环境下可以使用 #[derive(Debug)] 注解来自动生成 Debug trait 的实现,方便调试
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
fn main() {
let person = Person {
name: String::from("Alice"),
age: 30,
};
println!("{:?}", person);
}
枚举
无参枚举:
enum Planent{
Mars,
Earth,
}
带枚举值的枚举:
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}
带参数的枚举:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8),
}
模式匹配,如果不是 IpAddr::V4 类型,会执行 _ 分支
let localhost = IpAddr::V4(127, 0, 0, 1);
match localhost {
IpAddr::V4(a, b, c, d) => {
println!("localhost: {}.{}.{}.{}", a, b, c, d);
}
_ => {
println!("Not a V4 address");
}
}
模式匹配可以使用 if let 语法糖
if let IpAddr::V4(a, b, c, d) = localhost {
println!("localhost: {}.{}.{}.{}", a, b, c, d);
} else {
println!("Not a V4 address");
}
类型转换
rust 不支持隐式类型转换,只能使用 as 关键字进行显式类型转换
let a: i32 = 1;
let b: i64 = a as i64;
数值转换的语义:
- 两个相同大小的整型直接的转换是一个 (
no-op),例如i32->u32 - 从一个大的整型转换到一个小的整型,会进行截断,例如
i32->i8 - 从一个小的整型转换到一个大的整型,会进行符号扩展,例如
i8->i32- 如果源类型是无符号会补零
- 如果源类型是有符号会补符号位
- 从一个浮点转换为一个整型会向
0舍入 - 从一个整型转换为一个浮点会产生整型的浮点表示,如有必要会舍入(未指定舍入策略)
- 从
f32转换为f64是完美无缺的 - 从
f64转换为f32会产生最接近的可能值(未指定舍入策略)
for 循环
1..5 和 1..=5 的区别是,前者是左闭右开区间,后者是左闭右闭区间
fn main() {
for i in 1..=5 {
println!("Hello, World! {}", i);
}
}
// Hello, World! 1
// Hello, World! 2
// Hello, World! 3
// Hello, World! 4
// Hello, World! 5
.iter 方法返回一个迭代器,.iter_mut 方法返回一个可变的迭代器
let mut arr = [1, 2, 3, 4, 5];
for i in arr.iter_mut() {
*i = *i * 2; // 修改 arr 中的元素
}
for i in arr.iter() {
println!("Hello, World! {}", i);
}
// Hello, World! 2
// Hello, World! 4
// Hello, World! 6
// Hello, World! 8
// Hello, World! 10
方法
在 rust 中构造方法和普通方法没啥区别,只是约定为 new
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point { x, y }
}
}
fn main() {
let point = Point::new(1, 2);
println!("{:?}", point) // Point { x: 1, y: 2 }
}
结构体中的方法可以通过 self 访问自身的数据
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point { x, y }
}
fn get_x(&self) -> i32 {
self.x
}
}
fn main() {
let point = Point::new(1, 2);
println!("{:?}", point.get_x()) // 1
}
调用 Point::new() 是两个冒号,但是 point.get_x() 是点,这是为什么呢?
因为调用实例化后的方法用 .,直接调用结构体上的方法用 ::
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point { x, y }
}
fn set_x(&mut self, x: i32) {
self.x = x;
}
fn get_x(&self) -> i32 {
self.x
}
}
fn main() {
let mut point = Point::new(1, 2);
println!("{:?}", point.get_x()); // 1
point.set_x(3);
println!("{:?}", point.get_x()); // 3
}
修改结构体上的属性,在 set_x 方法上要加上 &mut self,在声明 point 时也要用 mut
闭包
闭包可以保存在变量中
let times = |n: u32| -> u32 { n * 3 };
println!("{}", times(5)); // 15
应用,使用 move 将主线程的变量移动到子线程中
use std::thread;
fn main() {
let hello = "Hello, World!";
thread::spawn(move || {
println!("{}", hello);
})
.join() // 除非主线程退出,否则会一直等待
.unwrap();
}
发散函数
发散函数永远不会被返回,它们的返回被标记为 !,这是一个空类型
fn foo() -> ! {
panic!("This call never returns.");
}
发散函数与空返回值函数不同,空返回值的函数可以被返回
fn some_fn() {
()
}
发散函数最大的用处是通过 rust 类型检查系统
fn main() {
let a = if false { 1 } else { foo() };
println!("a: {}", a);
}
fn foo() -> ! {
panic!("This call never returns.");
}
参数游戏
在 rust 中,标准库并没有直接给出随机数的 api,需要使用第三方库 rand
在 Cargo.toml 中添加依赖
[dependencies]
rand = "*"
导入 rand 需要导入 trait,也就是 rand::Rng
rand::thread_rng() 作用是返回一个线程本地的随机数生成器,这是一个线程安全的随机数,每个线程有自己的生成器
expect 和 unwrap 的区别:
expect:可以自定义错误信息unwrap:使用默认的错误信息
比较表达式:
guess.cmp(&secret_number) 会返回一个 Ordering 枚举值,用来比较两个数之间的大小,match 语句会根据枚举值执行相应的代码分支
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
如果是小于等于可以写成:
match guess.cmp(&secret_number) {
Ordering::Less | Ordering::Equal => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
猜数游戏源码:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..101);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Failed to read line");
// io::stdin().read_line(&mut guess).unwrap();
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;
}
}
}
}