返回 SC 笔记
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 traitSolidity 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

参考资源