欢迎订阅专栏 :10分钟Solana-性能web3
Rust 编程基础三:结构体、枚举、错误处理与集合
在掌握了所有权与生命周期之后,我们将进入 Rust 的面向对象风格编程。本篇涵盖结构体、枚举、模式匹配、错误处理、泛型、Trait 和常用集合类型,并最终组合成一个"代币账户管理"的实战示例。
一、结构体与方法
结构体是 Rust 中自定义数据类型的主要方式,类似于 Solidity 中的 struct。
1. 定义与实例化
rust
// 定义结构体
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
fn main() {
// 创建实例
let user1 = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
active: true,
sign_in_count: 1,
};
// 访问字段
println!("用户名: {}", user1.username);
// 可变实例
let mut user2 = User {
username: String::from("bob"),
email: String::from("bob@example.com"),
active: true,
sign_in_count: 1,
};
user2.email = String::from("bob@new.com");
// 结构体更新语法(类似 JS 的展开)
let user3 = User {
username: String::from("charlie"),
..user2 // 剩余字段从 user2 复制
};
}
注意 :如果字段包含 String 等非 Copy 类型,更新时可能发生所有权转移(如 user2 中的 email 可能被移动到 user3)。
2. 元组结构体 (Tuple Struct)
没有字段名,只有类型。
rust
struct Color(u8, u8, u8);
struct Point(i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0);
}
3. 方法 (Method)
方法定义在 impl 块中,第一个参数是 self(或 &self、&mut self)。
rust
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 不可变借用方法
fn area(&self) -> u32 {
self.width * self.height
}
// 可变借用方法
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
// 获取所有权的方法(较少使用)
fn into_tuple(self) -> (u32, u32) {
(self.width, self.height)
}
}
fn main() {
let mut rect = Rectangle { width: 10, height: 20 };
println!("面积: {}", rect.area());
rect.scale(2);
println!("缩放后面积: {}", rect.area());
let tuple = rect.into_tuple(); // rect 被移动,后续不可再使用
// println!("{}", rect.width); // 错误
}
4. 关联函数 (Associated Function)
不带 self 参数的函数,类似于 Solidity 中的 static 函数,通常用于构造器。
rust
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let rect = Rectangle::new(10, 20);
let square = Rectangle::square(15);
}
二、枚举与模式匹配
枚举是 Rust 中表示**"多种可能之一"**的强大工具,类似于 Solidity 中的枚举但功能更强大。
1. 定义枚举
rust
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
// 枚举可以携带数据
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
2. Option<T> 枚举
Rust 没有 null,而是使用 Option<T> 表示"有值"或"无值"。
rust
enum Option<T> {
Some(T),
None,
}
fn main() {
let some_number = Some(5);
let some_string = Some("hello");
let absent_number: Option<i32> = None; // 需要显式类型
}
使用 Option 时,必须处理 None 情况。
3. match 表达式
match 类似于 switch,但更强大,可以匹配枚举、字面量、范围等。
rust
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
_ 通配符:匹配所有情况。
rust
let dice_roll = 9;
match dice_roll {
3 => println!("得到 3"),
7 => println!("得到 7"),
_ => println!("其他值"),
}
4. if let 简洁匹配
当只关心一种模式时,if let 更简洁。
rust
let coin = Coin::Quarter;
if let Coin::Quarter = coin {
println!("这是 25 美分");
}
三、错误处理
Rust 将错误分为两类:可恢复错误 (使用 Result<T, E>)和不可恢复错误 (使用 panic!)。
1. panic! 宏
当程序遇到不可恢复错误时,可以调用 panic! 导致程序崩溃。
rust
fn main() {
panic!("发生致命错误!");
}
2. Result<T, E> 枚举
rust
enum Result<T, E> {
Ok(T),
Err(E),
}
常见用法:
rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let file = File::open("hello.txt");
let file = match file {
Ok(f) => f,
Err(e) => match e.kind() {
ErrorKind::NotFound => panic!("文件不存在"),
other => panic!("其他错误: {:?}", other),
},
};
}
3. 快捷方法:unwrap 和 expect
unwrap():如果Result是Ok,返回值;如果是Err,调用panic!。expect(msg):类似,但可自定义 panic 消息。
rust
let f = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("无法打开文件");
4. 传播错误:? 运算符
? 用于将错误向上传播,使代码更简洁。
rust
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? 会自动将错误类型转换(如果实现了 From trait)。
在 Solana 程序中 :通常使用 ProgramResult(Result<(), ProgramError>),并在链上函数中返回 Result。
四、泛型 (Generics)
泛型允许编写可处理多种类型的代码,类似于 Solidity 中的库函数或 C++ 模板。
1. 函数中的泛型
rust
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
println!("最大值: {}", result);
}
T: PartialOrd + Copy 是Trait Bound ,要求 T 实现了比较和复制。
2. 结构体中的泛型
rust
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
3. 枚举中的泛型
Option<T> 和 Result<T, E> 就是泛型枚举。
五、Trait
Trait 类似于 Solidity 中的接口,定义共享行为。
1. 定义与实现 Trait
rust
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, {}", self.headline, self.content)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
2. 默认实现
rust
pub trait Summary {
fn summarize(&self) -> String {
String::from("(阅读更多...)")
}
}
3. Trait 作为参数
rust
pub fn notify(item: &impl Summary) {
println!("新闻摘要: {}", item.summarize());
}
4. 常用 Trait
| Trait | 用途 |
|---|---|
Debug |
打印调试信息(用 {:?}) |
Clone |
显式复制 |
Copy |
隐式复制(仅适用于简单类型) |
PartialEq |
相等比较(==, !=) |
PartialOrd |
比较大小 |
Default |
默认值 |
六、集合类型
1. Vec<T>(动态数组)
rust
fn main() {
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
let v2 = vec![1, 2, 3];
// 读取元素
let third = &v2[2]; // 索引访问(可能 panic)
let third_opt = v2.get(2); // 返回 Option<&i32>
// 遍历
for i in &v2 {
println!("{}", i);
}
// 遍历并修改
for i in &mut v {
*i += 10;
}
}
2. HashMap<K, V>(哈希映射)
类似于 Solidity 的 mapping,但更灵活。
rust
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert("蓝队".to_string(), 10);
scores.insert("黄队".to_string(), 50);
// 获取值
let team_name = "蓝队".to_string();
let score = scores.get(&team_name).copied().unwrap_or(0);
// 遍历
for (key, value) in &scores {
println!("{}: {}", key, value);
}
// 插入或更新
scores.insert("黄队".to_string(), 60); // 覆盖旧值
scores.entry("红队".to_string()).or_insert(30); // 不存在才插入
}
3. HashSet<T>
不重复元素的集合。
rust
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(1);
set.insert(2);
set.insert(1); // 无变化
七、综合示例:代币账户管理(模拟 Solana)
我们结合所学内容,模拟一个简单代币账户的管理器,包含创建账户、转账和查询余额。
rust
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct TokenAccount {
owner: String,
balance: u64,
}
#[derive(Debug)]
enum AccountError {
AccountNotFound,
InsufficientBalance,
OwnerMismatch,
}
type Result<T> = std::result::Result<T, AccountError>;
struct TokenManager {
accounts: HashMap<String, TokenAccount>,
}
impl TokenManager {
fn new() -> Self {
TokenManager {
accounts: HashMap::new(),
}
}
// 创建账户
fn create_account(&mut self, address: String, owner: String) {
let account = TokenAccount { owner, balance: 0 };
self.accounts.insert(address, account);
}
// 查询余额
fn get_balance(&self, address: &str) -> Result<u64> {
let account = self.accounts.get(address).ok_or(AccountError::AccountNotFound)?;
Ok(account.balance)
}
// 转账
fn transfer(&mut self, from: &str, to: &str, amount: u64) -> Result<()> {
// 获取并检查 from 账户
let from_account = self.accounts.get_mut(from).ok_or(AccountError::AccountNotFound)?;
if from_account.balance < amount {
return Err(AccountError::InsufficientBalance);
}
from_account.balance -= amount;
// 获取并更新 to 账户
let to_account = self.accounts.get_mut(to).ok_or(AccountError::AccountNotFound)?;
to_account.balance += amount;
Ok(())
}
// 存款(增加余额)
fn deposit(&mut self, address: &str, amount: u64) -> Result<()> {
let account = self.accounts.get_mut(address).ok_or(AccountError::AccountNotFound)?;
account.balance += amount;
Ok(())
}
}
fn main() -> Result<()> {
let mut manager = TokenManager::new();
manager.create_account("alice".to_string(), "Alice".to_string());
manager.create_account("bob".to_string(), "Bob".to_string());
manager.deposit("alice", 100)?;
manager.transfer("alice", "bob", 30)?;
let alice_balance = manager.get_balance("alice")?;
let bob_balance = manager.get_balance("bob")?;
println!("Alice 余额: {}", alice_balance);
println!("Bob 余额: {}", bob_balance);
Ok(())
}
关键点:
- 使用
Result和?进行错误传播。 - 结构体和方法封装状态。
HashMap作为账户存储。- 泛型未直接使用,但
Result<T, E>是泛型。
八、总结与下一步
本系列第三篇涵盖了 Rust 编程的核心主题:
- 结构体与方法:自定义数据类型,封装行为。
- 枚举与模式匹配 :
Option、Result、match和if let。 - 错误处理 :
panic!、Result、?运算符。 - 泛型与 Trait:编写灵活、可重用的代码。
- 集合类型 :
Vec、HashMap、HashSet。 - 综合示例:代币账户管理。
现在,你已经具备了编写实用 Rust 程序的能力。在下一篇中,我们将进入 Solana 程序的实战开发,使用 Anchor 框架编写和部署链上程序。
延伸阅读:
如果你对某个话题有疑问或想深入,欢迎在评论区讨论!