Equality(相等性)

Move语言支持两种相等性操作 ==!=

操作

语法操作描述
==等于如果两个操作数具有相同的值,则返回 true,否则返回 false
!=不等于如果两个操作数具有不同的值,则返回 true,否则返回 false

类型

==!= 操作只有在两个操作数具有相同类型时才能使用。

0 == 0; // `true`
1u128 == 2u128; // `false`
b"hello" != x"00"; // `true`

相等性和不相等性操作也适用于所有用户定义的类型!

module 0x42::example {
    public struct S has copy, drop { f: u64, s: vector<u8> }

    fun always_true(): bool {
        let s = S { f: 0, s: b"" };
        s == s
    }

    fun always_false(): bool {
        let s = S { f: 0, s: b"" };
        s != s
    }
}

如果操作数具有不同的类型,则会出现类型检查错误:

1u8 == 1u128; // 错误!
//     ^^^^^ 需要类型为 'u8' 的参数
b"" != 0; // 错误!
//     ^ 需要类型为 'vector<u8>' 的参数

引用类型比较

在比较引用时,引用的类型(不可变或可变)并不重要。这意味着可以将一个不可变的 & 引用与相同底层类型的可变 &mut 引用进行比较。

let i = &0;
let m = &mut 1;

i == m; // `false`
m == i; // `false`
m == m; // `true`
i == i; // `true`

以上代码等同于在需要时对每个可变引用进行显式冻结:

let i = &0;
let m = &mut 1;

i == freeze(m); // `false`
freeze(m) == i; // `false`
m == m; // `true`
i == i; // `true`

但是,底层类型必须是相同的类型:

let i = &0;
let s = &b"";

i == s; // 错误!
//   ^ 需要类型为 '&u64' 的参数

自动借用

从 Move 2024 版本开始,如果一个操作数是引用而另一个不是,==!= 操作符会自动将其借用。这意味着以下代码可以正常工作而不会出现任何错误:

let r = &0;

// 在所有情况下,`0` 都会自动作为 `&0` 进行借用
r == 0; // `true`
0 == r; // `true`
r != 0; // `false`
0 != r; // `false`

这种自动借用始终是不可变借用。

限制

==!= 操作符在比较时会消耗值。因此,类型系统要求类型必须具有 drop 能力。需要注意的是,如果没有 drop 能力,所有权必须在函数结束时转移,并且此类值只能在其声明模块内显式销毁。如果直接在相等性 == 或不相等性 != 中使用它们,将会销毁该值,从而破坏 drop 能力 的安全性保证!

module 0x42::example {
    public struct Coin has store { value: u64 }
    fun invalid(c1: Coin, c2: Coin) {
        c1 == c2 // 错误!
//      ^^    ^^ 这些资产将被销毁!
    }
}

但是,程序员可以始终先借用该值而不是直接比较该值,并且引用类型具有 drop。例如:

module 0x42::example {
    public struct Coin has store { value: u64 }
    fun swap_if_equal(c1: Coin, c2: Coin): (Coin, Coin) {
        let are_equal = &c1 == c2; // 合法,注意 `c2` 会自动被借用
        if (are_equal) (c2, c1) else (c1, c2)
    }
}

避免额外的拷贝

虽然程序员可以比较任何具有 drop 的类型的值,但通常应该通过引用进行比较,以避免昂贵的拷贝操作。

let v1: vector<u8> = function_that_returns_vector();
let v2: vector<u8> = function_that_returns_vector();
assert!(copy v1 == copy v2, 42);
//      ^^^^       ^^^^
use_two_vectors(v1, v2);

let s1: Foo = function_that_returns_large_struct();
let s2: Foo = function_that_returns_large_struct();
assert!(copy s1 == copy s2, 42);
//      ^^^^       ^^^^
use_two_foos(s1, s2);

此代码是完全可接受的(假设 Foo 具有 drop),但不是高效的。可以移除高亮的拷贝操作,并用借用代替:

let v1: vector<u8> = function_that_returns_vector();
let v2: vector<u8> = function_that_returns_vector();
assert!(&v1 == &v2, 42);
//      ^      ^
use_two_vectors(v1, v2);

let s1: Foo = function_that_returns_large_struct();
let s2: Foo = function_that_returns_large_struct();
assert!(&s1 == &s2, 42);
//      ^      ^
use_two_foos(s1, s2);

== 本身的效率不变,但是拷贝操作被移除,因此程序更加高效。