Rust 的独特语法解析

  • 0xE
  • 发布于 3天前
  • 阅读 303

本文为从 Solidity 或 JavaScript 背景转向 Rust 的开发者解析了 Rust 中常见的语法特性,包括所有权与引用(& 和 *)、mut 可变性、泛型(<T>)、unwrap() 和 ? 运算符,帮助理解其逻辑并适应 Rust 的编程范式。

对于来自 Solidity 或 JavaScript 背景的开发者,Rust 中的 &mut&lt;T>unwrap()? 等语法可能显得陌生甚至有些别扭。本章将逐一解析这些语法的含义及其背后的逻辑。

如果初次阅读时无法完全理解,不必担心。你可以随时返回本文复习这些概念。


所有权与引用(& 和 *)

可复制类型

要理解 &(引用)和 *(解引用),首先需要认识 Rust 中的“可复制类型”。所谓可复制类型,指的是内存占用固定且较小、复制开销低的数据类型,包括:

  • 整数(i32, u64 等)、无符号整数(u32, u64 等)和浮点数(f32, f64)
  • 布尔值(bool)
  • 字符(char)

这些类型之所以“可复制”,是因为它们的大小已知且较小,复制成本可忽略。

反之,像 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)。

方法 1:引用(&)

通过 &,我们可以让变量“借用”数据而不转移所有权:

pub fn main() {
    let s1 = String::from("abc");
    let s2 = &s1; // s2 借用 s1 的值,不拥有
    println!("{}", s1); // 正常:s1 仍拥有值
    println!("{}", s2); // 正常:s2 查看 s1 的值
}

关键点:& 表示只读引用(借用),允许其他变量访问数据而不改变其所有权。

方法 2:克隆(clone)

如果需要独立副本,可以使用 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

mut 可变性

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,便于修正。


泛型语法 <T>

泛型允许函数或结构体处理多种类型,而无需为每种类型单独实现。示例:

#[derive(Debug)]
struct MyValues&lt;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&lt;T, U> {
    foo: T,
    bar: U,
}

用途:在 Solana 中“存储”值时,如果要存储数字、字符串或其他内容,泛型就可以灵活存储不同类型的数据。


Option、枚举与解引用

考虑以下代码:

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 类型

Option 是一个枚举,用于处理可能为空的情况:

  • Some(T):包含值
  • None:无值

使用 unwrap() 提取值,但若遇到 None 会引发 panic:

assert!(*v.iter().max().unwrap() == 5); // * 解引用 &i32 为 i32

**解引用 ***

max() 返回引用(&i32),需用 解引用为 i32。对于可复制类型, 会自动复制值。

注意:* 对不可复制类型的使用更复杂,此处暂不深入。


Result 与 Option 的区别

  • Option:处理可能为空的值(Some 或 None)。
  • Result:处理可能出错的操作(Ok 或 Err)。

Result<T, E> 定义:

enum Result&lt;T, E> {
    Ok(T), // 成功,返回 T
    Err(E), // 失败,返回错误 E
}

? 运算符

? 是 Result 的快捷方式,仅用于返回 Result 的函数。若结果为 Err,立即返回错误;若为 Ok,提取值继续执行。

示例:

pub fn encode_and_decode(_ctx: Context&lt;Initialize>) -> Result&lt;()> {
    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&lt;Initialize>, encoded_data: Vec&lt;u8>) -> Result&lt;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

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,Web3 开发者。刨根问底探链上真相,品味坎坷悟 Web3 人生。