向量与字符串

向量是 Move 唯一的基本集合类型。vector<T> 是类型为 T 的元素的同构集合,可以通过从末端推入/弹出值来增加和删减向量中的元素。 其中 T 可以是任意的类型,例如:vector<u64>vector<address>vector<0x42::MyModule::MyResource>, 以及 vector<vector<u8>> 都是合法的。

任何类型的向量都可以通过字面值创建:


#![allow(unused)]
fn main() {
let v = vector[1, 2, 3];
}

当然也可以创建一个空向量:


#![allow(unused)]
fn main() {
let v = vector[];
}

但是当我们单独执行上面这行代码时,编译器会报错:

error[E04010]: cannot infer type
  ┌─ ./scripts/debug_script.move:8:9
  │
8 │         vector[];    
  │         ^^^^^^^^ Could not infer this type. Try adding an annotation

编译器告诉我们,无非推断向量的类型,尝试添加标注。这是因为向量的类型是从元素的类型或从向量的使用上推断出来的。我们声明了一个空向量,也没有使用它,编译器自然无法判断它的类型。 只要我们后续使用了这个向量,编译器就可以进行推断,例如:


#![allow(unused)]
fn main() {
let v = vector[]; 
let another_v =  vector[1, 2, 3];
assert!(v != another_v, 0);
}

我们知道整型的默认类型是 u64 ,所以 another_v 是一个 u64 的向量,当 vanother_v 比较时,编译器就会推断出 v 也是一个 u64 的向量。

或者我们可以显式的指明向量的类型:


#![allow(unused)]
fn main() {
vector<u8>[];
vector<u64>[];
vector<u128>[];
vector<address>[];
vector<bool>[];
vector<M::Coin>[];
let _v: vector<u8> = vector[];
}

vector<u8>

Move 中向量的一个常见用例是表示“字节数组”,用 vector<u8> 表示。这些值通常用于加密目的,例如公钥或哈希结果。 这些值非常常见,以至于提供了特定的语法使其更具可读性,而不是必须使用 vector[],其中每个单独的 u8 值都以数字形式指定。

目前支持两种类型的 vector<u8> 字面量,字节字符串(Byte Strings)和十六进制字符串(Hex Sstrings)。

Byte Strings

字节字符串是带引号的字符串字面值,以 b 为前缀,例如:


#![allow(unused)]
fn main() {
let s = b"Hello, world!";
debug::print(&s);
}

可以看到打印的结果是一串整型:

[debug] (&) [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]

字节字符串是允许转义序列的 ASCII 编码字符串。目前,支持的转义序列如下: | 转义序列 | 描述 | | :------: | :---------------------------------------: | | \n | 换行 | | \r | 回车 | | \t | 制表符 | | \\ | 反斜杠 | | \0 | Null | | \" | 引号 | | \xHH | 十六进制进制转义,插入十六进制字节序列 HH |

Hex Sstrings

十六进制字符串是以 x 为前缀的带引号的字符串字面值,例如:


#![allow(unused)]
fn main() {
let s = x"48656C6C6F210A";
debug::print(&s);
}

可以看到打印的结果是:

[debug] (&) [72, 101, 108, 108, 111, 33, 10]

每个字节对,范围从 00 到 FF 都被解析为十六进制编码的 u8 值。 所以每个字节对对应于结果 vector<u8> 中的单个元素。

字节字符串十六进制字符串本质上都是 vector<u8>,因此它们的类型实际上是一样的:


#![allow(unused)]
fn main() {
assert!(b"" == vector<u8>[], 0);
// assert!(b"" == vector<u64>[], 0);    // Error: Incompatible arguments to '=='
assert!(b"" == x"", 1);
assert!(b"Hello!\n" == x"48656C6C6F210A", 2);
assert!(b"\x48\x65\x6C\x6C\x6F\x21\x0A" == x"48656C6C6F210A", 3);
assert!(
    b"\"Hello\tworld!\"\n \r \\Null=\0" ==
        x"2248656C6C6F09776F726C6421220A200D205C4E756C6C3D00",
    4
);
}

标准库 std::vector

Move 标准库中的 std::vector 模块提供了一些用于操作 vector 的函数

  • empty<Element>(): vector<Element>;
    创建一个类型为 Element 的空向量

#![allow(unused)]
fn main() {
let _empty_v = vector::empty<u128>();
}
  • singleton<Element>(e: Element): vector<Element>
    返回一个包含元素 e 的长度为1的向量

#![allow(unused)]
fn main() {
let v = vector::singleton<u64>(123u64);
}
  • length<Element>(v: &vector<Element>): u64
    返回向量的长度

#![allow(unused)]
fn main() {
assert!(vector::length(&v) == 1, 0);
}

这时候向量 v 中只有一个元素 123u64,所以长度是 1

  • push_back<Element>(v: &mut vector<Element>, e: Element)
    将元素 e 添加到向量 v 的末尾

#![allow(unused)]
fn main() {
vector::push_back(&mut v, 456u64);
vector::push_back(&mut v, 789u64);
assert!(vector::length(&v) == 3, 0);
}

这里需要注意,因为需要对向量 v 的内容进行修改,所以需要传入 v可变引用

  • pop_back<Element>(v: &mut vector<Element>): Element
    从向量 v 中移除最后一个元素,并返回

#![allow(unused)]
fn main() {
assert!(vector::pop_back(&mut v) == 789u64, 0);
assert!(vector::length(&v) == 2, 0);
// vector::pop_back(&mut _empty_v); // Abort!
}

当我们对一个空向量执行这个函数时,程序回异常退出。

  • borrow<Element>(v: &vector<Element>, i: u64): &Element
    获得向量在索引 i 处的不可变引用

#![allow(unused)]
fn main() {
assert!(*vector::borrow(&v, 0) == 123u64, 0);
assert!(vector::borrow(&v, 1) == &456u64, 0);
// vector::borrow(&v, 2); // Abort!
}

函数返回的是 &Element,使用的时候需要注意类型的匹配。引用一个超出索引范围的元素时,程序会异常退出。

  • borrow_mut<Element>(v: &mut vector<Element>, i: u64): &mut Element
    获得向量在索引 i 处的可变引用

#![allow(unused)]
fn main() {
assert!(v == vector[123, 456], 0);
*vector::borrow_mut(&mut v, 0) = 321u64;
assert!(v == vector[321, 456], 0);
}

我们可以通过这个函数来修改向量内部的元素,注意这里需要用到解引用 *

  • destroy_empty<Element>(v: vector<Element>)
    销毁一个空的向量

#![allow(unused)]
fn main() {
vector::destroy_empty(_empty_v);
}
  • swap<Element>(v: &mut vector<Element>, i: u64, j: u64)
    交换向量 ij 索引处的元素

#![allow(unused)]
fn main() {
assert!(v == vector[321, 456], 0);
vector::swap(&mut v, 0, 1);
assert!(v == vector[456, 321], 0);
// vector::swap(&mut v, 0, 2);  // Abort!
}

i 或者 j 超出向量索引范围时,程序会异常退出。

  • reverse<Element>(v: &mut vector<Element>) 反转向量中元素的顺序

#![allow(unused)]
fn main() {
assert!(v == vector[456, 321], 0);
vector::reverse(&mut v);
assert!(v == vector[321, 456], 0);
}
  • append<Element>(lhs: &mut vector<Element>, other: vector<Element>)other 向量中的元素全部添加到 lhs 的末尾

#![allow(unused)]
fn main() {
assert!(v == vector[321, 456], 0);
vector::append(&mut v, vector[1001, 1002, 1003]);
assert!(v == vector[321, 456, 1001, 1002, 1003], 0);
}
  • contains<Element>(v: &vector<Element>, e: &Element): bool
    判断 e 是否在向量中,存在返回 true ,否则返回 false

#![allow(unused)]
fn main() {
assert!(vector::contains(&v, &321), 0);
}

注意这里的 e 是引用类型 &Element

  • index_of<Element>(v: &vector<Element>, e: &Element): (bool, u64)
    如果元素 e 位于向量的索引 i 处,返回 (true, i),否则返回 false, 0

#![allow(unused)]
fn main() {
let (if_exist, index) = vector::index_of(&v, &456);
assert!(if_exist == true, 0);
assert!(index == 1 , 0);
let (if_exist, index) = vector::index_of(&v, &999);
assert!(if_exist == false, 0);
assert!(index == 0 , 0);
}
  • remove<Element>(v: &mut vector<Element>, i: u64): Element. 移除索引 i 处的元素,并返回该元素,后续的元素按照原来顺序往前移

#![allow(unused)]
fn main() {
assert!(v == vector[321, 456, 1001, 1002, 1003], 0);
assert!(vector::remove(&mut v, 2) == 1001, 0);
assert!(v == vector[321, 456, 1002, 1003], 0);
}
  • swap_remove<Element>(v: &mut vector<Element>, i: u64): Element
    首先将索引 i 处的元素与最后的元素交换,然后将最后的元素弹出。上一个函数的复杂度是 O(n),这个函数的复杂度是 O(1),但是不能保持原有的顺序

#![allow(unused)]
fn main() {
assert!(v == vector[321, 456, 1002, 1003], 0);
assert!(vector::swap_remove(&mut v, 1) == 456, 0);
assert!(v == vector[321, 1003, 1002], 0);
}

销毁和复制 vector

vector<T> 的某些行为取决于元素类型 T 的能力(ability), 例如:如果向量中包含不具有 drop 能力的元素,就不能隐式的丢弃,必须用 vector::destroy_empty 显式销毁。 但前面讲到 vector::destroy_empty 只能销毁空向量,那对于非空的向量,我们应该如何销毁呢? 我们尝试在 module 中编写一个函数来销毁任意向量:


#![allow(unused)]
fn main() {
fun destroy_any_vector<T>(_vec: vector<T>) { }
}

我们想把向量传入函数中,然后隐式的丢弃它,但编译器会报错:

error[E06001]: unused value without 'drop'
   ┌─ ./sources/debug_module.move:12:47
   │  
12 │       fun destroy_any_vector<T>(vec: vector<T>) {
   │                                 ---  ---------
   │                                 │    │      │
   │                                 │    │      The type 'vector<T>' can have the ability 'drop' but the type argument 'T' does not have the required ability 'drop'
   │                                 │    The type 'vector<T>' does not have the ability 'drop'
   │                                 The parameter 'vec' still contains a value. The value does not have the 'drop' ability and must be consumed before the function returns
   │ ╭───────────────────────────────────────────────^
13 │ │             // vector::destroy_empty(vec) 
14 │ │     }
   │ ╰─────^ Invalid return

编译器告诉我们,T 没有 drop 的能力,因此不能这样丢弃。但如果我们给 T 加上一个限制,要求它具备 drop 的能力:


#![allow(unused)]
fn main() {
fun destroy_any_vector<T: drop>(_vec: vector<T>) { }
}

这样子我们就可以销毁具有 drop 能力的 T 组成向量了。

同样,除非元素类型具有 copy 能力,否则无法复制向量。换句话说,当且仅当 T 具有 copy 能力时,vector<T> 才具有 copy 能力。


#![allow(unused)]
fn main() {
let x = vector::singleton<u64>(10);
let y = copy x;
assert!(x == y, 0);

// without copy ability
// let p = vector::singleton<M::Coin>( M::create_coin(1) );
// let q = copy p;
// assert!(p == q, 0);
}

u64 因为具有 copy 能力,因此可以复制,但我们在实现 M::Coin 时没有赋予它 copy 的能力,当我们复制时,编译器就会报错。

ps:let y = copy x; 这行代码中,如果不加 copy 关键词也可以编译成功。 这是因为在 rust 中 place expressions 在被求值时,如果该类型实现了 Copy trait,那么值就会被copy。 如果该类型实现了 Sized trait,则会被move。【参考资料】

标准库 std::string

前面讲到,字节字符串 和 十六进制字符串 本质上是 vector<u8> ,Move 标准库中也提供了 std::string 模块。并提供了 String 类型:


#![allow(unused)]
fn main() {
/// A `String` holds a sequence of bytes which is guaranteed to be in utf8 format.
struct String has copy, drop, store {
    bytes: vector<u8>,
}
}

String 就是对 vector<u8> 的封装,模块内部也提供了一些操作函数:

  • utf8(bytes: vector<u8>): String
    vector<u8> 构建一个 String ,如果字节不能表示一个合法的utf8,则程序终止。

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108, 108, 111, 33, 10]);
debug::print(&s);
}

输出结果是:

[debug] (&) { [72, 101, 108, 108, 111, 33, 10] }
  • try_utf8(bytes: vector<u8>): Option<String>
    这个函数和上面类似,但输出是 Option,成功的话 Option 内部的向量会包含一个 String, 否则就只有一个空向量:

#![allow(unused)]
fn main() {
let valid_s = string::try_utf8(vector[72, 101, 108, 108, 111, 33, 10]);
let invalid_s = string::try_utf8(vector[72, 101, 108, 108, 111, 33, 255]);
debug::print(&valid_s);
debug::print(&invalid_s);
}

输出结果为:

[debug] (&) { [{ [72, 101, 108, 108, 111, 33, 10] }] }
[debug] (&) { [] }
  • bytes(s: &String): &vector<u8>
    返回对基础字节向量的引用:

#![allow(unused)]
fn main() {
assert!(*string::bytes(&s) == vector[72, 101, 108, 108, 111, 33, 10], 0);
}
  • is_empty(s: &String): bool
    检查字符串是否为空:

#![allow(unused)]
fn main() {
assert!(string::is_empty(&string::utf8(vector[])) == true, 0);
assert!(string::is_empty(&s) == false, 0);
}
  • length(s: &String): u64
    返回字符串的长度:

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108, 108, 111, 33, 10]);
assert!(string::length(&s) == 7, 0);
}
  • append(s: &mut String, r: String)
    s 后面追加字符串 r

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108]);
let r = string::utf8(vector[111, 33, 10]);
string::append(&mut s, r);
debug::print(&s);
}

可以看到输出结果是连个字符串的拼接:

[debug] (&) { [72, 101, 108, 111, 33, 10] }
  • append_utf8(s: &mut String, bytes: vector<u8>).
    另一种方法是,直接在字符串后面追加合法的 utf8 字节向量 vector<u8>

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108]);
let r = vector[111, 33, 10];
string::append_utf8(&mut s, r);
debug::print(&s);
}

输出结果和前面一致:

[debug] (&) { [72, 101, 108, 111, 33, 10] }
  • insert(s: &mut String, at: u64, o: String)
    s 中给定的字节索引位置处插入新字符串 o

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108]);
let o = string::utf8(vector[111, 33, 10]);
string::insert(&mut s, 2, o);
debug::print(&s);
}

可以看到新的字符串从第二位开始被插入:

[debug] (&) { [72, 101, 111, 33, 10, 108] }
  • sub_string(s: &String, i: u64, j: u64): String
    根据给定的索引 ij 返回子字符串:

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108, 108, 111, 33, 10]);
let sub_s = string::sub_string(&s, 2, 5);
debug::print(&sub_s);
}

从结果可以看到,子字符串包括开始的索引 i 但不包括结束的索引 j

[debug] (&) { [108, 108, 111] }
  • index_of(s: &String, r: &String): u64
    查询 r 字符串在 s 中第一次出现的索引值,若果 r 不是 s 的子字符串,则返回 s 的长度:

#![allow(unused)]
fn main() {
let s = string::utf8(vector[72, 101, 108, 108, 111, 33, 10]);
let sub_s = string::utf8(vector[108, 108, 111]);
let another_s = string::utf8(vector[108, 108, 112]);
assert!(string::index_of(&s, &sub_s) == 2, 0);
assert!(string::index_of(&s, &another_s) == string::length(&s), 0);
}