返回 SC 笔记
SC Day 7

Week 1 总结与复习

回顾 Week 1 所有核心概念,构建系统化知识框架

2026-04-16
第一阶段:基础构建
reviewcomparisonweek1solidityrust

日期: 2026-04-16 方向: Solidity + Rust 阶段: 第一阶段:基础构建 标签: #review #comparison #week1 #solidity #rust


今日目标

类型内容
学习回顾 Week 1 所有核心概念,构建系统化知识框架
实操完成自测题,对比 Solidity 与 Rust 的关键差异
产出Week 1 知识图谱 + 对比表 + 自测通过

一、Week 1 学习路径回顾

Day 1: Solidity - Remix IDE + 数据类型 + 变量
Day 2: Rust    - 安装环境 + Cargo + 变量/可变性 + 基本类型
Day 3: Solidity - 函数可见性 + 事件 + 错误处理
Day 4: Rust    - 所有权 + 借用 + 引用
Day 5: Solidity - mapping + struct + array + enum
Day 6: Rust    - 字符串 + 切片 + 生命周期
Day 7: 复习    - 总结对比 + 自测

二、Solidity vs Rust 全面对比

2.1 语言定位

维度SolidityRust
设计目标EVM 智能合约系统级编程
运行环境以太坊虚拟机 (EVM)原生编译(x86/ARM)
区块链用途Ethereum/L2 合约Solana/Near/Substrate 链上程序
内存模型storage/memory/calldata栈/堆/所有权系统
安全保障运行时检查 + 审计编译时保证 + 所有权
学习曲线中等(像 JS/Java)陡峭(所有权/生命周期)
包管理npm (Hardhat/Foundry)Cargo(内置)
测试外部框架 (Foundry/Hardhat)内置 (cargo test)
编译速度很快较慢(但增量编译快)

2.2 数据类型对比

类型SolidityRust说明
整数uint256 (默认)i32 (默认)Solidity 偏向大数
无符号uint8 ~ uint256u8 ~ u128Solidity 最大 256位
有符号int8 ~ int256i8 ~ i128-
布尔boolbool相同
地址address (20 bytes)无原生类型Rust 用 [u8; 32]
字符串stringString / &strRust 区分所有权
固定字节bytes1 ~ bytes32[u8; N]类似
动态字节bytesVec<u8>类似
浮点无!f32 / f64区块链避免浮点
数组T[] / T[N]Vec<T> / [T; N]类似
映射mapping(K => V)HashMap<K, V>Solidity 不可遍历
枚举enum (简单)enum (带数据)Rust 枚举更强大
结构体structstruct类似
元组函数返回值(T1, T2, ...)Rust 更常用

2.3 变量与可变性对比

特性SolidityRust
默认可变性可变不可变
声明可变变量默认就是let mut x
常量constant (编译时)const (编译时)
不可变量immutable (部署时)let(默认行为)
变量遮蔽不支持支持(Shadowing)
类型推导不支持(必须声明)支持(大部分情况)

2.4 函数对比

特性SolidityRust
可见性public/private/internal/externalpub / 默认私有
只读标记view&self
纯计算pure无特殊标记(pure 是默认)
修饰符modifier无(用 trait 或手动检查)
错误处理require/revert/assert/custom errorResult<T, E> / panic!
返回值returns (type)-> Type
多返回值returns (T1, T2)-> (T1, T2) 元组
重载支持不支持(但有 trait)
默认参数不支持不支持

2.5 内存管理对比

概念SolidityRust
持久存储storage(链上永久)无(需要序列化到磁盘)
临时内存memory(函数调用内)栈(自动释放)
只读数据calldata(交易输入)&T(不可变引用)
堆分配new T[](memory中)Box<T>, Vec<T>, String
垃圾回收EVM 自动管理无需(所有权系统)
成本gas(storage 最贵)CPU 时间(堆分配较贵)

2.6 事件/日志对比

特性SolidityRust
日志机制event + emitprintln! / log crate
链上日志交易日志(Logs)Solana: msg! / emit!
可过滤indexed 参数取决于运行时环境
成本很低(vs storage)N/A(不涉及 gas)

三、核心概念对照代码

3.1 "Hello World" 对比

// Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract HelloWorld {
    string public message = "Hello, Solidity!";

    function setMessage(string calldata _msg) external {
        message = _msg;
    }
}
// Rust
fn main() {
    let mut message = String::from("Hello, Rust!");
    println!("{}", message);

    message = String::from("New message");
    println!("{}", message);
}

3.2 计数器对比

// Solidity
contract Counter {
    uint256 public count;

    function increment() external {
        count += 1;
    }

    function decrement() external {
        require(count > 0, "Count is zero");
        count -= 1;
    }

    function getCount() external view returns (uint256) {
        return count;
    }
}
// Rust
struct Counter {
    count: u64,
}

impl Counter {
    fn new() -> Self {
        Counter { count: 0 }
    }

    fn increment(&mut self) {
        self.count += 1;
    }

    fn decrement(&mut self) -> Result<(), String> {
        if self.count == 0 {
            return Err("Count is zero".to_string());
        }
        self.count -= 1;
        Ok(())
    }

    fn get_count(&self) -> u64 {
        self.count
    }
}

fn main() {
    let mut counter = Counter::new();
    counter.increment();
    counter.increment();
    println!("Count: {}", counter.get_count()); // 2

    match counter.decrement() {
        Ok(()) => println!("Decremented: {}", counter.get_count()),
        Err(e) => println!("Error: {}", e),
    }
}

3.3 简单存储对比

// Solidity
contract SimpleStorage {
    mapping(string => string) private store;

    event ValueSet(address indexed setter, string key, string value);
    error KeyEmpty();

    function set(string calldata key, string calldata value) external {
        if (bytes(key).length == 0) revert KeyEmpty();
        store[key] = value;
        emit ValueSet(msg.sender, key, value);
    }

    function get(string calldata key) external view returns (string memory) {
        return store[key];
    }
}
// Rust
use std::collections::HashMap;

struct SimpleStorage {
    store: HashMap<String, String>,
}

impl SimpleStorage {
    fn new() -> Self {
        SimpleStorage {
            store: HashMap::new(),
        }
    }

    fn set(&mut self, key: &str, value: &str) -> Result<(), String> {
        if key.is_empty() {
            return Err("Key cannot be empty".to_string());
        }
        self.store.insert(key.to_string(), value.to_string());
        println!("[Event] ValueSet: key={}, value={}", key, value);
        Ok(())
    }

    fn get(&self, key: &str) -> Option<&String> {
        self.store.get(key)
    }
}

fn main() {
    let mut storage = SimpleStorage::new();
    storage.set("name", "Alice").unwrap();
    storage.set("age", "25").unwrap();

    match storage.get("name") {
        Some(value) => println!("name = {}", value),
        None => println!("Key not found"),
    }
}

四、Week 1 关键要点清单

Solidity 要点

  • 理解 Remix IDE 的编译和部署流程
  • 掌握所有基础数据类型(uint/int/bool/address/bytes/string/enum)
  • 理解状态变量、局部变量、全局变量的区别
  • 理解 constant 和 immutable 的区别与 gas 优化
  • 掌握四种函数可见性(public/private/internal/external)
  • 理解 view 和 pure 函数
  • 会写自定义 modifier
  • 理解事件(event)的用途和 indexed 参数
  • 掌握四种错误处理方式(require/revert/assert/custom error)
  • 理解 mapping 的限制(不可遍历、不可检查存在性)
  • 掌握 struct 的 storage vs memory 区别
  • 理解动态数组的 gas 陷阱

Rust 要点

  • 会用 Cargo 创建、构建、运行项目
  • 理解 let(不可变)vs let mut(可变)vs const
  • 理解 Shadowing 与 mut 的区别
  • 掌握所有基本类型(i32/u64/f64/bool/char/tuple/array)
  • 深入理解所有权三大规则
  • 理解 Move 语义 vs Copy trait
  • 掌握不可变引用(&T)和可变引用(&mut T)
  • 牢记借用规则:多个 &T 或一个 &mut T
  • 理解 String vs &str 的区别
  • 会使用切片(&[T]、&str)
  • 理解生命周期基础和省略规则
  • 会解决常见的编译器错误(borrow of moved value 等)

五、自测题

Solidity 部分

Q1: 下面的代码有什么问题?

contract Bug {
    uint8 public value = 300;
}
<details> <summary>答案</summary>

uint8 最大值为 255,300 超出范围,编译时就会报错。应该使用 uint16 或更大的类型。

</details>

Q2: 以下代码的 getUser 函数有 bug,是什么?

contract UserStore {
    struct User { string name; uint balance; }
    mapping(address => User) users;

    function updateBalance(address _addr, uint _amount) public {
        User memory user = users[_addr];
        user.balance = _amount;
    }
}
<details> <summary>答案</summary>

User memory user 是从 storage 复制到 memory 的副本。修改副本不会影响 storage 中的实际数据。应该改为 User storage user = users[_addr];

</details>

Q3: external 函数和 public 函数有什么区别?什么时候用 external

<details> <summary>答案</summary>

external 只能从外部调用,参数可以使用 calldata(只读、不复制);public 内外都能调用,参数用 memory(复制到内存)。当函数只需要被外部调用且有大型数组/字符串参数时,用 external + calldata 更省 gas。

</details>

Q4: 以下四种错误处理方式,哪种 gas 消耗最低?

// A
require(x > 0, "Must be positive");

// B
if (x == 0) revert("Must be positive");

// C
assert(x > 0);

// D
error NotPositive();
if (x == 0) revert NotPositive();
<details> <summary>答案</summary>

D 最省 gas。自定义错误只存储 4 字节的函数选择器,不需要存储和编码字符串。A 和 B 消耗类似(都有字符串),C 从 0.8.0 起也使用 revert,但没有错误信息。

</details>

Q5: 为什么 mapping 不能遍历?如何实现遍历?

<details> <summary>答案</summary>

Mapping 的键通过 keccak256 哈希后分散存储在 2^256 的存储空间中,没有维护键的列表,所以无法遍历。实现遍历需要额外维护一个数组存储所有键:mapping(address => uint) balances; address[] allUsers;

</details>

Rust 部分

Q6: 以下代码会编译通过吗?如果不会,为什么?

fn main() {
    let s = String::from("hello");
    let s2 = s;
    println!("{}", s);
}
<details> <summary>答案</summary>

不会。let s2 = s; 触发了 Move,s 的所有权转移给了 s2,之后 s 不再有效。println!("{}", s) 会报错 "borrow of moved value"。修复方法:使用 s.clone() 或传递引用 &s

</details>

Q7: 以下代码有什么问题?

fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &mut s;
    println!("{} {}", r1, r2);
}
<details> <summary>答案</summary>

违反借用规则:不能同时存在不可变引用(r1)和可变引用(r2)。修复方法:在使用完 r1 之后再创建 r2

</details>

Q8: String&str 有什么区别?函数参数应该用哪个?

<details> <summary>答案</summary>

String 是堆分配的、拥有所有权的字符串;&str 是字符串切片(引用)。函数参数推荐用 &str,因为它更通用——既可以接受 &String(自动解引用),也可以接受字符串字面量。

</details>

Q9: 什么是 Shadowing?它和 mut 有什么区别?

<details> <summary>答案</summary>

Shadowing 是用同名 let 声明创建新变量,遮蔽旧变量。与 mut 的关键区别:(1) Shadowing 可以改变类型,mut 不行;(2) Shadowing 创建的是新变量,旧变量被遮蔽但不是被修改。

let x = "123";      // &str
let x = x.parse::<i32>().unwrap(); // i32, 类型改变!Shadowing OK

let mut y = "123";
// y = y.parse::<i32>().unwrap(); // ❌ 类型不能变
</details>

Q10: 生命周期标注 'a 是什么意思?什么时候需要?

<details> <summary>答案</summary>

'a 描述引用之间的有效范围关系。当函数接受多个引用参数并返回引用时,编译器需要知道返回的引用和哪个输入有关联。三条省略规则能自动推断大多数情况,需要显式标注的最常见场景是:两个引用输入,返回其中一个。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
</details>

六、Week 1 知识图谱

Week 1 知识图谱
│
├── Solidity 基础
│   ├── 环境: Remix IDE
│   ├── 数据类型
│   │   ├── 值类型: uint, int, bool, address, bytes, enum
│   │   ├── 引用类型: string, array, struct, mapping
│   │   └── 特殊: constant, immutable
│   ├── 变量
│   │   ├── 状态变量 (storage, 链上永久)
│   │   ├── 局部变量 (memory/stack, 临时)
│   │   └── 全局变量 (msg.sender, block.timestamp 等)
│   ├── 函数
│   │   ├── 可见性: public > external > internal > private
│   │   ├── 状态: 默认/view/pure
│   │   ├── modifier: 前置/后置检查
│   │   └── payable: 接收 ETH
│   ├── 事件: event + emit + indexed
│   ├── 错误处理: require/revert/assert/custom error
│   └── 数据结构
│       ├── mapping (KV, 不可遍历)
│       ├── struct (组合类型)
│       ├── array (固定/动态)
│       └── enum (状态机)
│
├── Rust 基础
│   ├── 环境: rustup + cargo
│   ├── 数据类型
│   │   ├── 标量: i32, u64, f64, bool, char
│   │   ├── 复合: tuple, array
│   │   └── 集合: Vec, String, HashMap
│   ├── 变量与可变性
│   │   ├── let (默认不可变)
│   │   ├── let mut (可变)
│   │   ├── const (编译时常量)
│   │   └── Shadowing (类型可变)
│   ├── ★★★ 所有权系统 ★★★
│   │   ├── 三大规则
│   │   ├── Move vs Copy
│   │   ├── clone (深拷贝)
│   │   └── Drop (自动释放)
│   ├── 借用与引用
│   │   ├── &T (不可变引用, 多个)
│   │   ├── &mut T (可变引用, 唯一)
│   │   └── 借用规则 (编译时检查)
│   └── 字符串与切片
│       ├── String vs &str
│       ├── 切片: &[T], &str
│       └── 生命周期 ('a)
│
└── 交叉对比
    ├── 内存模型: storage/memory/calldata vs 栈/堆/所有权
    ├── 可变性: Solidity 默认可变 vs Rust 默认不可变
    ├── 安全: 运行时检查 vs 编译时保证
    └── 类型系统: Solidity 简单 vs Rust 强大(泛型/trait/enum)

七、下周预告

Week 2 计划:
Day 8:  Solidity - ERC20 标准详解 + 手写完整实现
Day 9:  Rust    - struct + impl + 方法 + 关联函数
Day 10: Solidity - ERC20 进阶 + OpenZeppelin + mint/burn/pause
Day 11: Rust    - enum + Option + Result + 模式匹配
Day 12: Solidity - 继承 + 接口 + 抽象合约
Day 13: Rust    - trait + 泛型基础
Day 14: 复习    - Week 2 总结

Week 2 的核心目标:

  1. 完全理解 ERC20 代币标准——这是 DeFi 的基石
  2. 掌握 Rust 的 struct/enum/trait——这是 Rust 面向对象的核心
  3. 开始理解"接口"和"抽象"的概念——Solidity 的继承 vs Rust 的 trait

八、常见误区回顾

Week 1 最容易犯的 5 个错误

  1. Solidity: 用 memory 修改 struct 以为会保存到链上 → 必须用 storage 引用

  2. Rust: 到处 .clone() 让编译器不报错 → 先理解为什么报错,优先用引用

  3. Solidity: 在循环中遍历长数组 → 可能超 gas limit,必须分页

  4. Rust: 同时创建 &T 和 &mut T → 理解借用规则是唯一的出路

  5. 通用: 以为 private 数据真的不可读 → 链上数据全部公开,private 只是接口限制


九、面试关联

综合面试题

Q: 如果你要在 Solidity 和 Rust(Solana)之间选择构建一个 DeFi 协议,你会如何考虑?

<details> <summary>参考答案</summary>

选择维度:

  1. 生态系统:Solidity 的 DeFi 生态更成熟(Uniswap/Aave/Compound 都是 Solidity),开发工具和审计资源更丰富。Solana/Rust 的 DeFi 生态较新但增长快。

  2. 性能需求:如果需要高吞吐量和低延迟(如订单簿 DEX),Solana+Rust 更适合。如果是标准 AMM/借贷,以太坊+Solidity 足够。

  3. 安全考虑:Solidity 的审计工具和审计师更多,但 Rust 的编译时安全性更强。Solana 程序的安全模型不同(账户模型 vs 合约存储),需要不同的安全思维。

  4. 用户基础:以太坊用户最多,TVL 最高。但 Solana 吸引了大量零售用户(低 gas、快速确认)。

  5. 开发效率:Solidity 学习曲线低,快速原型开发;Rust 学习曲线高,但代码质量更可靠。

  6. 组合性:以太坊的合约可组合性(composability)是 DeFi 的核心优势。Solana 的 CPI(Cross-Program Invocation)也支持,但模式不同。

我的建议:看目标用户。面向机构/高价值交易 → 以太坊/L2 + Solidity。面向零售/高频交易 → Solana + Rust。或者用 Solidity 在 L2 上部署,兼顾成本和生态。

</details>

十、参考资源

本周所有参考资源汇总

类别资源链接
Solidity 官方Solidity Docshttps://docs.soliditylang.org/
Solidity 示例Solidity by Examplehttps://solidity-by-example.org/
Solidity IDERemixhttps://remix.ethereum.org/
Rust 官方The Rust Bookhttps://doc.rust-lang.org/book/
Rust 示例Rust by Examplehttps://doc.rust-lang.org/rust-by-example/
Rust 练习Rustlingshttps://github.com/rust-lang/rustlings
Rust 在线Rust Playgroundhttps://play.rust-lang.org/
测试网Sepolia Etherscanhttps://sepolia.etherscan.io/
OpenZeppelin合约安全库https://docs.openzeppelin.com/
SolanaSolana Cookbookhttps://solanacookbook.com/