Sui推出新的代码检查工具linter和增强警告信息warning,提升了Move开发者的体验,为许多编程系统中常见的编码提供支持。新增的六个linters主要处理对象方面的问题,可以发现Sui特定代码中潜在的问题模式。此外,Move编译器现在还包括有关未使用结构体的警告,这对于Move开发者是很有帮助的。
这些新增功能有助于新手和经验丰富的开发者在构建利用Sui创新技术的apps时避免代码问题。这些linter和warning都包括忽略选项,允许开发者自定义其工作流程。
许多开发人员依赖linters在编码过程中捕捉代码问题,特别是在代码检查不常见的高效开发环境中。然而,由于Sui Move相对较新,它没有像使用了多年的编程语言那样拥有广泛的代码检查工具支持。
在Sui上的这一新框架中,目前代码检查支持是自愿选择的。开发人员需要在任何构建、测试、发布和升级命令中指定 --lint标志,以查看linter消息。在将来的版本中,这个框架将切换到默认开启的模式( --no-lint),以便linter消息的默认显示。
目前,Sui支持六种不同的linters,后续还会增加更多。未来的linters发展将受到社区反馈的指导。
这个分析工具会标记在其他对象和结构字段中使用sui::coin::Coin对象的情况。在大多数情况下,开发人员应该使用sui::balance::Balance来节省空间。
举个例子,在这个定义了sui::coin::Coin类型字段的简单模块中:
#[allow(unused_field)]
module coin_field::test {
struct S1 {}
struct S2 has key, store {
id: sui::object::UID,
c: sui::coin::Coin,
}
}
构建这个模块会导致以下linter消息:
warning[Lint W03001]: sub-optimal 'sui::coin::Coin' field type
┌─ ./sources/test.move:5:12
│
5 │ struct S2 has key, store {
│ ^^ The field 'c' of 'S2' has type 'sui::coin::Coin'
6 │ id: sui::object::UID,
7 │ c: sui::coin::Coin,
│ - Storing 'sui::balance::Balance' in this field will typically be more space-efficient
│
= This warning can be suppressed with '#[lint_allow(coin_field)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
注意,源代码中的#[allow(unused_field)]注解用于忽略未使用字段的警告,以使输出更加简洁。下文的“增强警告”部分将详细介绍新警告以及如何忽略警告和linter消息。
此linter会标记在某些情况下对集合进行相等性比较,比如sui::table::Table, sui::table_vec::TableVec, sui::bag::Bag等。此linter存在的理由在于,这种类型的比较并不是很有用,而且没有考虑到结构上的相等性。
举个例子,在一个包含尝试比较两个不同sui::bag::Bag集合引用的基本模块中:
module collection_eq::test {
public fun bag_eq(bag1: &sui::bag::Bag, bag2: &sui::bag::Bag): bool {
bag1 == bag2
}
}
构建此模块会导致以下linter消息:
warning[Lint W05001]: possibly useless collections compare
┌─ ./sources/test.move:3:14
│
3 │ bag1 == bag2
│ ^^ Comparing collections of type 'sui::bag::Bag' may yield unexpected result.
│
= Equality for collections of type 'sui::bag::Bag' IS NOT a structural check based on content
= This warning can be suppressed with '#[lint_allow(collection_equality)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
此linter会标记在已经具有store功能且开发者可以使用这些调用的对象上,存在对转移、共享和冻结调用的潜在自定义实现。在这种情况下,使用这些调用可能会有风险,因为自定义的转移、共享和冻结操作在这种情况下无法执行。如果一个函数被认为是潜在的自定义实现,那么它会将一个在给定模块中具有store功能的结构类型实例作为参数,然后将其作为私有转移、共享和冻结调用的参数传递。
举个例子,在一个包含尝试使用公共sui::transfer::transfer函数来传递具有store功能的对象作为参数的简单模块:
#[allow(unused_field)]
module custom_state_change::test {
struct S1 has key, store {
id: sui::object::UID
}
public fun custom_transfer(o: S1, a: address) {
sui::transfer::transfer(o, a)
}
}
构建此模块会导致以下linter消息:
warning[Lint W02001]: potentially unenforceable custom transfer/share/freeze policy
┌─ ./sources/test.move:7:16
│
7 │ public fun custom_transfer(o: S1, a: address) {
│ ^^^^^^^^^^^^^^^ - An instance of a module-private type with a store ability to be transferred coming from here
│ │
│ Potential unintended implementation of a custom transfer function.
8 │ sui::transfer::transfer(o, a)
│ -------- Instances of a type with a store ability can be transferred using the public_transfer function which often negates the intent of enforcing a custom transfer policy
│
= A custom transfer policy for a given type is implemented through calling the private transfer function variant in the module defining this type
= This warning can be suppressed with '#[lint_allow(custom_state_change)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
此linter会标记冻结包含(不论是否具有传递性)内部对象的对象。换句话说,它会标记对象的冻结,而这些对象的字段(直接或非直接)包含其他对象。冻结这种对象会防止内部对象的解包。
举个例子,在一个包含尝试冻结类型为Wrapper的对象的基本模块,其中Wrapper包含另一个对象类型Inner字段:
#[allow(unused_field)]
module freeze_wrapped::test {
struct Inner has key, store {
id: sui::object::UID
}
struct Wrapper has key, store {
id: sui::object::UID,
inner: Inner,
}
public fun freeze(w: Wrapper) {
sui::transfer::public_freeze_object(w);
}
}
构建此模块会导致以下linter消息:
warning[Lint W04001]: attempting to freeze wrapped objects
┌─ ./sources/test.move:13:45
│
9 │ inner: Inner,
│ ----- The field of this type is a wrapped object
·
13 │ sui::transfer::public_freeze_object(w);
│ ^ Freezing an object of type 'Wrapper' also freezes all objects wrapped in its field 'inner'.
│
= This warning can be suppressed with '#[lint_allow(freeze_wrapped)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
此linter标记将对象转账给从sui::tx_context::sender()调用获取的交易发送方。此linter的目标是鼓励开发人员从函数中返回对象,而不是将它们转移给交易发送方。从函数中返回对象可以通过允许调用者直接使用返回的对象来增加可编程交易区块中函数的可组合性。
举个例子,在一个包含尝试将新创建的对象转移给交易发送方的简单模块中:
module self_transfer::test {
struct S1 has key, store {
id: sui::object::UID
}
public fun public_transfer(ctx: &mut sui::tx_context::TxContext) {
let o = S1 { id: sui::object::new(ctx) };
sui::transfer::public_transfer(o, sui::tx_context::sender(ctx))
}
}
构建此模块会导致以下linter消息:
warning[Lint W01001]: non-composable transfer to sender
┌─ ./sources/test.move:8:9
│
6 │ public fun public_transfer(ctx: &mut sui::tx_context::TxContext) {
│ --------------- Returning an object from a function, allows a caller to use the object and enables composability via programmable transactions.
7 │ let o = S1 { id: sui::object::new(ctx) };
8 │ sui::transfer::public_transfer(o, sui::tx_context::sender(ctx))
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ │ │
│ │ Transaction sender address coming from here
│ Transfer of an object to transaction sender address in function public_transfer
│
= This warning can be suppressed with '#[lint_allow(self_transfer)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
6. 共享和独享对象linter
此linter标记将作为函数参数传递或从可共享对象的拆包中产生的对象(这些对象很可能已经是独享对象),这将导致中止。建议的模式是在同一函数中创建一个新对象并在其中共享它。通常,以按值传递给函数的任何对象都是独享对象。
例如,考虑一个包含尝试共享作为参数传递对象的基本模块(该对象的数据流在函数中被跟踪):
#[allow(unused_field)]
module unused::test {
struct Obj has key, store {
id: sui::object::UID
}
public fun arg_object(o: Obj) {
let arg = o;
sui::transfer::public_share_object(arg);
}
}
构建此模块会导致以下linter消息:
warning[Lint W00001]: possible owned object share
┌─ ./sources/test.move:9:9
│
7 │ public fun arg_object(o: Obj) {
│ - A potentially owned object coming from here
8 │ let arg = o;
9 │ sui::transfer::public_share_object(arg);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ │ │
│ │ Creating a fresh object and sharing it within the same function will ensure this does not abort.
│ Potential abort from a (potentially) owned object created by a different transaction.
│
= This warning can be suppressed with '#[lint_allow(share_owned)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
除了linters及其警告之外,Move编译器现在还通过警告来帮助开发人员识别未使用的结构体,包括未使用的函数、常量、(函数)类型参数以及结构/对象字段。尽管许多开发人员认为这些警告很有帮助,但它们可能不受欢迎,特别是对于之前编译而没有任何警告的代码。希望忽略特定代码元素的这些警告的开发人员可以为模块、常量、类型定义或函数指定注释(形式为#[…])。
以下代码提供了生成一个新警告的示例。这个基本模块定义了一个未使用的常量:
module unused::test {
const UNUSED_CONST: u64 = 42;
}
构建这个模块将产生以下警告:
warning[W09011]: unused constant
┌─ ./sources/test.move:2:11
│
2 │ const UNUSED_CONST: u64 = 42;
│ ^^^^^^^^^^^^ The constant 'UNUSED_CONST' is never used. Consider removing it.
│
= This warning can be suppressed with '#[allow(unused_const)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
编译器返回了有关如何忽略特定警告的建议。在这种情况下,在常量级别放置#[allow(unused_const)]注释,如下所示:
module unused::test {
#[allow(unused_const)]
const UNUSED_CONST: u64 = 42;
}
在模块级别包含注释可以忽略相同类型的多个警告:
#[allow(unused_const)]
module unused::test {
const UNUSED_CONST: u64 = 42;
}
开发人员可以以与标准编译器警告类似的方式忽略linter消息。linter消息包括开发人员可以使用来忽略它的注释描述。用于忽略linter警告的注释中使用的关键字是lint_allow,而用于忽略标准警告的关键字是allow。如果开发人员选择忽略linter,编译器还会打印关于它忽略了多少条消息以及多少种消息的简单统计信息。
Move编程语言仅诞生四年,而Sui Move更是在后来才出现。尽管该语言的安全性和实用性使其在开发者社区中迅速受到欢迎,但与更成熟的编程语言相比,其开发工具的数量仍然不足。添加了一些专门针对Sui Move的linters和编译器警告,这只是改进开发者体验的众多努力之一。
然而,Move的开源性质意味着整个开发者社区都可以并且应该参与改进开发者体验。人们可以通过在论坛或其他场合表达需求,或者通过向存储库提交新的linters实现提案或其他语言改进请求来贡献。不管社区希望以哪种方式做出贡献,参与Sui论坛是帮助开发Move的第一步。
Sui是基于第一原理重新设计和构建而成的L1公有链,旨在为创作者和开发者提供能够承载Web3中下一个十亿用户的开发平台。Sui上的应用基于Move智能合约语言,并具有水平可扩展性,让开发者能够快速且低成本支持广泛的应用开发。获取更多信息:https://linktr.ee/sui_apac
官网|英文Twitter|中文Twitter|Discord|英文电报群|中文电报群