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
的向量,当 v
和 another_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[];
Move 中向量的一个常见用例是表示“字节数组”,用 vector<u8>
表示。这些值通常用于加密目的,例如公钥或哈希结果。
这些值非常常见,以至于提供了特定的语法使其更具可读性,而不是必须使用 vector[]
,其中每个单独的 u8
值都以数字形式指定。
目前支持两种类型的 vector<u8>
字面量,字节字符串(Byte Strings)和十六进制字符串(Hex Sstrings)。
字节字符串是带引号的字符串字面值,以 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 |
十六进制字符串是以 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",
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)
交换向量 i
和 j
索引处的元素
#![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<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。【参考资料】
前面讲到,字节字符串 和 十六进制字符串 本质上是 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
根据给定的索引 i
和 j
返回子字符串:
#![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);