复合类型
字符串与切片
let my_name = "Pascal";
// 在rust中 这样写是切片...
let s = String::from("hello world");
let hello = &s[..5];
let world = &s[6..11];
// 切片看起来长这样
let len = s.len();
let slice = &s[4..len];
// 也可以这样
let slice = &s[0..len];
let slice = &s[..];
// 如果要用的话,加上引用
fn main() {
let s = String::from("hello,world!");
say_hello(&s);
say_hello(&s[..]);
say_hello(s.as_str());
}
fn say_hello(s: &str) {
println!("{}",s);
}
操作字符串
追加
let mut s = String::from("Hello ");
s.push_str("rust");
println!("追加字符串 push_str() -> {}", s);
s.push('!');
println!("追加字符 push() -> {}", s);
插入
let mut s = String::from("Hello ");
s.insert(5, ',');
println!("插入字符 insert() -> {}", s);
s.insert_str(6, " I like");
println!("插入字符串 insert_str() -> {}", s);
替换
// 替换 (Replace)
// 该方法可适用于 String 和 &str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。
let string_replace = String::from("I like rust. Learning rust is my favorite!");
let new_string_replace = string_replace.replace("rust", "RUST");
//replacen
// 该方法可适用于 String 和 &str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。
let string_replace = "I like rust. Learning rust is my favorite!";
let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
// replace_range
该方法仅适用于 String 类型。replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰。
let mut string_replace_range = String::from("I like rust!");
string_replace_range.replace_range(7..8, "R");
删除
fn main() {
// pop —— 删除并返回字符串的最后一个字符
// 该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None。
let mut string_pop = String::from("rust pop 中文!");
let p1 = string_pop.pop();
let p2 = string_pop.pop();
// remove —— 删除并返回字符串中指定位置的字符
// 该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
let mut string_remove = String::from("测试remove方法");
println!(
"string_remove 占 {} 个字节",
std::mem::size_of_val(string_remove.as_str())
);
// 删除第一个汉字
string_remove.remove(0);
// 下面代码会发生错误
// string_remove.remove(1);
// 直接删除第二个汉字
// string_remove.remove(3);
// truncate —— 删除字符串中从指定位置开始到结尾的全部字符
// 该方法是直接操作原来的字符串。无返回值。
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3);
// clear —— 清空字符串
// 该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。
let mut string_clear = String::from("string clear");
string_clear.clear();
}
连接
// 连接 (Concatenate)
let string_append = String::from("hello ");
let string_rust = String::from("rust");
// &string_rust会自动解引用为&str
let result = string_append + &string_rust;
let mut result = result + "!"; // `result + "!"` 中的 `result` 是不可变的
result += "!!!";
println!("连接字符串 + -> {}", result);
// 使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。+ 是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰。
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
println!("{}", s);
操作 UTF-8 字符串
for c in "中国人".chars() {
println!("{}", c);
}
// 中
// 国
// 人
for b in "中国人".bytes() {
println!("{}", b);
}
// 228
// 184
// 173
// 229
// 155
// 189
// 228
// 186
// 186
对于 String 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:
- 首先向操作系统请求内存来存放 String 对象
- 在使用完成后,将内存释放,归还给操作系统 其中第一部分由 String::from 完成,它创建了一个全新的 String。
重点来了,到了第二部分,就是百家齐放的环节,在有垃圾回收 GC 的语言中,GC 来负责标记并清除这些不再使用的内存对象,这个过程都是自动完成,无需开发者关心,非常简单好用;但是在无 GC 的语言中,需要开发者手动去释放这些内存对象,就像创建对象需要通过编写代码来完成一样,未能正确释放对象造成的后果简直不可估量。
对于 Rust 而言,安全和性能是写到骨子里的核心特性,如果使用 GC,那么会牺牲性能;如果使用手动管理内存,那么会牺牲安全,这该怎么办?为此,Rust 的开发者想出了一个无比惊艳的办法:变量在离开作用域后,就自动释放其占用的内存:
{
let s = String::from("hello"); // 从此处起,s 是有效的
// 使用 s
} // 此作用域已结束,
// s 不再有效,内存被释放
与其它系统编程语言的 free 函数相同,Rust 也提供了一个释放内存的函数: drop,但是不同的是,其它语言要手动调用 free 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 drop 函数: 上面代码中,Rust 在结尾的 } 处自动调用 drop。
元祖
元组是由多种类型组合到一起形成的,因此它是复合类型,元组的长度是固定的,元组中元素的顺序也是固定的。
在 Rust 中,元组(Tuple)常用于以下场景:
返回多个值:当函数需要返回多个不同类型的值时,可以使用元组作为返回类型。元组允许将多个值打包在一起,并以单个返回值的形式返回。例如,可以使用元组返回一个函数的计算结果和状态信息。
函数参数:当函数需要接受多个不同类型的参数时,可以使用元组作为函数的参数类型。通过将多个参数打包成一个元组,可以方便地传递和处理多个值。
模式匹配:元组可以与模式匹配一起使用,用于解构和提取元组中的值。模式匹配允许根据元组的结构进行分支逻辑和条件判断。
临时数据组织:如果需要将一些临时的、相关的数据组织在一起,但不需要为其定义一个专门的结构体,可以使用元组。元组提供了一种轻量级的方式来组织和传递相关的数据。
可以使用模式匹配或者 . 操作符来获取元组中的值。
// 用模式匹配解构元组
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
// .
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
// 以下是一些在 Rust 中使用元组的常见示例:
// 返回多个值:
fn calculate_stats(numbers: &[i32]) -> (i32, i32, i32) {
let sum = numbers.iter().sum();
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(sum, min, max)
}
fn main() {
let numbers = [1, 2, 3, 4, 5];
let (sum, min, max) = calculate_stats(&numbers);
println!("Sum: {}, Min: {}, Max: {}", sum, min, max);
}
// 函数参数:
fn process_person(name: &str, age: u32, address: &str) {
// 处理逻辑
println!("Name: {}, Age: {}, Address: {}", name, age, address);
}
fn main() {
let person = ("Alice", 25, "123 Main St");
process_person(person.0, person.1, person.2);
}
// 模式匹配:
fn process_result(result: Result<i32, &str>) {
match result {
Ok(value) => println!("Success: {}", value),
Err(err) => println!("Error: {}", err),
}
}
fn main() {
let result1 = Ok(42);
process_result(result1);
let result2 = Err("Something went wrong");
process_result(result2);
}
// 临时数据组织:
fn print_person_info(person: (&str, u32, &str)) {
let (name, age, address) = person;
println!("Name: {}, Age: {}, Address: {}", name, age, address);
}
fn main() {
let person = ("Bob", 30, "456 Elm St");
print_person_info(person);
}
结构体
Rust 中的 struct 可以类比为 JavaScript 中的对象,用于定义自定义的数据结构,具有字段、实例化、方法和可变性等特点。
一个结构体由几部分组成:
- 通过关键字 struct 定义
- 一个清晰明确的结构体 名称
- 几个有名字的结构体 字段
有几点值得注意:
- 初始化实例时,每个字段都需要进行初始化
- 初始化时的字段顺序不需要和结构体定义时的顺序一致
通过 . 操作符即可访问结构体实例内部的字段值,也可以修改它们;
必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。
简化结构体创建
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
结构体更新
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
let user2 = User {
email: String::from("another@example.com"),
..user1
};
..user1 必须在结构体的尾部使用。
把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段。
元组结构体(Tuple Struct)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
单元结构体(Unit-like Struct)
如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体:
struct AlwaysEqual;
let subject = AlwaysEqual;
// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {
// impl SomeTrait 特征 后面会讲
}
结构体数据的所有权
结构体中使用引用,必须加上生命周期
使用 #[derive(Debug)]
来打印结构体的信息
打印结构体
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
// --------
// 或者用 dbg!
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale),
height: 50,
};
dbg!(&rect1);
}
枚举
enum Status {
IDEL,
BUSY,
Call,
}
fn main() {
let mut status: Status = Status::IDEL;
status = Status::BUSY;
status = Status::Call;
match status {
Status::IDEL => {
println!("Go IDEL");
}
Status::BUSY => {
println!("Go BUSY");
}
Status::Call => {
println!("Go Call");
}
// Direction::South => println!("Go South"),
// Direction::East => println!("Go East"),
// Direction::West => println!("Go West"),
}
}
fn divide(x: f64, y: f64) -> Option<f64> {
if y != 0.0 {
Some(x / y)
} else {
None
}
}
fn main() {
let result = divide(10.0, 0.0);
match result {
Some(value) => println!("Result: {}", value),
None => println!("Cannot divide by zero"),
}
}
数组
rust 中,数组是 Rust 的基本类型,是固定长度的; 可变数组为动态数组 Vector 类型; array 是存储在栈上,Vector 是存储在堆上;
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
let a: [i32; 5] = [1, 2, 3, 4, 5];
// let a = [3; 5];
let first = a[0]; // 获取a数组第一个元素
数组切片
数组切片允许我们引用数组的一部分:
let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
assert_eq!(slice, &[2, 3]);
- 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
- 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
- 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,&str 字符串切片也同理
总结
fn main() {
// 编译器自动推导出one的类型
let one = [1, 2, 3];
// 显式类型标注
let two: [u8; 3] = [1, 2, 3];
let blank1 = [0; 3];
let blank2: [u8; 3] = [0; 3];
// arrays是一个二维数组,其中每一个元素都是一个数组,元素类型是[u8; 3]
let arrays: [[u8; 3]; 4] = [one, two, blank1, blank2];
// 借用arrays的元素用作循环中
for a in &arrays {
print!("{:?}: ", a);
// 将a变成一个迭代器,用于循环
// 你也可以直接用for n in a {}来进行循环
for n in a.iter() {
print!("\t{} + 10 = {}", n, n+10);
}
let mut sum = 0;
// 0..a.len,是一个 Rust 的语法糖,其实就等于一个数组,元素是从0,1,2一直增加到到a.len-1
for i in 0..a.len() {
sum += a[i];
}
println!("\t({:?} = {})", a, sum);
}
}