SC Day 13
Rust - trait + 泛型(generics) + trait bound
### 1. Trait:定义共享行为
2026-04-13
第一阶段:基础构建rusttraitgenericstrait-boundpolymorphism
日期: 2026-04-13 方向: Rust 阶段: 第一阶段:基础构建 标签: #rust #trait #generics #trait-bound #polymorphism
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 掌握 trait 定义与实现、泛型函数与结构体、trait bound 的各种写法 |
| 实操 | 实现 Transferable trait 用于不同代币类型、编写泛型函数处理区块链数据 |
| 产出 | 完整的 trait 体系代码 + 与 Solidity interface 的深度对比 |
核心概念
1. Trait:定义共享行为
Trait 是 Rust 中定义共享行为的方式,类似于其他语言的接口 (interface),但功能更强大——trait 可以提供默认实现。
/// 定义一个 Transferable trait:所有可转移的代币都必须实现
trait Transferable {
/// 获取代币名称
fn name(&self) -> &str;
/// 获取持有者地址
fn holder(&self) -> &str;
/// 转移代币(必须由实现者提供)
fn transfer(&mut self, to: &str) -> Result<(), String>;
/// 默认实现:打印转账信息
fn log_transfer(&self, to: &str) {
println!("[{}] {} -> {}", self.name(), self.holder(), to);
}
}
为不同代币类型实现 Transferable
// ========== ERC20 代币 ==========
struct FungibleToken {
name: String,
symbol: String,
holder: String,
balance: u128,
decimals: u8,
}
impl Transferable for FungibleToken {
fn name(&self) -> &str {
&self.name
}
fn holder(&self) -> &str {
&self.holder
}
fn transfer(&mut self, to: &str) -> Result<(), String> {
if self.balance == 0 {
return Err("余额为零,无法转移".to_string());
}
self.log_transfer(to); // 使用默认实现
self.holder = to.to_string();
Ok(())
}
}
// ========== ERC721 NFT ==========
struct NonFungibleToken {
collection_name: String,
token_id: u64,
owner: String,
metadata_uri: String,
}
impl Transferable for NonFungibleToken {
fn name(&self) -> &str {
&self.collection_name
}
fn holder(&self) -> &str {
&self.owner
}
fn transfer(&mut self, to: &str) -> Result<(), String> {
if to == self.owner {
return Err("不能转给自己".to_string());
}
self.log_transfer(to);
self.owner = to.to_string();
Ok(())
}
// 覆盖默认实现:NFT 需要显示 token_id
fn log_transfer(&self, to: &str) {
println!(
"[{} #{}] {} -> {}",
self.collection_name, self.token_id, self.owner, to
);
}
}
// ========== ERC1155 多代币 ==========
struct MultiToken {
name: String,
token_id: u64,
owner: String,
amount: u128,
}
impl Transferable for MultiToken {
fn name(&self) -> &str {
&self.name
}
fn holder(&self) -> &str {
&self.owner
}
fn transfer(&mut self, to: &str) -> Result<(), String> {
if self.amount == 0 {
return Err("持有量为零".to_string());
}
self.log_transfer(to);
self.owner = to.to_string();
Ok(())
}
}
2. 泛型 (Generics):编写适用于多种类型的代码
泛型让你编写一个函数/结构体,可以处理多种类型,而不必为每种类型重复代码。
// ========== 泛型结构体 ==========
/// 通用的交易记录,T 代表代币类型,可以是任何类型
struct Transaction<T> {
from: String,
to: String,
asset: T,
timestamp: u64,
tx_hash: String,
}
impl<T> Transaction<T> {
fn new(from: String, to: String, asset: T, timestamp: u64) -> Self {
let tx_hash = format!("0x{:016x}", timestamp); // 简化的 hash
Transaction { from, to, asset, timestamp, tx_hash }
}
fn is_self_transfer(&self) -> bool {
self.from == self.to
}
}
// 使用:同一个 Transaction 可以包裹不同类型
fn demo_generics() {
// ETH 转账(用 u128 表示 wei)
let eth_tx = Transaction::new(
"0xAlice".into(),
"0xBob".into(),
1_000_000_000_000_000_000u128, // 1 ETH in wei
1700000000,
);
// NFT 转账
let nft_tx = Transaction::new(
"0xAlice".into(),
"0xBob".into(),
NonFungibleToken {
collection_name: "CryptoPunks".into(),
token_id: 1234,
owner: "0xAlice".into(),
metadata_uri: "ipfs://...".into(),
},
1700000001,
);
// 多代币转账
let multi_tx = Transaction::new(
"0xAlice".into(),
"0xBob".into(),
vec!["USDC: 1000", "WETH: 0.5"],
1700000002,
);
}
3. Trait Bound:约束泛型的行为
裸泛型 <T> 只知道 T 是"某个类型",不能调用任何方法。Trait Bound 告诉编译器:T 必须实现某些 trait,这样你就可以调用那些方法。
use std::fmt::{Display, Debug};
// ========== 基础 Trait Bound 写法 ==========
/// 方法1:在泛型参数后用冒号指定 bound
fn print_asset_info<T: Transferable>(asset: &T) {
println!("代币: {}, 持有者: {}", asset.name(), asset.holder());
}
/// 方法2:多个 bound 用 + 连接
fn print_detailed<T: Transferable + Display + Debug>(asset: &T) {
println!("详情: {:?}", asset);
println!("名称: {}", asset.name());
println!("显示: {}", asset); // 需要 Display trait
}
/// 方法3:where 子句(推荐用于复杂 bound)
fn process_transfer<T, U>(from_asset: &mut T, to_asset: &U) -> Result<(), String>
where
T: Transferable + Clone,
U: Transferable,
{
println!(
"处理转账: {} -> {}",
from_asset.name(),
to_asset.holder()
);
from_asset.transfer(to_asset.holder())
}
/// 方法4:impl Trait 语法(更简洁,适合简单情况)
fn get_holder(asset: &impl Transferable) -> &str {
asset.holder()
}
/// 方法5:返回值中使用 impl Trait
fn create_default_token() -> impl Transferable {
FungibleToken {
name: "Default Token".to_string(),
symbol: "DFT".to_string(),
holder: "0x0000".to_string(),
balance: 0,
decimals: 18,
}
}
4. 高级 Trait 用法
关联类型 (Associated Types)
/// 使用关联类型定义代币标准
trait TokenStandard {
/// 关联类型:每种标准有自己的 ID 类型
type TokenId;
type Balance;
fn token_id(&self) -> &Self::TokenId;
fn balance_of(&self, holder: &str) -> Self::Balance;
}
struct ERC20Token {
address: String,
balances: std::collections::HashMap<String, u128>,
}
impl TokenStandard for ERC20Token {
type TokenId = String; // ERC20 用合约地址作为 ID
type Balance = u128;
fn token_id(&self) -> &String {
&self.address
}
fn balance_of(&self, holder: &str) -> u128 {
*self.balances.get(holder).unwrap_or(&0)
}
}
struct ERC721Token {
address: String,
token_id: u64,
owner: String,
}
impl TokenStandard for ERC721Token {
type TokenId = (String, u64); // ERC721 用合约地址 + tokenId
type Balance = bool; // 要么持有要么不持有
fn token_id(&self) -> &(String, u64) {
// 这里简化处理,实际应该存储元组
todo!()
}
fn balance_of(&self, holder: &str) -> bool {
self.owner == holder
}
}
Trait 继承
/// 基础 trait
trait Identifiable {
fn address(&self) -> &str;
}
/// Verifiable 继承 Identifiable
trait Verifiable: Identifiable {
fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool;
// 可以使用父 trait 的方法
fn verify_and_log(&self, message: &[u8], signature: &[u8]) -> bool {
let result = self.verify_signature(message, signature);
println!("验证 {} 的签名: {}", self.address(), result);
result
}
}
struct Account {
addr: String,
public_key: Vec<u8>,
}
// 必须同时实现 Identifiable 和 Verifiable
impl Identifiable for Account {
fn address(&self) -> &str {
&self.addr
}
}
impl Verifiable for Account {
fn verify_signature(&self, _message: &[u8], _signature: &[u8]) -> bool {
// 简化:实际应做密码学验证
!self.public_key.is_empty()
}
}
Trait Object:动态分发
/// 当你需要在同一个集合中存放不同类型时,使用 trait object
fn portfolio_demo() {
// dyn Transferable = trait object,运行时动态分发
let mut portfolio: Vec<Box<dyn Transferable>> = Vec::new();
portfolio.push(Box::new(FungibleToken {
name: "USDC".into(),
symbol: "USDC".into(),
holder: "0xAlice".into(),
balance: 1000_000_000, // 1000 USDC
decimals: 6,
}));
portfolio.push(Box::new(NonFungibleToken {
collection_name: "BAYC".into(),
token_id: 8888,
owner: "0xAlice".into(),
metadata_uri: "ipfs://...".into(),
}));
// 统一操作不同类型的资产
for asset in &portfolio {
println!("资产: {}, 持有者: {}", asset.name(), asset.holder());
}
}
代码实战:完整的区块链资产管理系统
use std::collections::HashMap;
use std::fmt;
// ========== Trait 定义 ==========
trait Asset: fmt::Display {
fn asset_type(&self) -> &str;
fn value_in_usd(&self) -> f64;
fn holder(&self) -> &str;
}
trait Stakeable: Asset {
fn stake(&mut self, amount: f64) -> Result<(), String>;
fn unstake(&mut self, amount: f64) -> Result<(), String>;
fn staked_amount(&self) -> f64;
fn apy(&self) -> f64;
/// 默认实现:计算预期年收益
fn expected_yearly_return(&self) -> f64 {
self.staked_amount() * self.apy() / 100.0
}
}
// ========== 代币实现 ==========
struct StakingToken {
name: String,
holder: String,
balance: f64,
staked: f64,
price_usd: f64,
annual_rate: f64,
}
impl fmt::Display for StakingToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {:.2} (质押: {:.2}) @ ${:.2}",
self.name, self.balance, self.staked, self.price_usd
)
}
}
impl Asset for StakingToken {
fn asset_type(&self) -> &str { "Staking Token" }
fn value_in_usd(&self) -> f64 {
(self.balance + self.staked) * self.price_usd
}
fn holder(&self) -> &str { &self.holder }
}
impl Stakeable for StakingToken {
fn stake(&mut self, amount: f64) -> Result<(), String> {
if amount > self.balance {
return Err(format!(
"余额不足: 需要 {:.2},可用 {:.2}",
amount, self.balance
));
}
self.balance -= amount;
self.staked += amount;
Ok(())
}
fn unstake(&mut self, amount: f64) -> Result<(), String> {
if amount > self.staked {
return Err(format!(
"质押量不足: 需要 {:.2},已质押 {:.2}",
amount, self.staked
));
}
self.staked -= amount;
self.balance += amount;
Ok(())
}
fn staked_amount(&self) -> f64 { self.staked }
fn apy(&self) -> f64 { self.annual_rate }
}
// ========== 泛型投资组合 ==========
struct Portfolio<T: Asset> {
name: String,
assets: Vec<T>,
}
impl<T: Asset> Portfolio<T> {
fn new(name: String) -> Self {
Portfolio { name, assets: Vec::new() }
}
fn add_asset(&mut self, asset: T) {
self.assets.push(asset);
}
fn total_value(&self) -> f64 {
self.assets.iter().map(|a| a.value_in_usd()).sum()
}
fn print_summary(&self) {
println!("=== {} ===", self.name);
for asset in &self.assets {
println!(" {} | 价值: ${:.2}", asset, asset.value_in_usd());
}
println!(" 总价值: ${:.2}", self.total_value());
}
}
// 为包含 Stakeable 资产的组合添加额外方法
impl<T: Stakeable> Portfolio<T> {
fn total_staked_value(&self) -> f64 {
self.assets.iter()
.map(|a| a.staked_amount() * a.value_in_usd() /
(a.value_in_usd() / a.staked_amount().max(0.01)))
.sum()
}
fn total_expected_return(&self) -> f64 {
self.assets.iter()
.map(|a| a.expected_yearly_return())
.sum()
}
}
// ========== 泛型函数示例 ==========
/// 找到价值最高的资产
fn find_most_valuable<T: Asset>(assets: &[T]) -> Option<&T> {
assets.iter().max_by(|a, b| {
a.value_in_usd().partial_cmp(&b.value_in_usd()).unwrap()
})
}
/// 过滤出某个持有者的资产
fn filter_by_holder<'a, T: Asset>(assets: &'a [T], holder: &str) -> Vec<&'a T> {
assets.iter()
.filter(|a| a.holder() == holder)
.collect()
}
fn main() {
let mut portfolio = Portfolio::new("DeFi 质押组合".to_string());
let mut eth = StakingToken {
name: "stETH".into(),
holder: "0xAlice".into(),
balance: 10.0,
staked: 0.0,
price_usd: 3500.0,
annual_rate: 3.5,
};
// 质押 8 ETH
eth.stake(8.0).expect("质押失败");
println!("预期年收益: ${:.2}", eth.expected_yearly_return());
portfolio.add_asset(eth);
portfolio.add_asset(StakingToken {
name: "ATOM".into(),
holder: "0xAlice".into(),
balance: 100.0,
staked: 500.0,
price_usd: 12.0,
annual_rate: 18.0,
});
portfolio.print_summary();
println!("预期总年收益: ${:.2}", portfolio.total_expected_return());
}
关键要点总结
| 要点 | 说明 |
|---|---|
| trait 定义共享行为 | 类似接口,但可以有默认实现 |
| 泛型编译期单态化 | 零开销抽象,生成针对具体类型的代码 |
| trait bound 约束泛型 | 告诉编译器 T 必须具备哪些能力 |
| where 子句 | 复杂 bound 时更清晰 |
| impl Trait | 函数参数/返回值的简写语法 |
| dyn Trait | 动态分发,运行时决定调用哪个实现 |
| 关联类型 | 让 trait 的实现者决定某些类型 |
| trait 继承 | 子 trait 可以依赖父 trait 的方法 |
Rust trait vs Solidity interface
| 特性 | Rust trait | Solidity interface |
|---|---|---|
| 默认实现 | 支持 | 不支持(必须全部实现) |
| 泛型 | 支持 | 不支持 |
| 数据字段 | 不能定义(只有方法) | 不能定义 |
| 多重实现 | 一个类型可实现多个 trait | 一个合约可实现多个 interface |
| 编译时检查 | 完整的类型检查 | ABI 级别的检查 |
| 动态分发 | dyn Trait (堆分配) | 通过地址调用(天然动态) |
常见误区
误区 1:Trait Object 和泛型混淆
// 泛型:编译期确定类型,零开销,但不能混合不同类型
fn process_static<T: Asset>(asset: &T) { /* ... */ }
// Trait Object:运行时分发,有额外开销,但可以混合不同类型
fn process_dynamic(asset: &dyn Asset) { /* ... */ }
// 只有 trait object 才能放入同一个集合
let mixed: Vec<Box<dyn Asset>> = vec![/* 不同类型的资产 */];
误区 2:忘记 trait 需要引入作用域
// 即使类型实现了 trait,也需要 use 才能调用 trait 方法
// use crate::Transferable; // 如果不 use,下面会报错
// asset.transfer("0xBob"); // 编译错误:找不到方法
误区 3:不理解孤儿规则 (Orphan Rule)
// 你不能为外部类型实现外部 trait
// impl Display for Vec<u8> { } // 编译错误!
// 解决方案:创建 newtype wrapper
struct ByteArray(Vec<u8>);
impl fmt::Display for ByteArray {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{}", hex::encode(&self.0))
}
}
面试关联
Q: Rust 的 trait 和 Solidity 的 interface 有什么区别?
30 秒回答:最大区别是 Rust trait 可以有默认实现和泛型,而 Solidity interface 所有函数必须由合约实现。另外 Rust trait 在编译期做类型检查(单态化),Solidity 是在 ABI 级别做运行时检查。Rust 的 trait bound 系统让泛型编程非常强大,Solidity 没有泛型。
Q: 什么时候用泛型,什么时候用 trait object?
- 泛型 (静态分发):编译期确定类型,性能最好,适合类型已知的场景
- Trait Object (动态分发):运行时确定类型,需要
Box<dyn Trait>堆分配,适合需要在集合中存放不同类型的场景 - 经验法则:优先用泛型;当需要异构集合或类型在编译期不可知时,才用 trait object