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

特性SolidityRust
定义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.5https://doc.rust-lang.org/book/ch05-00-structs.html结构体章节
Rust by Example: Structshttps://doc.rust-lang.org/rust-by-example/custom_types/structs.html代码示例
Rust API Guidelineshttps://rust-lang.github.io/api-guidelines/naming.html命名约定
Rustlings structsrustlings 的 structs 系列练习动手练习