Rust learning 01

Solana 是目前最快的区块链网络,而以太坊已经逐渐走下坡路,而且考虑到 Solana 优秀的设计,以及 Rust 的执行速度的加持,在可以预见的相当一段时间内,它可能都是最流行的区块链网络,所以学习合约开发的话,我打算以 Solana 平台开始。

同时,Rust 是目前集抽象能力与性能为一体的最流行的编程语言,有了 Solana 的招牌效应,在未来即使有新的区块链出现,也很有可能会继续采用 Rust ,所以在进行合约开发钱最好先学习一下 Rust 的常规用法。

安装

按照官网使用 rustup 把所有组件安装好,使用默认安装方式,它一次性就把所用到的组件安装到了 $HOME/.cargo/bin 目录下,下面这三个是最主要的

  • rustup: 管理 rust 相关的工具链,比如升级等等
  • rustc: 编译器,相当于 gcc
  • cargo: 包管理器,相当于 npm 或者 poetry

Cargo

使用 rustc 编译单个文件还行,但如果文件多了,甚至还有一大堆第三方依赖的话,编译的时候就得传很多参数。所以 cargo 就是用来解决这个问题的,它定义了 rust 项目的一个标准目录结构,他可以完成管理依赖、编译、发布等工作。实际项目开发中基本都会用 cargo 来管理项目。

cargo 初始化项目时,需要选择这个项目的最终产物是一个可以单独运行的文件,还是一个供其他项目引入的依赖包,前者是 cargo new [--bin] ,后者是 cargo new --lib ,学习阶段线使用 --bin 的方式即可。

使用 cargo new hello-rust 得到了一个初始目录,其结构如下,和大多数比较流行的语言的目录差不多,一个源码目录,一个包的描述文件,安装依赖后生成一个 lock 文件。

src/           # 源码目录
  |- main.rs
Cargo.lock
Cargo.toml     # 包的信息

代码示例

官方提供了一个简单的猜数字游戏的代码,如下所示

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess = match guess.trim().parse::<u32>() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

对于每一个 Rust 程序,都会默认包含一些 Rust 的标准库里的东西在里面,这叫 prelude (预导入),如果你想要的东西没有被 prelude 的话,就需要显式地使用 use 将他们导入到程序中,use std::io 便引入了很多功能,包括读取用户输入的功能。

在上面的例子中

  1. println! 是一个宏(带了感叹号的都是宏)

  2. let secret_number = rand::thread_rng().gen_range(1..=100); 表示调用 rand crate 的 thread_rng 关联函数,创建一个 rand::Rng 实例,然后再调用这个实例的 gen_range 方法生成一个 1 到 100 之间(闭区间)的随机整数。

  3. let mut guess = String::new(); 定义一个 String 类型的变量

    • mut 意味着这个变量可修改,如果是常量的话就不要加 mut
    • :: 的表示 newString 类型的一个关联函数,关联函数就是某个类型所实现的函数,很多类型都有 new 函数,它通常表示用于创建该类型的一个实例
  4. io::stdin().read_line(&mut guess) 中表示在 call std::io 模块上的 stdin 函数

    • 如果没有手动 use 这个模块,也可以写 std::io::stdin(),有点像 python 的 from aaa import bbbimport aaa.bbb 的意思
    • 这个函数返回了一个 std::io::Stdin 实例
  5. .read_line(&mut guess) 调用了 std::io::Stdin 实例的 read_line 方法来获取用户的输入

    • read_line 就是把用户输入的任何内容追加到一个字符串上,所以它的参数是一个 string 类型
    • & 表示这个参数是一个引用,它可以使得你的程序在多个地方访问相同的数据,而不是把一个数据在内存中多次拷贝
    • 像变量一样,引用默认也是不可修改的,所以这里要用 &mut guess 而不是 &guess
  6. read_line 返回了一个 std::result::Result 类型的值,它是一个枚举 enumeration ,它可能有很多状态,它的每个状态被叫做 variant (有点像薛定谔的猫)。

    Result 的变体是 OkErr ,而 Result 类型的值本身也像其它类型一样,有一些方法,其中一个方法就说 expect 。当这个 Result 是一个 Err 时,expect 方法就会让程序崩溃并且显示你传给 expect 的参数。而如果 ResultOk ,那么 expect 就会返回 Okholding 的返回值给你拿来用。

  7. 这段代码和上面的 match 类似,guess.trim().parse::<u32>() 返回 Result 类型,match 和后面的花括号则是进行模式匹配然后选择是返回转换后的数值还是使用 continue 进入下一轮循环。

    let guess = match guess.trim().parse::<u32>() {
       Ok(num) => num,
       Err(_) => continue,
    };
  8. loop 是一个无限循环,两个 match 代码块中的 continuebreak 的逻辑都是针对这个循环的。