分类 Solana 下的文章

Solana基础 - Rust 和 Solana 中的可见性与“继承”


本文详细讲解了如何将Solidity中的函数可见性和合约继承概念化到Solana中,并提供了Rust语言中实现这些概念的代码示例。

今天我们将学习如何在 Solana 中构思 Solidity 的函数可见性和合约继承。Solidity 中有四种函数可见性级别,它们是:

  • public – 从合约内部和外部均可访问。
  • external – 从合约外部仅可访问。
  • internal – 从合约内部和继承合约中可访问。
  • private – 仅在合约内部可访问。

让我们在 Solana 中实现相同的功能,好吗?

公共函数

自第一天起到现在我们定义的所有函数都是公共函数:

pub fn my_public_function(ctx: Context<Initialize>) -> Result<()> {
    // 函数逻辑...

    Ok(())
}

在函数声明前添加 pub 关键字,从而使函数变为公共。

你不能移除标记为 #[program] 的模块 (mod) 内部的函数的 pub 关键字。这将导致无法编译。

不用太担心 external 和 public 之间的区别

在 Solana 程序中,调用自己公共函数通常是不方便的。如果 Solana 程序中存在 pub 函数,从实际角度来看,你可以将其视作为在 Solidity 中的外部函数。

如果你想在同一个 Solana 程序中调用公共函数,包裹这个公共函数并调用该内部实现函数要更容易。

私有和内部函数

虽然你不能在使用 #[program] 宏的模块中声明没有 pub 的函数,但你可以在文件内部声明函数。考虑以下代码:

use anchor_lang::prelude::*;

declare_id!("F26bvRaY1ut3TD1NhrXMsKHpssxF2PAUQ7SjZtnrLkaM");

#[program]
pub mod func_test {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        // -------- 调用一个“私有”函数 --------
        let u = get_a_num();
        msg!("{}", u);
        Ok(())
    }
}

// ------- 我们在此声明了一个非 pub 函数 -------
fn get_a_num() -> u64 {
    2
}

#[derive(Accounts)]
pub struct Initialize {}

这将按预期运行并记录。

如果你想构建简单的 Solana 程序,这些是你真正需要了解的有关公共和内部函数的所有内容。但如果你希望比仅仅在文件中声明一堆函数更好地组织代码,你可以继续深入。Rust,因此 Solana,没有像 Solidity 那样的“类”,因为 Rust 不是面向对象的。因此,“私有”和“内部”之间的区别在 Rust 中没有直接类比。

Rust 使用模块来组织代码。模块内外函数的可见性在Rust 文档的可见性和隐私部分中有很好的讨论,但我们将添加自己针对 Solana 的见解。

内部函数

通过在程序模块内定义函数并确保其在其自己的模块及导入或使用它的其他模块内可访问来实现。这是怎么做的:

use anchor_lang::prelude::*;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod func_visibility {
    use super::*;

    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        // 从其父模块内部调用 internal_function
        some_internal_function::internal_function();

        Ok(())
    }

    pub mod some_internal_function {
        pub fn internal_function() {
            // 内部函数逻辑...
        }
    }
}

mod do_something {
    // 导入 func_visibility 模块
    use crate::func_visibility;

    pub fn some_func_here() {
        // 从外部其父模块调用 internal_function
        func_visibility::some_internal_function::internal_function();

        // 做其他事情...
    }
}

#[derive(Accounts)]
pub struct Initialize {}

构建程序后,如果导航到 ./target/idl/func_visibility.json 文件,你将看到定义在 some_internal_function 模块中的函数没有被包含在构建程序中。这表明 some_internal_function 函数是内部的,仅能在程序内部及任何导入或使用它的程序中访问。

从上面的例子中,我们能够从其“父”模块(func_visibility)内部访问 internal_function 函数,也能从 func_visibility 模块外的单独模块中访问(do_something)。

私有函数

在特定模块内定义函数并确保它们不会暴露在该作用域之外就是实现私有可见性的一种方式:

use anchor_lang::prelude::*;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod func_visibility {
    use super::*;

    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        // 从其父模块内部调用 private_function
        some_function_function::private_function();

        Ok(())
    }

    pub mod some_function_function {
        pub(in crate::func_visibility) fn private_function() {
            // 私有函数逻辑...
        }
    }
}

#[derive(Accounts)]
pub struct Initialize {}

pub(in crate::func_visibility) 关键字表明 private_function 函数仅在 func_visibility 模块中可见。

我们能够成功在初始化函数中调用 private_function,因为初始化函数处于 func_visibility 模块内。让我们尝试从模块外部调用 private_function

use anchor_lang::prelude::*;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod func_visibility {
    use super::*;

    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        // 从其父模块内部调用 private_function
        some_private_function::private_function();

        Ok(())
    }

    pub mod some_private_function {
        pub(in crate::func_visibility) fn private_function() {
            // 私有函数逻辑...
        }
    }
}

mod do_something {
    // 导入 func_visibility 模块
    use crate::func_visibility;

    pub fn some_func_here() {
        // 从外部其父模块调用 private_function
        func_visibility::some_private_function::private_function()

        // 做一些事情...
    }
}

#[derive(Accounts)]
pub struct Initialize {}

构建程序。发生了什么?我们得到了一个错误:

error[E0624]: associated functionprivate_function is private

这表明 private_function 不是公开可访问的,无法从其可见的模块外部调用。查看Rust 文档中的可见性和隐私以了解更多关于 pub 可见性关键字的信息。

合约继承

将 Solidity 合约继承直接翻译为 Solana 是不可能的,因为 Rust 没有类。

然而,Rust 中的解决方法涉及创建定义特定功能的单独模块,然后在我们的主程序中使用这些模块,从而实现类似于 Solidity 的合约继承的效果。

从另一个文件获取模块

随着程序变大,我们通常不希望将所有内容放入一个文件中。以下是如何将逻辑组织到多个文件中的示例。

src 文件夹下创建一个叫 calculate.rs 的文件,并将提供的代码复制到其中。

pub fn add(x: u64, y: u64) -> u64 {
    // 返回 x 和 y 的和
    x + y
}

这个 add 函数返回 xy 的和。

以及将其放入 lib.rs 中。

use anchor_lang::prelude::*;

// 导入 `calculate` 模块或库
pub mod calculate;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod func_visibility {
    use super::*;

    pub fn add_two_numbers(_ctx: Context<Initialize>, x: u64, y: u64) -> Result<()> {
        // 调用 calculate.rs 中的 `add` 函数
        let result = calculate::add(x, y);

        msg!("{} + {} = {}", x, y, result);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

在上面的程序中,我们导入了之前创建的 calculate 模块,并声明了一个名为 add_two_numbers 的函数,该函数添加两个数字并记录结果。add_two_numbers 函数调用 calculate 模块中的 add 函数,将 xy 作为参数传递,然后将返回值存储在 result 变量中。msg! 宏记录了两个相加的数字和结果。

模块不必是单独的文件

以下示例在 lib.rs 中声明一个模块,而不是在 calculate.rs 中。

use anchor_lang::prelude::*;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod func_visibility {
    use super::*;

    pub fn add_two_numbers(_ctx: Context<Initialize>, x: u64, y: u64) -> Result<()> {
        // 调用 calculate.rs 中的 `add` 函数
        let result = calculate::add(x, y);

        msg!("{} + {} = {}", x, y, result);

        Ok(())
    }
}

mod calculate {
    pub fn add(x: u64, y: u64) -> u64 {
        // 返回 x 和 y 的总和
        x + y
    }
}

#[derive(Accounts)]
pub struct Initialize {}

这个程序与前一个示例的功能相同,唯一的区别是 add 函数出现在 lib.rs 文件和计算模块内。此外,向函数添加 pub 关键字非常重要,因为这使函数对外公开可访问。以下代码将无法编译:

use anchor_lang::prelude::*;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod func_visibility {
    use super::*;

    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        // 调用私有类函数
        let result2 = do_something::some_func_here();

        msg!("The result is {}", result2);

        Ok(())
    }
}

mod do_something {
    // 私有类函数。它存在于代码中,但并不是每个人都可以调用它
    fn some_func_here() -> u64 {
        // 做一些事情...

        return 20;
    }
}

#[derive(Accounts)]
pub struct Initialize {}

总结

在 Solidity 中,我们非常重视函数可见性,因为它非常关键。以下是我们在 Rust 中的思考方式:

  • 公共/外部函数:这些是可以在程序内部和外部访问的函数。在 Solana 中,所有声明的函数默认都是公共的。#[program] 块中的所有内容必须声明为 pub。
  • 内部函数:这些是可在程序内部及继承该程序的程序中访问的函数。在嵌套的 pub mod 块内的函数不会包含在构建程序中,但仍然可以在父模块内或外访问。
  • 私有函数:这些是不能公开访问的函数,无法从其所在模块外部调用。在 Rust/Solana 中实现私有可见性的方法是定义一个位于特定模块内的函数,使用 pub(in crate::<module>) 关键字,使得函数仅在定义它的模块内可见。

Solidity 通过类实现合约继承,而 Rust,Solana 使用的语言,并没有这个特性。尽管如此,你仍然可以使用 Rust 模块来组织代码。

转载:https://learnblockchain.cn/article/11424


Solana基础 - Rust 结构体与属性式和自定义派生宏


文章详细介绍了 Rust 语言中的 attribute-like 和 custom derive 宏的使用方法,通过具体代码示例展示了如何通过宏在编译时修改结构体,并解释了宏的工作原理和实现方式。
Rust 中的类似属性和自定义派生宏用于在编译时以某种方式修改一段 Rust 代码,通常是为了增加功能。

要理解 Rust 中的类似属性和自定义派生宏,我们首先需要简要介绍 Rust 中的实现结构。

结构的实现:impl

以下结构应该是相当容易理解的。当我们创建对特定结构进行操作的函数时,事情就变得有趣了。我们这样做的方法是使用 impl

struct Person {
    name: String,
    age: u8,
}

关联函数和方法是在 impl 块中为结构实现的。

关联函数可以与 Solidity 中为与结构交互创建库的场景进行比较。当我们定义 using lib for MyStruct 时,它允许我们使用语法 myStruct.associatedFunction()。这使得该函数可以通过 Self 关键字访问 myStruct

我们建议使用 Rust Playground,但对于更复杂的示例,你可能需要设置你的 IDE。

让我们看一个下面的示例:

struct Person {
    age: u8,
    name: String,
}

// 为 `Person` 结构实现方法 `new()`,允许初始化一个 `Person` 实例
impl Person {
    // 使用提供的 `name` 和 `age` 创建一个新的 `Person`
    fn new(name: String, age: u8) -> Self {
        Person { name, age }
    }

    fn can_drink(&self) -> bool {
        if self.age >= 21 as u8 {
            return true;
        }
        return false;
    }

    fn age_in_one_year(&self) -> u8 {
        return &self.age + 1;
    }
} 

fn main() {
    // 用法:创建一个带有名字和年龄的新 `Person` 实例
    let person = Person::new(String::from("Jesserc"), 19);

    // 使用一些实现函数
    println!("{:?}", person.can_drink()); // false
    println!("{:?}", person.age_in_one_year()); // 20
    println!("{:?}", person.name);
}

用法:

// 用法:创建一个带有名字和年龄的新 `Person` 实例
let person = Person::new(String::from("Jesserc"), 19);

// 使用一些实现函数
person.can_drink(); // false
person.age_in_one_year(); // 20

Rust Traits

Rust traits 是在不同的 impl 之间实现共享行为的一种方式。可以将它们视为 Solidity 中的接口或抽象合约——任何使用该接口的合约必须实现某些函数。

例如,假设我们有一个需要定义汽车和船的结构的场景。我们想附加一个方法,允许我们以每小时公里数检索它们的速度。在 Rust 中,我们可以通过使用单个 trait 并在两个结构之间共享该方法来实现这个目标。

如下所示:

// traits 用 `trait` 关键字定义,后跟其名称
trait Speed {
    fn get_speed_kph(&self) -> f64;
}

// 汽车结构
struct Car {
    speed_mph: f64,
}

// 船结构
struct Boat {
    speed_knots: f64,
}

// 使用 `impl` 关键字为类型实现 traits,如下所示
impl Speed for Car {
    fn get_speed_kph(&self) -> f64 {
        // 将英里每小时转换为公里每小时
        self.speed_mph * 1.60934
    }
}

// 我们也为 `Boat` 实现 `Speed` trait
impl Speed for Boat {
    fn get_speed_kph(&self) -> f64 {
        // 将节转换为公里每小时
        self.speed_knots * 1.852
    }
}

fn main() {
    // 初始化一个 `Car` 和 `Boat` 类型
    let car = Car { speed_mph: 60.0 };
    let boat = Boat { speed_knots: 30.0 };

    // 获取并打印以公里每小时为单位的速度
    let car_speed_kph = car.get_speed_kph();
    let boat_speed_kph = boat.get_speed_kph();

    println!("Car Speed: {} km/h", car_speed_kph); // 96.5604 km/h
    println!("Boat Speed: {} km/h", boat_speed_kph); // 55.56 km/h
}

宏如何修改结构

在我们关于类似函数的宏的教程中,我们看到了宏如何扩展代码,比如 println!(...)msg!(...) 在大型 Rust 代码中的用法。在 Solana 的上下文中,我们关心的另一种宏是 attribute-like 宏和 derive 宏。我们可以在 anchor 创建的启动程序中看到这三种宏(函数式、类似属性和派生):

Rust attribute and custom-derive macros

为了直观了解类似属性的宏做了什么,我们将创建两个宏:一个将字段添加到结构中,另一个将其删除。

示例 1:类似属性的宏,插入字段

为了更好地理解 Rust 的属性和宏如何工作,我们将创建一个 attribute-like macro,其功能为:

  1. 处理一个不包含 foobar 字段的结构,字段类型为 i32
  2. 将这些字段插入到结构中
  3. 创建一个包含称为 double_foo 的函数的 impl,该函数返回 foo 字段所持的整数值的两倍。

设置

首先我们创建一个新的 Rust 项目:

cargo new macro-demo --lib 
cd macro-demo
touch src/main.rs

在 Cargo.toml 文件中添加以下内容:

[lib]
proc-macro = true

[dependencies]
syn = {version="1.0.57",features=["full","fold"]}
quote = "1.0.8"

创建主程序

将以下代码粘贴到 src/main.rs 文件中。请确保阅读注释:

// src/main.rs
// 导入 macro_demo crate 并用 `*` 通配符引入所有项
// (实际上是此 crate 中的所有内容,包括我们在 `src/lib.rs` 中的宏)
use macro_demo::*;

// 将我们在 `src/lib.rs` 中创建的 `foo_bar_attribute` 过程属性宏应用于 `struct MyStruct`
// 该过程宏将生成一个带有指定字段和方法的新结构定义
#[foo_bar_attribute]
struct MyStruct {
    baz: i32,
}

fn main() {
    // 使用 `default()` 方法创建 `MyStruct` 的新实例
    // 此方法由宏生成的 `Default` trait 实现提供
    let demo = MyStruct::default();

    // 将 `demo` 的内容打印到控制台
    // 宏生成的 `Debug` trait 实现允许使用 `println!` 进行格式化输出
    println!("struct is {:?}", demo);

    // 在 `demo` 上调用 `double_foo()` 方法
    // 此方法由宏生成,返回 `foo` 字段值的两倍
    let double_foo = demo.double_foo();

    // 将调用 `double_foo` 的结果打印到控制台
    println!("double foo: {}", double_foo);
}

一些观察:

  • 结构 MyStruct 中没有字段 foo
  • double_foo 函数没有在上面的代码中定义,假设它存在。

现在让我们创建将修改 MyStruct 的类似属性的宏。

将 src/lib.rs 中的代码替换为以下代码(请确保阅读注释):

// src/lib.rs
// 导入必要的外部库
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemStruct};

// 声明一个使用 `proc_macro_attribute` 指令的过程属性宏
// 这使得宏可以作为属性使用
#[proc_macro_attribute]
// `foo_bar_attribute` 函数接受两个参数:
// _metadata:提供给宏的参数(如果有)
// _input:宏所应用的 TokenStream
pub fn foo_bar_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
    // 将输入 TokenStream 解析为表示结构的 AST 节点
    let input = parse_macro_input!(_input as ItemStruct);
    let struct_name = &input.ident; // 获取结构的名称

    // 使用 quote! 宏构建输出 TokenStream
    // quote! 宏允许将 Rust 代码写成字符串的方式,但可以插入值
    TokenStream::from(quote! {
        // 为 #struct_name 派生 Debug trait,以启用使用 `println()` 的格式化输出
        #[derive(Debug)]
        // 定义具有两个字段:foo 和 bar 的新结构 #struct_name
        struct #struct_name {
            foo: i32,
            bar: i32,
        }

        // 为 #struct_name 实现 Default trait
        // 这提供了一个 default() 方法,用于创建 #struct_name 的新实例
        impl Default for #struct_name {
            // 默认方法返回一个新的 #struct_name 实例,其中 foo 设置为 10,bar 设置为 20
            fn default() -> Self {
                #struct_name { foo: 10, bar: 20}
            }
        }

        impl #struct_name {
            // 为 #struct_name 定义一个方法 double_foo
            // 此方法返回 foo 的双倍值
            fn double_foo(&self) -> i32 {
                self.foo * 2
            }
        }
    })
}

现在,为了测试我们的宏,我们使用 cargo run src/main.rs 运行代码。

我们会得到以下输出:

struct is MyStruct { foo: 10, bar: 20 }
double foo: 20

示例 2:类似属性的宏,删除字段

关于类似属性的宏,可以认为它们在修改结构时具有无限的能力。让我们重复上面的示例,但这次类似属性的宏将删除结构中的所有字段。

将 src/lib.rs 替换为以下内容:

// src/lib.rs
// 导入必要的外部库
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemStruct};

#[proc_macro_attribute]
pub fn destroy_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(_input as ItemStruct);
    let struct_name = &input.ident; // 获取结构的名称

    TokenStream::from(quote! {
        // 返回一个具有相同名称的空结构
        #[derive(Debug)]
        struct #struct_name {
        }
    })
}

将 src/main.rs 替换为以下内容:

use macro_demo::*;

#[destroy_attribute]
struct MyStruct {
    baz: i32,
    qux: i32,
}

fn main() {
    let demo = MyStruct { baz: 3, qux: 4 };

    println!("struct is {:?}", demo);
}

当你尝试使用 cargo run src/main.rs 编译时,会收到以下错误消息:

Error: struct MyStruct has no field named baz

这可能看起来很奇怪,因为结构显然有这些字段。然而,类似属性的宏删除了它们!

#[derive(…)]

#[derive(…)] 宏的功能远没有类似属性宏强大。对于我们的目的而言,派生宏 增强 了结构,而不是改变它。(这不是一个精确的定义,但现在足够了)。

派生宏可以,除了其他外,向结构附加一个 impl

例如,如果我们尝试做以下操作:

struct Foo {
    bar: i32,
}

pub fn main() {
    let foo = Foo { bar: 3 };
    println!("{:?}", foo);
}

编译时将不会通过,因为结构不可“打印”。

为了使它们可打印,需要有一个 impl,其中有一个函数 fmt,返回结构的字符串表示。

如果我们这样做:

#[derive(Debug)]
struct Foo {
    bar: i32,
}

pub fn main() {
    let foo = Foo { bar: 3 };
    println!("{:?}", foo);
}

我们期望它打印:

Foo { bar: 3 }

派生属性以某种方式“增强”了 Foo,使得 println! 可以为其创建字符串表示。

总结

impl 是一组对结构进行操作的函数。它们通过使用与结构相同的名称“附加”到结构上。trait 强制工具的 impl 实现某些函数。在我们的示例中,我们通过语法 impl Speed for Car 将 trait Speed 附加到 impl Car。

类似属性的宏接收一个结构,可以完全重写它。

派生宏增强了结构,使其具有附加功能。

宏允许 Anchor 隐藏复杂性

让我们再看看 Anchor 在 anchor init 时创建的程序:

Rust attribute and custom-derive macros

属性 #[program] 在幕后修改模块。例如,它实现了一个路由器,自动将传入的区块链指令定向到模块内的适当函数。

结构 Initialize {} 被增强了额外的功能,以便在 Solana 框架中使用。

总结

宏是一个非常大的主题。我们在这里的目的是让你了解当你看到 #[program]#[derive(Accounts)] 时发生了什么。不要因它感到陌生而沮丧。你不需要能够编写宏才能编写 Solana 程序

不过,了解它们的作用希望能让你看到的程序变得不那么神秘。
转载:https://learnblockchain.cn/article/11373


Solana基础 - Rust 函数式过程宏


本教程解释了函数与函数式宏之间的区别。例如,为什么 msg! 后面有一个感叹号?本教程将解释这种语法。作为一种强类型语言,Rust 不能接受函数的任意数量的参数。例如,Python 的 print 函数可以接受任意数量的参数:

print(1)
print(1, 2)
print(1, 2, 3)

! 表示“函数”是一个函数式宏。Rust 的函数式宏通过 ! 符号来标识,例如 Solana 中的 println!(...)msg!(...)。在 Rust 中,打印内容的常规函数(非函数式宏)是 std::io::stdout().write,它只接受一个字节字符串作为参数。如果你想运行以下代码,如果你不想设置开发环境,Rust Playground 是一个方便的工具。我们使用以下示例(取自这里):

use std::io::Write;

fn main() {
    std::io::stdout().write(b"Hello, world!
").unwrap();
}

请注意,write 是一个函数,而不是宏,因为它没有 !。如果你尝试像上面在 Python 中那样做,代码将无法编译,因为 write 只接受一个参数:

// 这段代码无法编译
use std::io::Write;

fn main() {
    std::io::stdout().write(b"1
").unwrap();
    std::io::stdout().write(b"1", b"2
").unwrap();
    std::io::stdout().write(b"1", b"2", b"3
").unwrap();
}

因此,如果你想打印任意数量的参数,你需要为每种参数数量编写一个自定义的 print 函数来处理每种情况 —— 这是非常低效的!以下是这样代码的样子(非常不推荐!):

use std::io::Write;

// 打印一个参数
fn print1(arg1: &[u8]) -> () {
    std::io::stdout().write(arg1).unwrap();
}

// 打印两个参数
fn print2(arg1: &[u8], arg2: &[u8]) -> () {
    let combined_vec = [arg1, b" ", arg2].concat();
    let combined_slice = combined_vec.as_slice();
    std::io::stdout().write(combined_slice).unwrap();
}

// 打印三个参数
fn print3(arg1: &[u8], arg2: &[u8], arg3: &[u8]) -> () {
    let combined_vec = [arg1, b" ", arg2, b" ", arg3].concat();
    let combined_slice = combined_vec.as_slice();
    std::io::stdout().write(combined_slice).unwrap();
}

fn main() {
    print1(b"1
");
    print2(b"1", b"2
");
    print3(b"1", b"2", b"3
");
}

如果我们观察 print1print2print3 函数中的模式,它只是将参数插入到向量中并在它们之间添加一个空格,然后将向量转换回字节字符串(准确说是字节切片)。如果我们能像 println! 这样编写一段代码,并自动将其扩展为一个 print 函数,接受我们所需的任意数量的参数,那岂不是很好?这就是 Rust 宏的作用。Rust 宏将 Rust 代码作为输入,并以编程方式将其扩展为更多的 Rust 代码。 这帮助我们避免了必须为代码所需的每种 print 语句编写 print 函数的无聊工作。

宏的扩展

要查看 Rust 编译器如何扩展 println! 宏的示例,请查看 cargo expand github 仓库。结果相当冗长,所以我们不在这里展示。

将宏视为黑盒子是可以的

当宏由库提供时,它们非常方便,但手动编写宏非常繁琐,因为它需要字面解析 Rust 代码。

Rust 中不同类型的宏

我们给出的 println! 示例是一个函数式宏。Rust 还有其他类型的宏,但我们关心的另外两种是自定义派生宏属性式宏。让我们看看 anchor 创建的新程序:不同类型的宏我们将在接下来的教程中解释它们的工作原理。

转载:https://learnblockchain.cn/article/11374


Solana基础 - Rust不寻常的语法


本文详尽地介绍了 Rust 的所有权、借用及其相关概念,包括 Rust 的复制类型、可变性、泛型、Option 和 Result 等内容。通过示例代码,深入解释了 Rust 的独特语法和其内在逻辑,尤其适合有 Solidity 或 JavaScript 背景的开发者。此外,文章结构清晰,包含代码示例和必要的注释,帮助读者更好地理解 Rust 编程语言。

来自Solidity或JavaScript背景的读者可能会发现Rust中&mut<_>unwrap()?的用法和语法显得奇怪(甚至丑陋)。本章将解释这些术语的含义。

如果一开始没有完全理解,不用担心。如果你忘记了语法定义,随时可以回来再读这个教程。

所有权与借用(引用 & 解引用运算符 *):

Rust的复制类型

要理解&*,我们首先需要了解Rust中的“复制类型”。复制类型是一种数据类型,其值的复制开销微不足道。以下值是复制类型:

  • 整数、无符号整数和浮点数
  • 布尔值
  • 字符

它们被称为“复制类型”,是因为它们的大小固定且较小。

另一方面,向量、字符串和结构体可以任意大,因此它们不是复制类型。

为什么Rust区分复制类型和非复制类型

考虑以下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");

    // 如果s1和s2被复制,可能会发生巨大的数据传输
    // 如果字符串非常长
    println!("{}", concat(s1, s2));
}

// 为了简洁,add()和concat()的实现未显示
// 这段代码无法编译

在第一段代码中,ab相加,只需要从变量复制64位数据(32位 * 2变量)。

然而,在字符串的情况下,我们并不总是知道要复制多少数据。如果字符串长达1GB,程序会显著变慢。

Rust希望我们明确如何处理大型数据。它不会像动态语言那样在后台自动复制。

因此,当我们做一些简单如_将字符串赋值给新变量_的操作时,Rust会做一些许多人认为意外的事情,正如我们在下一节所见。

Rust中的所有权

对于非复制类型(字符串、向量、结构体等),一旦值被赋给变量,该变量便“拥有”它。所有权的影响将在随后展示。

以下代码无法编译。解释在注释中:

// 非复制数据类型(字符串)上更改所有权的示例
let s1 = String::from("abc");

// s2成为`String::from("abc")`的所有者
let s2 = s1;

// 以下行编译失败,因为s1无法再访问其字符串值。
println!("{}", s1);

// 这一行编译成功,因为s2现在拥有字符串值。
println!("{}", s2);

要修复上面的代码,我们有两个选择:使用&运算符或克隆s1

选项 1:给s2一个s1的视图

在下面的代码中,注意重要的&前缀s1

pub fn main() {
    let s1 = String::from("abc");

    let s2 = &s1; // s2现在可以查看`String::from("abc")`但不能拥有它

    println!("{}", s1); // 这段代码编译,s1仍然保持其原始字符串值。
    println!("{}", s2); // 这段代码编译,s2持有对s1中的字符串值的引用。
}

如果我们希望另一个变量“查看”值(即获得只读访问权限),我们使用&运算符。

要给另一个变量或函数查看一个拥有的变量,我们在前面加上&

可以将&视为非复制类型的“仅视图”模式。我们所称的“仅视图”的技术术语是借用

选项 2:克隆s1

要理解我们如何可以克隆一个值,考虑以下示例:

fn main() {
    let mut message = String::from("hello");
    println!("{}", message);
    message = message + " world";
    println!("{}", message);
}

上面的代码将打印“hello”,然后打印“hello world”。

但是,如果我们添加一个查看message的变量y,代码将无法再编译:

// 无法编译
fn main() {
    let mut message = String::from("hello");
    println!("{}", message);
    let mut y = &message; // y正在查看message
    message = message + " world";
    println!("{}", message);
    println!("{}", y); // y应该是"hello"还是"hello world"?
}

Rust不接受上面的代码,因为变量message在被查看时不能重新赋值。

如果我们希望y能够在不干扰message的前提下复制message的值,我们可以选择克隆它:

fn main() {
    let mut message = String::from("hello");
    println!("{:?}", message);
    let mut y = message.clone(); // 这里改为克隆
    message = message + " world";
    println!("{:?}", message);
    println!("{:?}", y);
}

上面的代码将打印:

hello
hello world
hello

所有权只在非复制类型时才是问题

如果我们用一个复制类型(如整数)替换我们的字符串(一个非复制类型),我们就不会遇到以上任何问题。Rust会愉快地复制复制类型,因为开销可以忽略不计。

let s1 = 3;

let s2 = s1;

println!("{}", s1);
println!("{}", s2);

mut关键字

默认情况下,Rust中的所有变量都是不可变的,除非指定mut关键字。

以下代码将无法编译:

pub fn main() {
    let counter = 0;
    counter = counter + 1;

    println!("{}", counter);
}

如果我们尝试编译上面的代码,将会出现以下错误:

错误:不能赋值两次

幸运的是,如果你忘记包括mut关键字,编译器通常足够聪明,可以清楚地指出错误。以下代码添加了mut关键字,使得代码能够编译:

pub fn main() {
    let mut counter = 0;
    counter = counter + 1;

    println!("{}", counter);
}

Rust中的泛型:< >语法

考虑一个接受任意类型值并返回包含该值的字段foo的结构体的函数。与其为每种可能的类型编写一堆函数,我们可以使用一个泛型

下面的示例结构体可以是i32bool

// 推导调试trait,以便我们可以将结构体打印到控制台
#[derive(Debug)]
struct MyValues<T> {
    foo: T,
}

pub fn main() {
    let first_struct: MyValues<i32> = MyValues { foo: 1 }; // foo的类型为i32
    let second_struct: MyValues<bool> = MyValues { foo: false }; // foo的类型为bool

    println!("{:?}", first_struct);
    println!("{:?}", second_struct);
}

这里有一个便利之处:当我们在Solana中“存储”值时,我们希望能够灵活地存储数字、字符串或其他任何东西。

如果我们的结构体有多个字段,参数化类型的语法如下:

struct MyValues<T, U> {
    foo: T,
    bar: U,
}

泛型在Rust中是一个非常大的主题,因此我们这里并没有给出完整的处理。不过,这对于理解大多数Solana程序来说已经足够。

选项、枚举和解引用 *

为了展示选项和枚举的重要性,我们来看以下示例:

fn main() {
    let v = Vec::from([1,2,3,4,5]);

    assert!(v.iter().max() == 5);
}

这段代码编译失败,并显示以下错误:

6 |     assert!(v.iter().max() == 5);
  |                               ^ 预期为`Option<&{integer}>`,找到整数

max()的输出不是一个整数,因为存在v可能为空的边缘情况。

Rust选项

为了处理这个边缘情况,Rust返回一个选项。选项是一个枚举,可以包含预期的值,或者指示“没有值”的特殊值。

要将选项转换为基础类型,我们使用unwrap()unwrap()会在我们收到“没有值”的情况下导致恐慌,因此我们只应在希望发生恐慌或我们确定不会得到空值的情况下使用它。

为了让代码按预期工作,我们可以这样做:

fn main() {
    let v = Vec::from([1,2,3,4,5]);

    assert!(v.iter().max().unwrap() == 5);
}

解引用 * 运算符

但它仍然无法工作!这次我们收到一个错误:

19 |     assert!(v.iter().max().unwrap() == 5);
   |                                     ^^ 没有实现 `&{integer} == {integer}`

等式左侧的术语是一个整数的视图(即&),而右侧的术语是一个实际的整数。

要将整数的“视图”转换为常规整数,我们需要使用“解引用”操作。这是通过在值前加*运算符实现的。

fn main() {
    let v = Vec::from([1,2,3,4,5]);

    assert!(*v.iter().max().unwrap() == 5);
}

由于数组的元素是复制类型,解引用运算符会静默地复制max().unwrap()返回的5

你可以将*视为“撤销”一个&而不干扰原始值。

对非复制类型使用该运算符是一个复杂的话题。现在,你只需要知道,如果你收到一个视图(借用)一个复制类型并需要将其转换为“正常”类型,请使用*运算符。

Rust中的ResultOption

当我们可能收到“空”的东西时使用选项。Result(和Anchor程序返回的相同Result)用于我们可能收到错误的情况。

Result枚举

Rust中的Result<T, E>枚举用于一个函数的操作可能成功并返回类型为T(泛型类型)的值,也可能失败并返回类型为E(泛型错误类型)的错误。它的设计是为处理可能导致成功结果或错误条件的操作。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

在Rust中,?运算符用于Result<T, E>枚举,而unwrap()方法则用于Result<T, E>Option<T>枚举。

?运算符

?运算符只能在返回Result的函数中使用,因为它是返回ErrOk的语法糖。

?运算符用于从Result<T, E>枚举中提取数据,如果函数执行成功,则返回OK(T)变体,或者如果发生错误则抛出Err(E)

unwrap()方法的工作方式相同,但适用于Result<T, E>Option<T>枚举,然而由于它可能在发生错误时崩溃程序,应谨慎使用。

现在,考虑以下代码:

pub fn encode_and_decode(_ctx: Context<Initialize>) -> Result<()> {
    // 创建`Person`结构的新实例
    let init_person: Person = Person {
        name: "Alice".to_string(),
        age: 27,
    };

    // 将`init_person`结构编码为字节向量
    let encoded_data: Vec<u8> = init_person.try_to_vec().unwrap();

    // 将编码的数据解码回`Person`结构
    let data: Person = decode(_ctx, encoded_data)?;

    // 日志打印解码的人的名字和年龄
    msg!("我的名字是 {:?},我今年 {:?}岁了。", data.name, data.age);

    Ok(())
}

pub fn decode(_accounts: Context<Initialize>, encoded_data: Vec<u8>) -> Result<Person> {
    // 将编码的数据解码回`Person`结构
    let decoded_data: Person = Person::try_from_slice(&encoded_data).unwrap();

    Ok(decoded_data)
}

try_to_vec()方法将结构编码为字节向量,并返回Result<T, E>枚举,其中T是字节向量,而unwrap()方法用于从OK(T)中提取字节向量的值。如果该方法无法将结构转换为字节向量,这将导致程序崩溃。

转载:https://learnblockchain.cn/article/11426


Solana基础 - Solidity开发者的Rust基础


本教程深入探讨了Solidity和Rust在控制流、数组、映射、结构体和常量等方面的语法对比,旨在帮助掌握Solidity的开发者快速上手Rust编程。

本教程介绍了在 Solidity 中最常用的语法,并展示了其在 Rust 中的等价实现。

如果你想了解 Rust 与 Solidity 之间的高层次差异,请查看链接的教程。本教程假设你已经了解 Solidity,假如你对 Solidity 不熟悉,请查看我们免费的 Solidity 教程

创建一个名为 tryrust 的新 Solana Anchor 项目并设置环境。

条件语句

我们可以说,开发人员在 Solidity 中可以根据特定条件控制执行流程的方式有 2 种:

  • If-Else 语句
  • 三元运算符

现在让我们看看上述内容在 Solidity 中的实现,以及它们在 Solana 中的翻译。

If-Else 语句

在 Solidity 中:

function ageChecker(uint256 age) public pure returns (string memory) {
    if (age >= 18) {
        return "你已满 18 岁或以上";
    } else {
        return "你未满 18 岁";
    }
}

在 Solana 中,在 lib.rs 中添加一个名为 age_checker 的新函数:

pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
    if age >= 18 {
        msg!("你已满 18 岁或以上");
    } else {
        msg!("你未满 18 岁");
    }
    Ok(())
 }

请注意,条件 age >= 18 没有括号——这在 if 语句中是可选的。

要测试,请在 ./tests/tryrust.ts 中添加另一个 it 块:

it("年龄检查", async () => {
    // 在这里添加你的测试。
    const tx = await program.methods.ageChecker(new anchor.BN(35)).rpc();
    console.log("你的交易签名", tx);
});

运行测试后,我们应该得到以下日志:

Transaction executed in slot 77791:
  Signature: 2Av18ej2YjkRhzybbccPpwEtkw73VcBpDPZgC9iKrmf6mvwbqjA517garhrntWxKAM1ULL2eAv5vDWJ3SjnFZq6j
  Status: Ok
  Log Messages:
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
    Program log: Instruction: AgeChecker
    Program log: 你已满 18 岁或以上
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 440 of 200000 compute units
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success

三元运算符

在 Solidity 中将 if-else 语句分配给变量:

function ageChecker(uint256 age) public pure returns (bool a) {
    a = age % 2 == 0 ? true : false;
}

在 Solana 中,我们基本上只是将一个 if-else 语句分配给一个变量。下面的 Solana 程序与上述相同:

pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
    let result = if age >= 18 {"你已满 18 岁或以上"} else { "你未满 18 岁" };
    msg!("{:?}", result);
    Ok(())
}

请注意,在 Rust 的三元运算符示例中,if/else 块以分号结尾,因为这是分配给一个变量的。

还要注意,内部值没有以分号结尾,因为它作为返回值返回给变量,就像在表达式而不是语句的情况下不在 Ok(()) 之后加分号一样。

如果年龄是偶数,程序将输出 true,否则为 false:

Transaction executed in slot 102358:
  Signature: 2zohZKhY56rLb7myFs8kabdwULJALENyvyFS5LC6yLM264BnkwsThMnotHNAssJbQEzQpmK4yd3ozs3zhG3GH1Gx
  Status: Ok
  Log Messages:
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
    Program log: Instruction: AgeChecker
    Program log: true
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 792 of 200000 compute units
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success

Rust 还有一个更强大的控制流结构叫做 match。让我们看看下面的使用 match 的示例:

pub fn age_checker(ctx: Context<Initialize>, age: u64) -> Result<()> {
    match age {
        1 => {
            // 如果年龄等于 1 则执行的代码块
            msg!("年龄是 1");
        },
        2 | 3 => {
            // 如果年龄等于 2 或 3 则执行的代码块
            msg!("年龄是 2 或 3");
        },
        4..=6 => {
            // 如果年龄在 4 到 6(包括)之间则执行的代码块
            msg!("年龄在 4 到 6 之间");
        },
        _ => {
            // 任何其他年龄的代码块
            msg!("年龄是其他值");
        }
    }
    Ok(())
}

For 循环

我们知道,for 循环允许遍历范围、集合和其他可迭代对象,在 Solidity 中是这样写的:

function loopOverSmth() public {
    for (uint256 i=0; i < 10; i++) {
        // 做一些事情...
    }
}

这在 Solana(Rust)中的等价实现为:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    for i in 0..10 {
        // 做一些事情...
    }

    Ok(())
}

是的,简单如斯,但我们如何使用自定义步长遍历范围呢?以下是在 Solidity 中的预期行为:

function loopOverSmth() public {
    for (uint256 i=0; i < 10; i+=2) {
        // 做一些事情...

        // 将 i 增加 2
    }
}

在 Solana 中使用 step_by 的等价实现是:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    for i in (0..10).step_by(2) {
        // 做一些事情...

        msg!("{}", i);
    }     

    Ok(())
}

运行测试后,我们应该获得以下日志:

Transaction executed in slot 126442:
  Signature: 3BSPA11TZVSbF8krjMnge1fgwNsL9odknD2twAsDeYEF39AzaJy1c5TmFCt6LEzLtvWnjzx7VyFKJ4VT1KQBpiwm
  Status: Ok
  Log Messages:
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
    Program log: Instruction: Initialize
    Program log: 0
    Program log: 2
    Program log: 4
    Program log: 6
    Program log: 8
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2830 of 200000 compute units
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success

数组和向量

Rust 和 Solidity 在数组支持上有所不同。虽然 Solidity 原生支持固定和动态数组,但 Rust 仅内置支持固定数组。如果你想要动态长度的列表,请使用向量。

现在,让我们看一些示例,演示如何声明和初始化固定和动态数组。

固定数组

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    // 声明一个固定大小为 5 的 u32 数组
    let my_array: [u32; 5] = [10, 20, 30, 40, 50];

    // 访问数组元素
    let first_element = my_array[0];
    let third_element = my_array[2];

    // 声明一个固定大小为 3 的可变 u32 数组
    let mut mutable_array: [u32; 3] = [100, 200, 300];

    // 将第二个元素从 200 修改为 250
    mutable_array[1] = 250;

    // 你程序的其余逻辑

    Ok(())
}

动态数组

在 Solana 中模拟动态数组的方法涉及使用来自 Rust 标准库的 Vec(向量)。以下是一个示例:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    // 使用 Vec 声明一个类似动态数组的结构
    let mut dynamic_array: Vec<u32> = Vec::new();

    // 向动态数组添加元素
    dynamic_array.push(10);
    dynamic_array.push(20);
    dynamic_array.push(30);

    // 访问动态数组的元素
    let first_element = dynamic_array[0];
    let third_element = dynamic_array[2];

    // 你程序的其余逻辑
    msg!("第三个元素 = {}", third_element);

    Ok(())
}

dynamic_array 变量必须声明为可变(mut),以允许进行变更(推送、弹出、覆盖索引等)。

程序在运行测试后应该记录如下内容:

Transaction executed in slot 195373:
  Signature: 4113irrcBsFbNaiZia5c84yfJpS4Hn4H1QawfUSHYoPuuQPj22JnVFtDMHmZDFkQ3vK15SrDUSTakh5fT4N8UVRf
  Status: Ok
  Log Messages:
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
    Program log: Instruction: Initialize
    Program log: 第三个元素 = 30
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 1010 of 200000 compute units
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success

映射

与 Solidity 不同,Solana 缺少内置的映射数据结构。然而,我们可以通过利用 Rust 标准库中的 HashMap 类型在 Solana 中复制键值映射的功能。与 EVM 链不同的是,我们在这里展示的映射是在内存中的,而不是存储中的。EVM 链没有内存哈希映射。我们将在稍后的教程中展示 Solana 中的存储映射。

让我们看看如何使用 HashMap 在 Solana 中创建一个映射。将提供的代码片段复制并粘贴到 lib.rs 文件中,并记得将程序 ID 替换为你自己的:

use anchor_lang::prelude::*;

declare_id!("53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX");

#[program]
pub mod tryrust {
    use super::*;
        // 导入 HashMap 库
    use std::collections::HashMap;

    pub fn initialize(ctx: Context<Initialize>, key: String, value: String) -> Result<()> {
        // 初始化映射
        let mut my_map = HashMap::new();

        // 向映射中添加键值对
        my_map.insert(key.to_string(), value.to_string());

        // 记录映射中与键对应的值
        msg!("我的名字是 {}", my_map[&key]);

        Ok(())
    }
}

my_map 变量也被声明为可变,以便进行编辑(即,添加/删除键→值对)。还注意我们是如何导入 HashMap 库的?

由于 initialize 函数接收两个参数,因此测试也需要进行更新:

it("已初始化!", async () => {
    // 在这里添加你的测试。
    const tx = await program.methods.initialize("name", "Bob").rpc();
    console.log("你的交易签名", tx);
});

当我们运行测试时,会看到以下日志:

Transaction executed in slot 216142:
  Signature: 5m4Cx26jaYT3c6YeJbLMDHppvki4Kmu3zTDMgk8Tao9v8b9sH7WgejETzymnHuUfr4hY25opptqniBuwDpncbnB9
  Status: Ok
  Log Messages:
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
    Program log: Instruction: Initialize
    Program log: 我叫 Bob
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2634 of 200000 compute units
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success

结构体

在 Solidity 和 Solana 中,结构体用于定义可以包含多个字段的自定义数据结构。让我们看看在 Solidity 和 Solana 中的结构体示例。

在 Solidity 中:

contract SolidityStructs {

    // 在 Solidity 中定义结构体
    struct Person {
        string my_name;
        uint256 my_age;
    }

    // 创建结构体的实例
    Person person1;

    function initPerson1(string memory name, uint256 age) public {
        // 访问和修改结构体字段
        person1.my_name = name;
        person1.my_age = age;
    }
}

在 Solana 中与之对应的实现:

pub fn initialize(_ctx: Context<Initialize>, name: String, age: u64) -> Result<()> {
    // 在 Solana 中定义结构体
    struct Person {
        my_name: String,
        my_age: u64,
    }

    // 创建结构体的实例
    let mut person1: Person = Person {
        my_name: name,
        my_age: age,
    };

    msg!("{} 的年龄为 {} 岁", person1.my_name, person1.my_age);

    // 访问和修改结构体字段
    person1.my_name = "Bob".to_string();
    person1.my_age = 18;

    msg!("{} 的年龄为 {} 岁", person1.my_name, person1.my_age);

    Ok(())
}

练习 : 更新测试文件以传递两个参数 Alice 和 20 给 initialize 函数并运行测试,你应该得到以下日志:

Transaction executed in slot 324406:
  Signature: 2XBQKJLpkJbVuuonqzirN9CK5dNKnuu5NqNCGTGgQovWBfrdjRcVeckDmqtzyEPe4PP8xSN8vf2STNxWygE4BPZN
  Status: Ok
  Log Messages:
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX invoke [1]
    Program log: Instruction: Initialize
    Program log: Alice 的年龄为 20 岁
    Program log: Bob 的年龄为 18 岁
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX consumed 2601 of 200000 compute units
    Program 53hgft52DHUKMPHGu1kusuwxFGk2T8qngwSw2SyGRNrX success

在提供的代码片段中,Solidity 实现将结构体的实例存储在存储中,而在 Solana 实现中,所有操作都发生在初始化函数中,且没有在链上存储。存储将在以后的教程中讨论。

Rust 中的常量

在 Rust 中声明常量变量非常简单。使用 const 关键字而不是 let 关键字。可以在 #[program] 块外声明这些常量。

use anchor_lang::prelude::*;

declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");

// *** 在这里声明常量 ***
const MEANING_OF_LIFE_AND_EXISTENCE: u64 = 42;

#[program]
pub mod tryrust {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!(&format!("终极问题的答案: {}", MEANING_OF_LIFE_AND_EXISTENCE)); // 这里的新行
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

usize 类型和类型转换

大多数时候我们可以假定在 Solana 中的无符号整数类型为 u64,但在测量列表的长度时,有一个例外:它将是 usize 类型。你需要将变量转换,如以下 Rust 代码所示:

use anchor_lang::prelude::*;

declare_id!("EiR8gcMCX11tYMRfoZ2vyheZsZ2NvdUTvYrRAUvTtYnL");

#[program]
pub mod usize_example {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {

       let mut dynamic_array: Vec<u32> = Vec::from([1,2,3,4,5,6]);
       let len = dynamic_array.len(); // 这个类型为 usize

       let another_var: u64 = 5; // 这个类型为 u64

       let len_plus_another_var = len as u64 + another_var;

       msg!("结果是 {}", len_plus_another_var);

       Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

Try Catch

Rust 没有 try catch。故障预期会返回错误(就像我们在 Solana 回滚和错误的教程中所做的)或对于不可恢复性错误引发恐慌。

练习 : 编写一个 Solana / Rust 程序,接受一个 u64 向量,遍历它,将所有偶数推送到另一个向量中,然后打印新的向量。

转载:https://learnblockchain.cn/article/11406