本文为从 Solidity 或 JavaScript 背景转向 Rust 的开发者解析了 Rust 中常见的语法特性,包括所有权与引用(& 和 *)、mut 可变性、泛型(<T>)、unwrap() 和 ? 运算符,帮助理解其逻辑并适应 Rust 的编程范式。
对于来自 Solidity 或 JavaScript 背景的开发者,Rust 中的 &
、mut
、<T>
、unwrap()
和 ?
等语法可能显得陌生甚至有些别扭。本章将逐一解析这些语法的含义及其背后的逻辑。
如果初次阅读时无法完全理解,不必担心。你可以随时返回本文复习这些概念。
要理解 &(引用)和 *(解引用),首先需要认识 Rust 中的“可复制类型”。所谓可复制类型,指的是内存占用固定且较小、复制开销低的数据类型,包括:
这些类型之所以“可复制”,是因为它们的大小已知且较小,复制成本可忽略。
反之,像 Vec(向量)、String(字符串)和结构体(struct)这样的大小不固定或可能很大的类型,则属于“不可复制类型”。
来看这段 Rust 代码:
pub fn main() {
let a: u32 = 2;
let b: u32 = 3;
println!("{}", add(a, b)); // a 和 b 被复制到 add 函数
let s1 = String::from("hello");
let s2 = String::from(" world");
println!("{}", concat(s1, s2)); // 无法编译
}
// add 和 concat 的实现此处省略
对于 a 和 b,Rust 会自动复制这两个 32 位整数到 add 函数,总共仅 64 位开销。但对于字符串 s1 和 s2,如果也直接复制,可能涉及大量数据(例如 1GB 的字符串),性能会显著下降。
Rust 要求开发者明确指定如何处理不可复制类型,而不是像动态语言那样隐式复制。这正是所有权机制的核心。
对于不可复制类型(如字符串、向量、结构体),变量一旦被赋值,就“拥有”该值的所有权。所有权转移后,原变量无法再访问该值。看下面例子:
let s1 = String::from("abc");
let s2 = s1; // s2 接管 "abc" 的所有权,s1 失效
// println!("{}", s1); // 编译失败:s1 已失去所有权
println!("{}", s2); // 正常运行:s2 拥有 "abc"
修复方法有两种:使用引用(&)或克隆(clone)。
通过 &,我们可以让变量“借用”数据而不转移所有权:
pub fn main() {
let s1 = String::from("abc");
let s2 = &s1; // s2 借用 s1 的值,不拥有
println!("{}", s1); // 正常:s1 仍拥有值
println!("{}", s2); // 正常:s2 查看 s1 的值
}
关键点:& 表示只读引用(借用),允许其他变量访问数据而不改变其所有权。
如果需要独立副本,可以使用 clone:
fn main() {
let mut message = String::from("hello");
let mut y = message.clone(); // y 获得 message 的副本
message.push_str(" world"); // 修改 message
println!("{:?}", message); // "hello world"
println!("{:?}", y); // "hello"
}
相比引用,clone 会复制数据,开销较大,但确保变量独立。
对于可复制类型(如整数),所有权问题不存在,Rust 会自动复制:
let s1 = 3;
let s2 = s1; // s1 被复制,s2 拥有独立副本
println!("{}", s1); // 3
println!("{}", s2); // 3
Rust 中变量默认不可变,需用 mut 关键字显式声明可变性。以下代码无法编译:
pub fn main() {
let counter = 0;
counter = counter + 1; // 编译失败:counter 不可变
println!("{}", counter);
}
加上 mut 后正常运行:
pub fn main() {
let mut counter = 0;
counter = counter + 1;
println!("{}", counter); // 1
}
提示:编译器会明确提示缺少 mut,便于修正。
泛型允许函数或结构体处理多种类型,而无需为每种类型单独实现。示例:
#[derive(Debug)]
struct MyValues<T> {
foo: T,
}
pub fn main() {
let first = MyValues { foo: 1 }; // foo 是 i32
let second = MyValues { foo: false }; // foo 是 bool
println!("{:?}", first); // MyValues { foo: 1 }
println!("{:?}", second); // MyValues { foo: false }
}
多参数泛型:
struct MyValues<T, U> {
foo: T,
bar: U,
}
用途:在 Solana 中“存储”值时,如果要存储数字、字符串或其他内容,泛型就可以灵活存储不同类型的数据。
考虑以下代码:
fn main() {
let v = Vec::from([1, 2, 3, 4, 5]);
assert!(v.iter().max().unwrap() == 5); // 获取最大值
}
但如果直接比较 v.iter().max() == 5,会失败,因为 max() 返回 Option<&i32> 而非整数。
Option 是一个枚举,用于处理可能为空的情况:
使用 unwrap() 提取值,但若遇到 None 会引发 panic:
assert!(*v.iter().max().unwrap() == 5); // * 解引用 &i32 为 i32
max() 返回引用(&i32),需用 解引用为 i32。对于可复制类型, 会自动复制值。
注意:* 对不可复制类型的使用更复杂,此处暂不深入。
Result<T, E> 定义:
enum Result<T, E> {
Ok(T), // 成功,返回 T
Err(E), // 失败,返回错误 E
}
? 是 Result 的快捷方式,仅用于返回 Result 的函数。若结果为 Err,立即返回错误;若为 Ok,提取值继续执行。
示例:
pub fn encode_and_decode(_ctx: Context<Initialize>) -> Result<()> {
let init_person = Person {
name: "Alice".to_string(),
age: 27,
};
let encoded_data = init_person.try_to_vec().unwrap(); // 编码为字节向量
let data = decode(_ctx, encoded_data)?; // 解码并提取
msg!("姓名: {:?}, 年龄: {:?}", data.name, data.age);
Ok(())
}
pub fn decode(_accounts: Context<Initialize>, encoded_data: Vec<u8>) -> Result<Person> {
let decoded_data = Person::try_from_slice(&encoded_data).unwrap(); // 解码
Ok(decoded_data)
}
try_to_vec() 方法将结构体编码为字节向量,返回一个 Result<T, E> 枚举,其中 T 为字节向量类型。unwrap() 方法则用于从 Ok(T) 变体中提取字节向量值。
对比:unwrap() 适用于 Option 和 Result,但可能 panic;? 更安全,仅限 Result。
【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!