SC Day 9
Rust struct + impl + 方法 + 关联函数
struct 定义、impl 块、&self/&mut self/self 方法、关联函数(::new 模式)
2026-04-18
第一阶段:基础构建ruststructimplmethodsassociated-functions
日期: 2026-04-18 方向: Rust 阶段: 第一阶段:基础构建 标签: #rust #struct #impl #methods #associated-functions
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | struct 定义、impl 块、&self/&mut self/self 方法、关联函数(::new 模式) |
| 实操 | 用 Rust struct 模拟 ERC20 代币的全部逻辑 |
| 产出 | Rust 版 Token 结构体 + 完整测试 |
一、struct 定义
1.1 基础结构体
// 命名字段结构体(最常用)
struct User {
address: String,
name: String,
balance: u64,
is_active: bool,
}
// 元组结构体(用于简单封装)
struct Color(u8, u8, u8);
struct Wei(u128);
struct Address(String);
// 单元结构体(无字段,用于标记)
struct Marker;
1.2 创建实例
fn main() {
// 方式 1:指定所有字段
let user = User {
address: String::from("0xABC"),
name: String::from("Alice"),
balance: 1000,
is_active: true,
};
// 方式 2:字段简写(变量名和字段名相同时)
let address = String::from("0xDEF");
let name = String::from("Bob");
let user2 = User {
address, // 等同于 address: address
name, // 等同于 name: name
balance: 500,
is_active: true,
};
// 方式 3:结构体更新语法(从另一个实例复制)
let user3 = User {
name: String::from("Charlie"),
..user2 // 其余字段从 user2 复制
// ⚠️ 注意:user2.address 被 Move 了(String 不是 Copy)
// user2.balance 和 user2.is_active 被 Copy 了
};
// println!("{}", user2.address); // ❌ 被 Move 了
println!("{}", user2.balance); // ✅ u64 是 Copy
// 元组结构体
let red = Color(255, 0, 0);
println!("Red: ({}, {}, {})", red.0, red.1, red.2);
let one_eth = Wei(1_000_000_000_000_000_000);
println!("1 ETH = {} wei", one_eth.0);
}
1.3 访问和修改字段
fn main() {
// 整个实例必须是 mut 才能修改任何字段
let mut user = User {
address: String::from("0xABC"),
name: String::from("Alice"),
balance: 1000,
is_active: true,
};
// 读取
println!("Name: {}", user.name);
println!("Balance: {}", user.balance);
// 修改(需要 mut)
user.balance += 500;
user.name = String::from("Alice Updated");
println!("New balance: {}", user.balance);
// 解构
let User { address, name, balance, is_active } = user;
println!("{} at {} has {} (active: {})", name, address, balance, is_active);
// ⚠️ 解构后 user 不再有效(String 字段被 Move 了)
// 部分解构
let user2 = User {
address: String::from("0xDEF"),
name: String::from("Bob"),
balance: 200,
is_active: false,
};
let User { balance, is_active, .. } = &user2; // 引用解构,不 Move
println!("Balance: {}, Active: {}", balance, is_active);
println!("User2 still valid: {}", user2.name); // ✅
}
二、impl 块与方法
2.1 实现方法
struct Rectangle {
width: f64,
height: f64,
}
// impl 块:为 struct 实现方法
impl Rectangle {
// 方法:第一个参数是 self(某种形式)
// &self: 不可变借用(只读,最常用)
fn area(&self) -> f64 {
self.width * self.height
}
// &self: 另一个只读方法
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
// &self: 带其他参数
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// &mut self: 可变借用(可以修改字段)
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
// self: 获取所有权(消费实例,少用)
fn into_square(self) -> Rectangle {
let side = (self.width + self.height) / 2.0;
Rectangle {
width: side,
height: side,
}
// self 在这里被消费,原实例不再有效
}
}
fn main() {
let mut rect = Rectangle { width: 30.0, height: 50.0 };
println!("Area: {}", rect.area()); // &self
println!("Perimeter: {}", rect.perimeter()); // &self
let small = Rectangle { width: 10.0, height: 20.0 };
println!("Can hold small: {}", rect.can_hold(&small)); // &self + &Rectangle
rect.scale(2.0); // &mut self
println!("Scaled area: {}", rect.area());
let square = rect.into_square(); // self (消费 rect)
// println!("{}", rect.area()); // ❌ rect 已被消费
println!("Square area: {}", square.area()); // ✅
}
2.2 self 的三种形式
| 形式 | 含义 | 使用场景 | 调用后原实例 |
|---|---|---|---|
&self | 不可变借用 | 只读操作(getter) | 仍可用 |
&mut self | 可变借用 | 修改操作(setter) | 仍可用 |
self | 获取所有权 | 转换/消费操作 | 不可用(被 Move) |
impl Rectangle {
fn read_only(&self) { } // rect.read_only() → rect 仍然有效
fn modify(&mut self) { } // rect.modify() → rect 仍然有效(需要 let mut)
fn consume(self) { } // rect.consume() → rect 不再有效
}
2.3 关联函数(Associated Functions)
关联函数不以 self 为第一个参数,通过 Type::function() 调用(类似其他语言的静态方法)。
impl Rectangle {
// 关联函数:构造器模式(最常见的 ::new)
fn new(width: f64, height: f64) -> Self {
// Self 是当前类型的别名 = Rectangle
Rectangle { width, height }
}
// 关联函数:特殊构造器
fn square(size: f64) -> Self {
Rectangle {
width: size,
height: size,
}
}
// 关联函数:从字符串解析
fn from_str(s: &str) -> Option<Self> {
let parts: Vec<&str> = s.split('x').collect();
if parts.len() != 2 {
return None;
}
let w = parts[0].trim().parse::<f64>().ok()?;
let h = parts[1].trim().parse::<f64>().ok()?;
Some(Rectangle::new(w, h))
}
}
fn main() {
// 关联函数用 :: 调用(不是 .)
let rect = Rectangle::new(30.0, 50.0);
let sq = Rectangle::square(25.0);
let parsed = Rectangle::from_str("100 x 200");
println!("rect area: {}", rect.area());
println!("square area: {}", sq.area());
if let Some(r) = parsed {
println!("parsed area: {}", r.area());
}
}
2.4 多个 impl 块
struct Token {
name: String,
symbol: String,
total_supply: u64,
}
// Rust 允许多个 impl 块(对 trait 实现特别有用)
impl Token {
fn new(name: String, symbol: String) -> Self {
Token {
name,
symbol,
total_supply: 0,
}
}
}
impl Token {
fn name(&self) -> &str {
&self.name
}
fn symbol(&self) -> &str {
&self.symbol
}
}
// 实现 Display trait
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({}) - Supply: {}", self.name, self.symbol, self.total_supply)
}
}
三、Debug 和 Display 派生
// #[derive] 自动实现常用 trait
#[derive(Debug, Clone, PartialEq)]
struct TokenInfo {
name: String,
symbol: String,
decimals: u8,
total_supply: u128,
}
fn main() {
let token = TokenInfo {
name: String::from("Momo Token"),
symbol: String::from("MOMO"),
decimals: 18,
total_supply: 1_000_000 * 10u128.pow(18),
};
// Debug 格式(开发调试用)
println!("{:?}", token);
// TokenInfo { name: "Momo Token", symbol: "MOMO", decimals: 18, total_supply: 1000000000000000000000000 }
// Pretty Debug
println!("{:#?}", token);
// Clone
let token2 = token.clone();
println!("Same? {}", token == token2); // PartialEq
}
四、代码实战:Rust 模拟 ERC20
use std::collections::HashMap;
use std::fmt;
/// 自定义错误类型(类比 Solidity 的 custom error)
#[derive(Debug)]
enum TokenError {
InsufficientBalance {
account: String,
balance: u128,
required: u128,
},
InsufficientAllowance {
owner: String,
spender: String,
allowance: u128,
required: u128,
},
ZeroAddress,
Overflow,
}
impl fmt::Display for TokenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenError::InsufficientBalance { account, balance, required } => {
write!(f, "Insufficient balance: {} has {}, needs {}", account, balance, required)
}
TokenError::InsufficientAllowance { owner, spender, allowance, required } => {
write!(f, "Insufficient allowance: {} approved {} for {}, needs {}",
owner, allowance, spender, required)
}
TokenError::ZeroAddress => write!(f, "Zero address not allowed"),
TokenError::Overflow => write!(f, "Arithmetic overflow"),
}
}
}
/// 事件类型(类比 Solidity 的 event)
#[derive(Debug, Clone)]
enum TokenEvent {
Transfer {
from: String,
to: String,
value: u128,
},
Approval {
owner: String,
spender: String,
value: u128,
},
}
impl fmt::Display for TokenEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TokenEvent::Transfer { from, to, value } => {
write!(f, "Transfer({} → {}, {})", from, to, value)
}
TokenEvent::Approval { owner, spender, value } => {
write!(f, "Approval({} → {}, {})", owner, spender, value)
}
}
}
}
/// ERC20 Token 结构体
struct ERC20Token {
// 元数据
name: String,
symbol: String,
decimals: u8,
// 状态
total_supply: u128,
balances: HashMap<String, u128>,
allowances: HashMap<String, HashMap<String, u128>>,
// 事件日志
events: Vec<TokenEvent>,
}
impl ERC20Token {
/// 构造函数(关联函数 ::new)
/// 类比 Solidity 的 constructor
fn new(name: &str, symbol: &str, decimals: u8, initial_supply: u128, deployer: &str) -> Self {
let mut token = ERC20Token {
name: name.to_string(),
symbol: symbol.to_string(),
decimals,
total_supply: 0,
balances: HashMap::new(),
allowances: HashMap::new(),
events: Vec::new(),
};
// 铸造初始供应量给部署者
let amount = initial_supply * 10u128.pow(decimals as u32);
token.mint(deployer, amount).expect("Initial mint failed");
token
}
/// totalSupply() - 查看总供应量
fn total_supply(&self) -> u128 {
self.total_supply
}
/// balanceOf(address) - 查看余额
fn balance_of(&self, account: &str) -> u128 {
self.balances.get(account).copied().unwrap_or(0)
}
/// allowance(owner, spender) - 查看授权额度
fn allowance(&self, owner: &str, spender: &str) -> u128 {
self.allowances
.get(owner)
.and_then(|m| m.get(spender))
.copied()
.unwrap_or(0)
}
/// transfer(to, amount) - 直接转账
/// msg.sender 在 Rust 中通过参数 `from` 模拟
fn transfer(&mut self, from: &str, to: &str, amount: u128) -> Result<bool, TokenError> {
self._transfer(from, to, amount)?;
Ok(true)
}
/// approve(spender, amount) - 授权额度
fn approve(&mut self, owner: &str, spender: &str, amount: u128) -> Result<bool, TokenError> {
self._approve(owner, spender, amount)?;
Ok(true)
}
/// transferFrom(from, to, amount) - 代理转账
fn transfer_from(
&mut self,
spender: &str, // msg.sender
from: &str,
to: &str,
amount: u128,
) -> Result<bool, TokenError> {
self._spend_allowance(from, spender, amount)?;
self._transfer(from, to, amount)?;
Ok(true)
}
// ============ 内部方法 ============
/// 内部转账逻辑
fn _transfer(&mut self, from: &str, to: &str, amount: u128) -> Result<(), TokenError> {
if from.is_empty() || to.is_empty() {
return Err(TokenError::ZeroAddress);
}
let from_balance = self.balance_of(from);
if from_balance < amount {
return Err(TokenError::InsufficientBalance {
account: from.to_string(),
balance: from_balance,
required: amount,
});
}
// 执行转账
*self.balances.entry(from.to_string()).or_insert(0) -= amount;
*self.balances.entry(to.to_string()).or_insert(0) += amount;
// 触发事件
let event = TokenEvent::Transfer {
from: from.to_string(),
to: to.to_string(),
value: amount,
};
println!(" [EVENT] {}", event);
self.events.push(event);
Ok(())
}
/// 内部授权逻辑
fn _approve(&mut self, owner: &str, spender: &str, amount: u128) -> Result<(), TokenError> {
if owner.is_empty() || spender.is_empty() {
return Err(TokenError::ZeroAddress);
}
self.allowances
.entry(owner.to_string())
.or_insert_with(HashMap::new)
.insert(spender.to_string(), amount);
let event = TokenEvent::Approval {
owner: owner.to_string(),
spender: spender.to_string(),
value: amount,
};
println!(" [EVENT] {}", event);
self.events.push(event);
Ok(())
}
/// 消费授权额度
fn _spend_allowance(&mut self, owner: &str, spender: &str, amount: u128) -> Result<(), TokenError> {
let current = self.allowance(owner, spender);
if current == u128::MAX {
return Ok(()); // 无限授权
}
if current < amount {
return Err(TokenError::InsufficientAllowance {
owner: owner.to_string(),
spender: spender.to_string(),
allowance: current,
required: amount,
});
}
self._approve(owner, spender, current - amount)?;
Ok(())
}
/// 铸造代币
fn mint(&mut self, to: &str, amount: u128) -> Result<(), TokenError> {
if to.is_empty() {
return Err(TokenError::ZeroAddress);
}
self.total_supply = self.total_supply.checked_add(amount)
.ok_or(TokenError::Overflow)?;
*self.balances.entry(to.to_string()).or_insert(0) += amount;
let event = TokenEvent::Transfer {
from: "0x0".to_string(),
to: to.to_string(),
value: amount,
};
println!(" [EVENT] {}", event);
self.events.push(event);
Ok(())
}
/// 销毁代币
fn burn(&mut self, from: &str, amount: u128) -> Result<(), TokenError> {
let balance = self.balance_of(from);
if balance < amount {
return Err(TokenError::InsufficientBalance {
account: from.to_string(),
balance,
required: amount,
});
}
*self.balances.entry(from.to_string()).or_insert(0) -= amount;
self.total_supply -= amount;
let event = TokenEvent::Transfer {
from: from.to_string(),
to: "0x0".to_string(),
value: amount,
};
println!(" [EVENT] {}", event);
self.events.push(event);
Ok(())
}
/// 打印代币信息
fn info(&self) {
println!("Token: {} ({})", self.name, self.symbol);
println!("Decimals: {}", self.decimals);
println!("Total Supply: {} (raw: {})",
self.total_supply / 10u128.pow(self.decimals as u32),
self.total_supply
);
}
/// 获取事件日志
fn event_count(&self) -> usize {
self.events.len()
}
}
// 实现 Display 方便打印
impl fmt::Display for ERC20Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({}) - Total Supply: {}",
self.name, self.symbol,
self.total_supply / 10u128.pow(self.decimals as u32)
)
}
}
fn main() {
println!("=== Creating MomoToken ===");
let mut token = ERC20Token::new("Momo Token", "MOMO", 18, 1_000_000, "Alice");
token.info();
println!();
// 查看余额
println!("=== Initial Balances ===");
let one_token = 10u128.pow(18);
println!("Alice: {} MOMO", token.balance_of("Alice") / one_token);
println!("Bob: {} MOMO", token.balance_of("Bob") / one_token);
// 直接转账
println!("\n=== Transfer 100,000 MOMO: Alice → Bob ===");
token.transfer("Alice", "Bob", 100_000 * one_token).unwrap();
println!("Alice: {} MOMO", token.balance_of("Alice") / one_token);
println!("Bob: {} MOMO", token.balance_of("Bob") / one_token);
// Approve + TransferFrom (模拟 DEX 交互)
println!("\n=== Approve DEX to spend Bob's tokens ===");
token.approve("Bob", "DEX", 50_000 * one_token).unwrap();
println!("Bob's allowance for DEX: {} MOMO",
token.allowance("Bob", "DEX") / one_token);
println!("\n=== DEX transfers 30,000 MOMO from Bob to Charlie ===");
token.transfer_from("DEX", "Bob", "Charlie", 30_000 * one_token).unwrap();
println!("Bob: {} MOMO", token.balance_of("Bob") / one_token);
println!("Charlie: {} MOMO", token.balance_of("Charlie") / one_token);
println!("Bob's remaining allowance for DEX: {} MOMO",
token.allowance("Bob", "DEX") / one_token);
// 错误处理
println!("\n=== Error Handling ===");
match token.transfer("Charlie", "Dave", 999_999 * one_token) {
Ok(_) => println!("Transfer succeeded"),
Err(e) => println!("Transfer failed: {}", e),
}
match token.transfer_from("DEX", "Bob", "Charlie", 999_999 * one_token) {
Ok(_) => println!("TransferFrom succeeded"),
Err(e) => println!("TransferFrom failed: {}", e),
}
// 统计
println!("\n=== Summary ===");
println!("{}", token);
println!("Total events emitted: {}", token.event_count());
}
// ============ 单元测试 ============
#[cfg(test)]
mod tests {
use super::*;
fn setup() -> ERC20Token {
ERC20Token::new("Test Token", "TEST", 18, 1_000_000, "Alice")
}
#[test]
fn test_initial_supply() {
let token = setup();
let one_token = 10u128.pow(18);
assert_eq!(token.total_supply(), 1_000_000 * one_token);
assert_eq!(token.balance_of("Alice"), 1_000_000 * one_token);
}
#[test]
fn test_transfer() {
let mut token = setup();
let one_token = 10u128.pow(18);
token.transfer("Alice", "Bob", 100 * one_token).unwrap();
assert_eq!(token.balance_of("Alice"), 999_900 * one_token);
assert_eq!(token.balance_of("Bob"), 100 * one_token);
}
#[test]
fn test_transfer_insufficient() {
let mut token = setup();
let one_token = 10u128.pow(18);
let result = token.transfer("Bob", "Alice", 100 * one_token);
assert!(result.is_err());
}
#[test]
fn test_approve_and_transfer_from() {
let mut token = setup();
let one_token = 10u128.pow(18);
token.approve("Alice", "DEX", 500 * one_token).unwrap();
assert_eq!(token.allowance("Alice", "DEX"), 500 * one_token);
token.transfer_from("DEX", "Alice", "Bob", 200 * one_token).unwrap();
assert_eq!(token.balance_of("Bob"), 200 * one_token);
assert_eq!(token.allowance("Alice", "DEX"), 300 * one_token);
}
#[test]
fn test_transfer_from_insufficient_allowance() {
let mut token = setup();
let one_token = 10u128.pow(18);
token.approve("Alice", "DEX", 100 * one_token).unwrap();
let result = token.transfer_from("DEX", "Alice", "Bob", 200 * one_token);
assert!(result.is_err());
}
#[test]
fn test_mint_and_burn() {
let mut token = setup();
let one_token = 10u128.pow(18);
let initial = token.total_supply();
token.mint("Bob", 1000 * one_token).unwrap();
assert_eq!(token.total_supply(), initial + 1000 * one_token);
assert_eq!(token.balance_of("Bob"), 1000 * one_token);
token.burn("Bob", 500 * one_token).unwrap();
assert_eq!(token.total_supply(), initial + 500 * one_token);
assert_eq!(token.balance_of("Bob"), 500 * one_token);
}
}
五、Solidity vs Rust struct/impl 对比
| 特性 | Solidity | Rust |
|---|---|---|
| 定义 | struct User { ... } | struct User { ... } |
| 方法 | 合约级函数 | impl User { fn method(&self) } |
| 构造函数 | constructor() | fn new() -> Self(惯例) |
| 可见性 | 字段无可见性 | pub / 默认私有 |
| 继承 | is 关键字 | 无继承(用 trait) |
| 克隆 | 自动(memory 类型) | 需要 #[derive(Clone)] |
| 比较 | 不支持直接比较 | #[derive(PartialEq)] |
| 打印 | 无 | #[derive(Debug)] 或 impl Display |
六、关键要点总结
| 要点 | 说明 |
|---|---|
| struct 是数据容器 | 类似 Solidity 的 struct,但更强大 |
| impl 是方法实现 | 分离数据定义和行为 |
| ::new() 是惯例 | Rust 没有构造函数语法,用关联函数 |
| &self = 只读 | 类似 Solidity 的 view |
| &mut self = 可写 | 类似 Solidity 的普通函数 |
| self = 消费 | 少用,转换操作时使用 |
| Self = 当前类型 | 在 impl 块中代替具体类型名 |
| #[derive] 自动实现 | Debug/Clone/PartialEq 等常用 trait |
七、常见误区
误区 1:以为 Rust 有构造函数
// Rust 没有 constructor 关键字
// ::new() 只是一个约定俗成的命名
// 你可以起任何名字:::create(), ::from_parts(), etc.
误区 2:忘记 &mut self
impl Counter {
fn increment(&self) { // ❌ 用了 &self
// self.count += 1; // 编译错误:不能修改不可变引用
}
fn increment(&mut self) { // ✅ 用 &mut self
self.count += 1;
}
}
误区 3:混淆方法调用和关联函数调用
let rect = Rectangle::new(10.0, 20.0); // :: 调用关联函数
let area = rect.area(); // . 调用方法
// :: 用于没有 self 参数的函数(类型级别)
// . 用于有 self 参数的方法(实例级别)
八、面试关联
Q: Rust 的 struct + impl 和面向对象语言的 class 有什么区别?
A: Rust 的 struct + impl 分离了数据和行为的定义,没有传统的类继承(inheritance)。Rust 通过 trait(接口)实现多态,通过组合(composition)实现代码复用。这种设计避免了继承带来的"脆弱基类问题"和"菱形继承问题"。在区块链上下文中,Solidity 的多重继承(contract A is B, C)可能导致复杂的存储布局问题,而 Rust 的组合方式更安全清晰。
Q: 为什么 Rust 没有构造函数?
A: Rust 选择用普通关联函数(惯例命名 new)代替专门的构造函数语法,原因是:(1) 可以有多个"构造函数"(new, with_capacity, from_str 等)而不需要方法重载;(2) 构造过程完全透明,没有隐藏的初始化逻辑;(3) 返回 Result<Self, Error> 比构造函数抛异常更符合 Rust 的错误处理哲学。
九、参考资源
| 资源 | 链接 | 说明 |
|---|---|---|
| The Rust Book Ch.5 | https://doc.rust-lang.org/book/ch05-00-structs.html | 结构体章节 |
| Rust by Example: Structs | https://doc.rust-lang.org/rust-by-example/custom_types/structs.html | 代码示例 |
| Rust API Guidelines | https://rust-lang.github.io/api-guidelines/naming.html | 命名约定 |
| Rustlings structs | rustlings 的 structs 系列练习 | 动手练习 |