Rust 基础入门指南:为 Solana 合约学习铺路 https://learnblockchain.cn/column/113
一、字符串
动态字符串切片
字符串切片是引用类型,类型为 &str,它通过索引或范围来指定字符串的一部分,提供了对字符串的引用,而不引入额外的内存开销。切片并不拥有字符串的内容,因此不会消耗额外的内存。
fn main() {
let s: String = String::from("Rust is powerful!");
// 从索引 0 开始,获取前 4 个字节
let slice1: &str = &s[0..4];
// 默认从索引 0 开始,获取前 4 个字节
let slice2: &str = &s[..4];
let len: usize = s.len();
// 从索引 5 开始,获取到字符串末尾
let slice3: &str = &s[5..len];
// 默认从索引 5 开始,获取到字符串末尾
let slice4: &str = &s[5..];
// 获取整个字符串的切片
let slice5: &str = &s[0..len];
// 同上,获取整个字符串的切片
let slice6: &str = &s[..];
// 输出结果
println!("slice1: {}", slice1); // 输出: Rust
println!("slice2: {}", slice2); // 输出: Rust
println!("slice3: {}", slice3); // 输出: is powerful!
println!("slice4: {}", slice4); // 输出: is powerful!
println!("slice5: {}", slice5); // 输出: Rust is powerful!
println!("slice6: {}", slice6); // 输出: Rust is powerful!
let chinese_string = "编程语言";
// 错误示例:试图获取 "编" 的前两个字节,但 "编" 占用 3 个字节
// let wrong_slice = &chinese_string[0..2]; // 编译通过,但运行时会导致 panic
// 正确示例:获取 "编" 的完整字节范围
let correct_slice = &chinese_string[0..3];
println!("正确的切片: {}", correct_slice); // 输出 "编"
}
索引范围是前闭后开的,即包含开始位置,但不包含结束位置。
字符串字面量与动态字符串
字符串字面量是在代码中直接写死的字符串,比如 "Rust is awesome!"。它们在程序编译时就已经确定并固定下来,类型为 &str
。与动态字符串不同,字符串字面量是不可变的。
字符串字面量在编译时已知大小,因此它的生命周期与整个程序的运行期一致。这使得它们在内存中更加高效,而无需像动态字符串那样进行内存分配。
fn main() {
// 字符串字面量转动态字符串
let s1: String = "Rust is cool".to_string();
let s2: String = String::from("Rust is powerful");
// 动态字符串转字符串字面量
let s3: &str = s1.as_str();
println!("s1: {}", s1); // 输出: Rust is cool
println!("s2: {}", s2); // 输出: Rust is powerful
println!("s3: {}", s3); // 输出: Rust is cool
}
动态字符串操作
fn main() {
let mut s = String::from("Hello");
// 追加字符串,修改原来的字符串
s.push_str(", world");
println!("追加字符串 push_str() -> {}", s);
// 追加单个字符
s.push('!');
println!("追加字符 push() -> {}", s);
// 在指定位置插入字符,修改原来的字符串
s.insert(5, ',');
println!("插入字符 insert() -> {}", s);
// 在指定位置插入字符串
s.insert_str(6, " how are you?");
println!("插入字符串 insert_str() -> {}", s);
// 替换字符串中的某个子串,返回新字符串
let str_old = String::from("I like rust, rust is great!");
let str_new = str_old.replace("rust", "Rust");
println!("原字符串:{}, 新字符串:{}", str_old, str_new);
// pop 删除操作,修改原来的字符串
let mut string_pop = String::from("删除操作,rust 中文!");
// 删除末尾字符
let p1 = string_pop.pop();
println!("删除字符 pop() -> {:#?}", p1); // 输出:Some('!')
println!("剩余字符串: {}", string_pop);
// 删除末尾字符,再次 pop
let p2 = string_pop.pop();
println!("删除字符 pop() -> {:?}", p2); // 输出:Some('中')
println!("剩余字符串: {}", string_pop);
}
二、元组
元组是一种复合类型,它将多个不同类型的值组合在一起,并且长度和顺序都是固定的。它们通过圆括号 ( ) 定义,元素之间用逗号 , 分隔。
想象你有一个旅行背包,里面装着不同种类的物品,比如护照(字符串)、现金(整数)、指南针(浮点数)。无论里面的物品是什么类型,它们都属于同一个背包(元组),并且位置固定。
定义元组
// 创建一个包含不同类型元素的元组
let info: (i32, bool, f64, &str) = (42, true, 3.14, "Rust");
// 元组可以嵌套
let nested_tuple: (u8, (char, &str)) = (1, ('A', "嵌套"));
访问元组元素
fn main() {
let person: (&str, i32, f64) = ("Alice", 30, 1.65);
// 解构方式获取元组元素
let (name, age, height) = person;
println!("{} is {} years old and {}m tall.", name, age, height);
// 使用索引访问元组元素
println!("Name: {}, Age: {}, Height: {}", person.0, person.1, person.2);
}
作为函数返回值
fn square_and_cube(n: i32) -> (i32, i32) {
(n * n, n * n * n)
}
fn main() {
let (square, cube) = square_and_cube(3);
println!("Square: {}, Cube: {}", square, cube);
}
三、结构体
结构体是一种自定义数据类型,可以组织多个相关的字段,使代码更清晰易读。可以将结构体看作是人物档案,每个字段代表一个特定信息,比如姓名、年龄、职业等。
基础语法
// 定义结构体
struct Book {
title: String,
author: String,
pages: u32,
is_hardcover: bool,
}
fn main() {
// 实例化
let rust_book = Book {
title: String::from("The Rust Programming Language"),
author: String::from("Steve Klabnik and Carol Nichols"),
pages: 552,
is_hardcover: true,
};
println!("Book: {} by {}", rust_book.title, rust_book.author);
// 通过已有结构体创建新实例
let book2 = Book {
title: String::from("Advanced Rust"),
..rust_book
};
println!("{} - {} pages", book2.title, book2.pages);
}
元组结构体(Tuple Struct)
如果你不需要字段名称,但仍然希望使用结构体的特性,可以使用元组结构体。
struct Coordinates(i32, i32, i32);
fn main() {
let origin = Coordinates(0, 0, 0);
println!("Origin is at ({}, {}, {})", origin.0, origin.1, origin.2);
}
四、枚举
枚举是一种用户自定义的数据类型,它允许一个类型有多个不同的可能值,每个值称为一个变体(variant)。
基础语法
enum CoffeeSize {
Small,
Medium,
Large,
}
enum Message {
Text(String),
Move { x: i32, y: i32 },
ChangeColor(i32, i32, i32),
}
fn main() {
let my_coffee = CoffeeSize::Medium;
match my_coffee {
CoffeeSize::Small => println!("You chose a small coffee."),
CoffeeSize::Medium => println!("You chose a medium coffee."),
CoffeeSize::Large => println!("You chose a large coffee."),
}
let msg1 = Message::Text(String::from("Hello, Rust!"));
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::ChangeColor(255, 0, 0);
}
Option 枚举
Rust 没有 null,取而代之的是 Option<T>
枚举,用于表示可能为空的值。它的定义如下:
// 它有两个枚举值,Some(T): 包含一个具体的值 T,以及None: 表示没有值。
enum Option<T> {
None,
Some(T),
}
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
fn main() {
match divide(10.0, 2.0) {
Some(result) => println!("Result: {}", result),
None => println!("Cannot divide by zero!"),
}
}
五、数组
数组是由多个相同类型
的元素组合而成的集合。在 Rust 中,数组主要分为两类:
- 静态数组 (array):分配在栈上,长度固定,访问速度快。
- 动态数组 (Vector):分配在堆上,长度可变,但操作会带来额外的性能开销。
静态数组的特点类似于一个固定大小的书架,一旦确定其大小,就无法再改变。相比之下,动态数组更像是一个可以随时调整大小的抽屉,可以根据需要扩展或缩小。
静态数组
fn main() {
// 由编译器推断类型
let a = [10, 20, 30, 40, 50];
// 显式指定类型和长度
let b: [i32; 5] = [10, 20, 30, 40, 50];
// 创建包含相同元素的数组
let c = [7; 5]; // c = [7, 7, 7, 7, 7]
// 静态数组:长度固定,存储在栈上
let a = [5u8; 5]; // a = [5, 5, 5, 5, 5]
// 非基础类型的数组需要使用 `std::array::from_fn` 来初始化
let b: [String; 3] = std::array::from_fn(|_| "hello".to_string());
// b = ["hello", "hello", "hello"]
// 数组的元素访问
let c: [u8; 5] = [15, 25, 35, 45, 55];
let first = c[0]; // first = 15
let second = c[1]; // second = 25
// 访问数组越界元素(会导致编译错误)
// let none_element = c[100];
// 二维数组:每个元素都是一个 `[u8; 5]` 类型的数组
let arrays: [[u8; 5]; 2] = [a, c];
}
动态数组
动态数组 Vec<T> 是 Rust 中可变长度的集合,允许在运行时动态调整大小。与 String 不同,Vec<T> 是通用的,可存储任意类型的元素。
fn main() {
// 显式声明类型
let v1: Vec<i32> = Vec::new();
// v1.push(1); 编译错误
// 让编译器推断类型。
let mut v2 = Vec::new();
v2.push(10);
// 使用 vec! 宏创建并初始化
let v3 = vec![10, 20, 30];
// 使用 [初始值;长度] 来创建数组
let v4 = vec![1; 3]; // v4 = [1, 1, 1]
// 使用from语法创建数组
let v5 = Vec::from([1, 1, 1]);
let mut v = vec![1, 2, 3, 4, 5];
// 访问元素
let first: &i32 = &v[0];
println!("第一个元素是 {}", first);
// 通过 get 方法安全访问
if let Some(last) = v.get(4) {
println!("最后一个元素是 {}", last);
}
// 修改元素
for i in &mut v {
*i *= 2;
}
println!("修改后的 v: {:?}", v); // [2, 4, 6, 8, 10]
// 删除元素
v.pop();
println!("删除最后一个元素后: {:?}", v); // [2, 4, 6, 8]
v.remove(1);
println!("删除索引 1 处的元素后: {:?}", v); // [2, 6, 8]
/* 自动扩容 */
let mut v = vec![1, 2, 3, 4];
println!("初始容量: {}", v.capacity());
println!("初始 v[0] 的地址: {:p}", &v[0]);
v.push(5);
println!("添加一个元素后,容量: {}", v.capacity());
println!("添加后 v[0] 的地址: {:p}", &v[0]);
v.push(6);
println!("再次添加元素后,容量: {}", v.capacity());
println!("再次添加后 v[0] 的地址: {:p}", &v[0]);
}
初始时 Vec 的容量可能是 4,存储在堆内存中,而栈上存储着指向堆的指针。当 push(5) 触发扩容时,Vec 会申请更大的空间(通常是当前容量的 2 倍),并将数据复制到新的地址。
六、hashmap
// 由于 HashMap 并没有包含在 Rust 的 prelude 库中,所以需要手动引入
use std::collections::HashMap;
fn main() {
// 创建一个HashMap,用于存储学生姓名和他们的年龄
let mut student_ages = HashMap::new();
student_ages.insert("Alice", 20);
student_ages.insert("Bob", 19);
student_ages.insert("Charlie", 21);
// 创建一个指定容量的 HashMap,用于存储学生姓名和他们的身高
// 这样可以预先分配内存,减少后续插入时的内存分配开销
let mut student_heights = HashMap::with_capacity(4);
student_heights.insert("Alice", 165);
student_heights.insert("Bob", 180);
student_heights.insert("Charlie", 175);
student_heights.insert("David", 170);
// 打印 HashMap 的内容
println!("学生年龄: {:?}", student_ages);
println!("学生身高: {:?}", student_heights);
}
复杂一点的例子:
use std::collections::HashMap;
fn main() {
// 创建一个包含用户姓名和积分的列表
let user_list = vec![("Alice", 300), ("Bob", 250), ("Eve", 150), ("Mallory", 50)];
// 将列表转换为 HashMap
let mut user_map: HashMap<&str, i32> = user_list.into_iter().collect();
println!("用户积分: {:?}", user_map);
// 获取元素
let alice_points = user_map["Alice"];
println!("Alice 的积分: {}", alice_points);
// 使用 get 方法安全获取元素
let alice_points = user_map.get("Alice");
println!("Alice 的积分: {:?}", alice_points);
// 尝试获取不存在的键
let trent_points = user_map.get("Trent");
println!("Trent 的积分: {:?}", trent_points);
// 覆盖已有的值,insert 会返回旧值
let old = user_map.insert("Alice", 400); // 更新 Alice 的积分
assert_eq!(old, Some(300)); // 确认旧值是 300
// 使用 entry 方法插入或获取值
let v = user_map.entry("Trent").or_insert(100); // Trent 不存在,插入 100
assert_eq!(*v, 100);
let v = user_map.entry("Trent").or_insert(200); // Trent 已存在,不会修改
assert_eq!(*v, 100);
// 打印更新后的 HashMap
println!("更新后的用户积分: {:?}", user_map);
}