欢迎订阅专栏:10分钟Solana-性能web3
Rust 编程基础一:环境搭建、基础语法与所有权
本系列教程面向有编程经验(如 JavaScript、Python 或 Solidity)的开发者,通过对比和实例,帮助你快速掌握 Rust 的核心概念。本文是系列第一篇,涵盖环境搭建、基础语法和所有权模型。
一、为什么学习 Rust?
Rust 是一门系统级编程语言,以其内存安全、高性能和并发性著称。在区块链领域,它是 Solana、Polkadot、Near 等公链的核心开发语言。
核心优势:
- 无需 GC(垃圾回收):在编译时通过所有权系统保证内存安全。
- 零成本抽象:高级语言特性编译后与手写底层代码一样高效。
- 可靠的并发:所有权系统天然防止数据竞争。
如果你有 Solidity 经验,会发现 Rust 在安全性上的追求与智能合约安全审计的理念高度一致。
二、环境搭建
1. 安装 Rust
推荐使用 rustup 安装工具链:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装完成后,重新打开终端或执行:
source ~/.cargo/env
验证安装:
rustc --version # 编译器版本
cargo --version # 包管理工具版本
2. 创建第一个项目
使用 Cargo(Rust 的包管理工具)创建项目:
cargo new hello_rust
cd hello_rust
目录结构如下:
hello_rust/
├── Cargo.toml # 项目配置(类似 package.json)
└── src/
└── main.rs # 主入口文件
Cargo.toml 内容:
[package]
name = "hello_rust"
version = "0.1.0"
edition = "2021"
[dependencies]
3. 运行程序
src/main.rs 默认内容:
fn main() {
println!("Hello, world!");
}
运行:
cargo run
你将看到输出:Hello, world!。
常用 Cargo 命令:
| 命令 | 用途 |
|---|---|
cargo new <name> | 创建新项目 |
cargo build | 编译项目(调试模式) |
cargo build --release | 编译项目(发布模式,优化) |
cargo run | 编译并运行 |
cargo test | 运行测试 |
cargo check | 检查代码但不编译(快速) |
三、变量与数据类型
1. 变量声明与不可变性
Rust 中的变量默认不可变。这是安全性的重要设计。
fn main() {
let x = 5;
// x = 6; // 编译错误:不能对不可变变量赋值
let mut y = 10; // 使用 mut 声明可变
y = 20; // 允许
println!("x = {}, y = {}", x, y);
}
与 Solidity 对比:
- Solidity:默认
storage变量可修改,constant不可改。 - Rust:默认不可变,需显式
mut才可变。
2. 常量与变量
- 常量:编译时确定,全局有效,使用
const声明。 - 变量:运行时确定,作用域内有效。
const MAX_POINTS: u32 = 100_000; // 常量,必须标注类型
fn main() {
let x = 5; // 变量
}
3. 数据类型
Rust 是静态强类型语言,但支持类型推断。
标量类型
| 分类 | 类型 | 说明 |
|---|---|---|
| 整数 | i8, i16, i32, i64, i128 | 有符号整数 |
u8, u16, u32, u64, u128 | 无符号整数 | |
isize, usize | 与平台相关(指针宽度) | |
| 浮点数 | f32, f64 | 默认 f64 |
| 布尔 | bool | true / false |
| 字符 | char | 4 字节 Unicode,用单引号 |
fn main() {
let a: u32 = 42;
let b = 3.14; // 默认 f64
let c = true;
let d = '🦀'; // char 可以存储 emoji
}
复合类型
元组 (Tuple):固定长度,可包含不同类型。
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // 解构
println!("x = {}, y = {}", x, y);
let first = tup.0; // 索引访问
数组 (Array):固定长度,所有元素类型相同。
let arr = [1, 2, 3, 4, 5]; // 类型 [i32; 5]
let first = arr[0];
4. 类型转换
Rust 不支持隐式类型转换,必须显式转换。
let a: u32 = 42;
let b: u64 = a as u64; // 显式转换
四、控制流
1. 条件表达式 if
Rust 的 if 是表达式(有返回值),且条件必须是 bool 类型。
fn main() {
let number = 7;
if number < 5 {
println!("小于 5");
} else if number == 5 {
println!("等于 5");
} else {
println!("大于 5");
}
// 作为表达式使用
let is_even = if number % 2 == 0 { "偶数" } else { "奇数" };
println!("{}", is_even);
}
2. 循环
loop:无限循环
let mut count = 0;
loop {
count += 1;
if count == 3 {
break; // 跳出循环
}
}
while:条件循环
let mut n = 3;
while n > 0 {
println!("{}", n);
n -= 1;
}
for:遍历集合(推荐)
let arr = [10, 20, 30, 40];
for element in arr.iter() {
println!("{}", element);
}
// 范围遍历
for i in 0..5 { // 0, 1, 2, 3, 4
println!("{}", i);
}
for i in 0..=5 { // 0, 1, 2, 3, 4, 5(包含右边界)
println!("{}", i);
}
五、所有权系统
所有权是 Rust 最独特的核心概念,也是新手最容易困惑的地方。理解它,就理解了 Rust 的内存安全基础。
1. 所有权规则
- 每个值有一个所有者(变量)。
- 值在任一时刻只能有一个所有者。
- 当所有者离开作用域,值被释放(自动调用
drop)。
2. 作用域
fn main() {
{ // s 在此处未定义
let s = "hello"; // s 进入作用域
// s 在此处有效
} // s 离开作用域,内存被释放
// s 在此处无效
}
3. String 类型与内存管理
Rust 需要管理存储在堆上的数据。String 是一个堆分配的字符串类型。
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被移动到 s2
// println!("{}", s1); // 编译错误:s1 已失效
println!("{}", s2); // 正确
}
为什么 s1 失效了?
- 当
s1赋值给s2时,Rust 不会复制堆上的数据(成本高),而是**移动(Move)**所有权。 - 这避免了双重释放(Double Free)的内存安全问题。
如果确实需要复制:
let s1 = String::from("hello");
let s2 = s1.clone(); // 显式深拷贝
println!("{} {}", s1, s2); // 都有效
与 Solidity 对比:
- Solidity 中
storage变量赋值可能产生引用,但memory变量赋值会复制。 - Rust 的所有权规则更严格,在编译时就防止了悬垂引用。
4. 引用与借用
使用 & 创建引用,允许不获取所有权即可访问值。
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 传入引用
println!("'{}' 的长度是 {}", s, len); // s 仍然有效
}
fn calculate_length(s: &String) -> usize {
s.len()
}
规则:
- 同一时刻,要么有多个不可变引用(
&T),要么有一个可变引用(&mut T)。 - 引用必须始终有效(编译器检查生命周期)。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不可变引用
let r2 = &s; // 可以有多个
// let r3 = &mut s; // 编译错误:不能同时有可变和不可变引用
println!("{} {}", r1, r2);
}
fn main() {
let mut s = String::from("hello");
let r1 = &mut s; // 可变引用
// let r2 = &mut s; // 编译错误:不能同时有多个可变引用
r1.push_str(", world"); // 通过可变引用修改
println!("{}", r1);
}
5. 切片 (Slice)
切片是集合的局部引用,没有所有权。
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
// 切片是 &str 类型
let full: &str = &s[..]; // 全部
}
六、综合示例:与 Solidity 的对比
以下是一个简单的 Rust 函数,模拟 Solidity 中的余额映射:
use std::collections::HashMap;
struct Bank {
balances: HashMap<String, u64>,
}
impl Bank {
// 存款:修改余额
fn deposit(&mut self, account: String, amount: u64) {
let balance = self.balances.entry(account).or_insert(0);
*balance += amount;
}
// 取款:检查余额并扣减
fn withdraw(&mut self, account: &str, amount: u64) -> Result<(), String> {
if let Some(balance) = self.balances.get_mut(account) {
if *balance >= amount {
*balance -= amount;
Ok(())
} else {
Err("余额不足".to_string())
}
} else {
Err("账户不存在".to_string())
}
}
// 查询余额
fn balance(&self, account: &str) -> u64 {
self.balances.get(account).copied().unwrap_or(0)
}
}
fn main() {
let mut bank = Bank {
balances: HashMap::new(),
};
bank.deposit("alice".to_string(), 100);
bank.deposit("alice".to_string(), 50);
match bank.withdraw("alice", 30) {
Ok(()) => println!("提款成功,余额: {}", bank.balance("alice")),
Err(e) => println!("提款失败: {}", e),
}
}
与 Solidity 的关键区别:
- Rust 使用
HashMap模拟映射,需要显式管理键值。 - Rust 通过
Result类型显式处理错误,而非revert。 - Rust 没有
address原生类型,使用String或Pubkey(Solana 中)。
七、总结与下一步
本文覆盖了 Rust 的基础语法和所有权系统,这些是理解后续高级特性(生命周期、Trait、闭包、并发)的前提。
关键要点回顾:
- 变量默认不可变,需
mut声明可变。 - 所有权规则决定了值何时被释放,移动 vs 克隆。
- 引用(
&)允许借用值而不转移所有权,但受严格规则约束。 - 切片(
&str)是常用的无所有权字符串引用。
下一步学习建议:
- 生命周期(Lifetimes):理解引用如何被约束以保持有效。
- Trait 与泛型:Rust 的接口与多态机制。
- 错误处理:
Result和Option的深度用法。 - 闭包与迭代器:函数式编程特性。
在下一节中,我们将深入 所有权、借用与生命周期,并结合 Solana 开发中的实际场景进行演练。