Rust 基礎(chǔ)入門指南:為 Solana 合約學(xué)習(xí)鋪路 https://learnblockchain.cn/column/113
一、字符串
動態(tài)字符串切片
字符串切片是引用類型,類型為 &str,它通過索引或范圍來指定字符串的一部分,提供了對字符串的引用,而不引入額外的內(nèi)存開銷。切片并不擁有字符串的內(nèi)容,因此不會消耗額外的內(nèi)存。
fn main() {
let s: String = String::from("Rust is powerful!");
// 從索引 0 開始,獲取前 4 個字節(jié)
let slice1: &str = &s[0..4];
// 默認(rèn)從索引 0 開始,獲取前 4 個字節(jié)
let slice2: &str = &s[..4];
let len: usize = s.len();
// 從索引 5 開始,獲取到字符串末尾
let slice3: &str = &s[5..len];
// 默認(rèn)從索引 5 開始,獲取到字符串末尾
let slice4: &str = &s[5..];
// 獲取整個字符串的切片
let slice5: &str = &s[0..len];
// 同上,獲取整個字符串的切片
let slice6: &str = &s[..];
// 輸出結(jié)果
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 = "編程語言";
// 錯誤示例:試圖獲取 "編" 的前兩個字節(jié),但 "編" 占用 3 個字節(jié)
// let wrong_slice = &chinese_string[0..2]; // 編譯通過,但運行時會導(dǎo)致 panic
// 正確示例:獲取 "編" 的完整字節(jié)范圍
let correct_slice = &chinese_string[0..3];
println!("正確的切片: {}", correct_slice); // 輸出 "編"
}
索引范圍是前閉后開的,即包含開始位置,但不包含結(jié)束位置。
字符串字面量與動態(tài)字符串
字符串字面量是在代碼中直接寫死的字符串,比如 "Rust is awesome!"。它們在程序編譯時就已經(jīng)確定并固定下來,類型為 &str。與動態(tài)字符串不同,字符串字面量是不可變的。
字符串字面量在編譯時已知大小,因此它的生命周期與整個程序的運行期一致。這使得它們在內(nèi)存中更加高效,而無需像動態(tài)字符串那樣進(jìn)行內(nèi)存分配。
fn main() {
// 字符串字面量轉(zhuǎn)動態(tài)字符串
let s1: String = "Rust is cool".to_string();
let s2: String = String::from("Rust is powerful");
// 動態(tài)字符串轉(zhuǎn)字符串字面量
let s3: &str = s1.as_str();
println!("s1: {}", s1); // 輸出: Rust is cool
println!("s2: {}", s2); // 輸出: Rust is powerful
println!("s3: {}", s3); // 輸出: Rust is cool
}
動態(tài)字符串操作
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);
}
二、元組
元組是一種復(fù)合類型,它將多個不同類型的值組合在一起,并且長度和順序都是固定的。它們通過圓括號 ( ) 定義,元素之間用逗號 , 分隔。
想象你有一個旅行背包,里面裝著不同種類的物品,比如護(hù)照(字符串)、現(xiàn)金(整數(shù))、指南針(浮點數(shù))。無論里面的物品是什么類型,它們都屬于同一個背包(元組),并且位置固定。
定義元組
// 創(chuàng)建一個包含不同類型元素的元組
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);
// 解構(gòu)方式獲取元組元素
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);
}
作為函數(shù)返回值
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);
}
三、結(jié)構(gòu)體
結(jié)構(gòu)體是一種自定義數(shù)據(jù)類型,可以組織多個相關(guān)的字段,使代碼更清晰易讀??梢詫⒔Y(jié)構(gòu)體看作是人物檔案,每個字段代表一個特定信息,比如姓名、年齡、職業(yè)等。
基礎(chǔ)語法
// 定義結(jié)構(gòu)體
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);
// 通過已有結(jié)構(gòu)體創(chuàng)建新實例
let book2 = Book {
title: String::from("Advanced Rust"),
..rust_book
};
println!("{} - {} pages", book2.title, book2.pages);
}
元組結(jié)構(gòu)體(Tuple Struct)
如果你不需要字段名稱,但仍然希望使用結(jié)構(gòu)體的特性,可以使用元組結(jié)構(gòu)體。
struct Coordinates(i32, i32, i32);
fn main() {
let origin = Coordinates(0, 0, 0);
println!("Origin is at ({}, {}, {})", origin.0, origin.1, origin.2);
}
四、枚舉
枚舉是一種用戶自定義的數(shù)據(jù)類型,它允許一個類型有多個不同的可能值,每個值稱為一個變體(variant)。
基礎(chǔ)語法
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!"),
}
}
五、數(shù)組
數(shù)組是由多個相同類型的元素組合而成的集合。在 Rust 中,數(shù)組主要分為兩類:
- 靜態(tài)數(shù)組 (array):分配在棧上,長度固定,訪問速度快。
- 動態(tài)數(shù)組 (Vector):分配在堆上,長度可變,但操作會帶來額外的性能開銷。
靜態(tài)數(shù)組的特點類似于一個固定大小的書架,一旦確定其大小,就無法再改變。相比之下,動態(tài)數(shù)組更像是一個可以隨時調(diào)整大小的抽屜,可以根據(jù)需要擴(kuò)展或縮小。
靜態(tài)數(shù)組
fn main() {
// 由編譯器推斷類型
let a = [10, 20, 30, 40, 50];
// 顯式指定類型和長度
let b: [i32; 5] = [10, 20, 30, 40, 50];
// 創(chuàng)建包含相同元素的數(shù)組
let c = [7; 5]; // c = [7, 7, 7, 7, 7]
// 靜態(tài)數(shù)組:長度固定,存儲在棧上
let a = [5u8; 5]; // a = [5, 5, 5, 5, 5]
// 非基礎(chǔ)類型的數(shù)組需要使用 `std::array::from_fn` 來初始化
let b: [String; 3] = std::array::from_fn(|_| "hello".to_string());
// b = ["hello", "hello", "hello"]
// 數(shù)組的元素訪問
let c: [u8; 5] = [15, 25, 35, 45, 55];
let first = c[0]; // first = 15
let second = c[1]; // second = 25
// 訪問數(shù)組越界元素(會導(dǎo)致編譯錯誤)
// let none_element = c[100];
// 二維數(shù)組:每個元素都是一個 `[u8; 5]` 類型的數(shù)組
let arrays: [[u8; 5]; 2] = [a, c];
}
動態(tài)數(shù)組
動態(tài)數(shù)組 Vec<T> 是 Rust 中可變長度的集合,允許在運行時動態(tài)調(diào)整大小。與 String 不同,Vec<T> 是通用的,可存儲任意類型的元素。
fn main() {
// 顯式聲明類型
let v1: Vec<i32> = Vec::new();
// v1.push(1); 編譯錯誤
// 讓編譯器推斷類型。
let mut v2 = Vec::new();
v2.push(10);
// 使用 vec! 宏創(chuàng)建并初始化
let v3 = vec![10, 20, 30];
// 使用 [初始值;長度] 來創(chuàng)建數(shù)組
let v4 = vec![1; 3]; // v4 = [1, 1, 1]
// 使用from語法創(chuàng)建數(shù)組
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]
/* 自動擴(kuò)容 */
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,存儲在堆內(nèi)存中,而棧上存儲著指向堆的指針。當(dāng) push(5) 觸發(fā)擴(kuò)容時,Vec 會申請更大的空間(通常是當(dāng)前容量的 2 倍),并將數(shù)據(jù)復(fù)制到新的地址。
六、hashmap
// 由于 HashMap 并沒有包含在 Rust 的 prelude 庫中,所以需要手動引入
use std::collections::HashMap;
fn main() {
// 創(chuàng)建一個HashMap,用于存儲學(xué)生姓名和他們的年齡
let mut student_ages = HashMap::new();
student_ages.insert("Alice", 20);
student_ages.insert("Bob", 19);
student_ages.insert("Charlie", 21);
// 創(chuàng)建一個指定容量的 HashMap,用于存儲學(xué)生姓名和他們的身高
// 這樣可以預(yù)先分配內(nèi)存,減少后續(xù)插入時的內(nèi)存分配開銷
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 的內(nèi)容
println!("學(xué)生年齡: {:?}", student_ages);
println!("學(xué)生身高: {:?}", student_heights);
}
復(fù)雜一點的例子:
use std::collections::HashMap;
fn main() {
// 創(chuàng)建一個包含用戶姓名和積分的列表
let user_list = vec![("Alice", 300), ("Bob", 250), ("Eve", 150), ("Mallory", 50)];
// 將列表轉(zhuǎn)換為 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)); // 確認(rèn)舊值是 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);
}