--- tags: research --- # Move Language Overview Concise document outlining Move Language features. ## Types Integer: `u8`, `u64`, `u128` (cast with `as`) Boolean: `bool` Address: `address` Comments: `// ...`, `/* */` ## Expressions Empty: `()` Literal: `10;` Assignment: `let name = literal;` Operators: `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `&`, `^`, `|` Use underscore for unused vars: `_ = literal`. Else compiler error. Shadowing without using the first assigned value will throw. ``` script { fun main() { let a = 1; // unused let a = 2; let _ = a; } } ``` Block Expression: `{}` Blocks return values if the final line does not end with `;`. ``` let a = { let b = 1; b + 1 }; // a == 2 ``` ## Control Flow If/else: `if {} else {}`. End w/ semi colon if block returns. While: `while (bool_expression) {};` Infinite Loop: `loop {};` Compiler doesn't check for halting problem. Continue / Break: ``` script { fun main() { let a = 1; loop { a = a + 1; if (a / 2 == 0) continue; if (a == 5) break; // ... } } } ``` Weird Rule: `if (bool_exp) continue;` works but not `if (bool_exp) { continue; }` Above applies for `break` as well. Abort: `if (bool_exp) { abort err_code; }` Assert: `assert!(bool_exp, err_code)` ## Module and Import Standard Library: `0x1` Define module: `module ModName {}` Direct import: `0x1::Offer::create(bool_exp, code);` Use Keyword: `use 0x1::Vector as Vec;` Method access: `Module::Method()` Member import: `use Module::Struct::member;` Multi-member import: `use Module::Struct::{member0, member1};` Self import: ``` script { // Vector::Self == Vector use 0x1::Vector::{ Self, empty }; fun main() { let vec = empty<u8>(); Vector::push_back(&mut vec, 10); } } ``` ## Constants Define: `const NAME: type = literal;` Must define constant in module. ## Functions Definition: `fun name(arg0: type0, .., arg_n: type_n): ret_type {}` Script can only contain `main` function. Executed as transaction. Multi Return: `fun mr(a: u8, b, u8): (c: u8, d: u8) {}` Module function visibility is private by default, make public with `public fun...` Native functions: Defined by the VM itself and may vary in different move impls. ``` module Signer { native public fun borrow_address(s: &signer): &address; } ``` ## Structures Struct: `struct Name { field: type, field: type }` Structs can have other structs as types. No recursive structs: `struct Node { child: Node } // NO` Create instance: `let country = Country { id: 0, population: 1 };` Access struct field: `country.id` Destructure: `let (id, population): (u8, u8) = country;` Also destructure: `let Country { id, population } = country;` ``` module Country { struct Country { id: u8, population: u8 } public fun new_country(id: u8, population: u8): Country { Country { id, population } } public fun id(country: Country): u8 { country.id } public fun population(country: Country): u8 { country.population } // ... } script { use {{sender}}::Country as C; use 0x1::Debug; fun main() { let country = C::new_country(1, 100); Debug::print<u8>(&C::id(&country)); Debug::print<u8>(&C::id(&country)); C::destroy(country); } } ``` ## Types and Abilities Four abilities: 1. Copy: can be cloned/copied 2. Drop: can be dropped by end of scope 3. Key: can be used as key for global storage ops 4. Store: can be stored in global storage Ability declaration: `struct Name as copy, drop, key, store {}` ## Ownership and References It's like rust. Variable has a single owner. When variable is passed to a function, the function now owns it. It cannot be used after this UNLESS the function returns ownership. Cloning will create a copy and pass ownership of the copy to the function. Primitive types implicitly copy. References are either immutable `&value` or mutable `&mut value`. Borrow checking happens at compile time. Dereferencing happens with `*ref_of_value`. Creates a copy. Copy inner field of struct: ``` module M { struct H as copy {} struct T { inner: H } public fun copy_inner(t: &T): H { *&t.inner } } ``` ## Generics Stand-in type. Replaced at compile time as necessary. ``` module Storage { struct Box<T> { value: T, } public fun create_box_u64(value: u64): Box<u64> { Box<u64>{ value } } // this constrains the `T` type to have `copy`. public fun create_box_with_copy<T: copy>(value: T): Box<T: copy> { Box<T: copy>{ value } } } ``` Multi type generic: `struct Name<T0, T1> { a: T0, b: t1 }` ## Vectors Bring into scope: `use 0x1::Vector;` Methods: create empty: `Vector::empty<E>(): vector<E>;` get length: `Vector::length<E>(v: &vector<E>): u64;` push element: `Vector::push_back<E>(v: &mut vector<E>, e: E);` pop element and return: `Vector::pop_back<E>(v: &mut vector<E>): E;` borrow immutable: `Vector::borrow<E>(v: &vector<E>, i: u64): &E;` borrow mutable: `Vector::borrow_mut<E>(v: &mut vector<E>, i: u64): &E; // may be typo` Use bytes as vector: `let str: vector<u8> = x"12435";` ## Programmable Resources Resources are custom types to encode digital assets with programability (jfc such marketing) They are ordinary values in the lang. Can be stored as data structs, args, rets, etc. Resources cannot be duped, reused, or discarded. Statically enforced by the Move VM verifier. ## Sender as Signer Signer is non-copyable resource-like type that holds tx sender address. Only has `drop`. Canonical name of var holding signer is `account`. Signer mod: `0x1::Signer::{borrow_address, address_of};` In modules, `&signer`as arg indicates the sender address. ## Resource Definition (only key and store): `struct T has key, store { field: u8 }` Key allows struct to be storage identifier. Store allows struct to be stored under key. Resource properties: 1. stored under account, exists only when assigned to account 2. account can only hold one resource of one type 3. resource cannot be copied nor dropped, only stored 4. resource value must be used. when read, must be stored or destructured ## Create and Move Resource ``` // modules/Collection.move module Collection { use 0x1::Vector; struct Item has drop, store { // todo } struct Collection has store, key { items: vector<Item> } public fun start_collection(account: &signer) { // native fun move_to<T: key>(account: &signer, value: T); move_to<Collection>(amount, Collection { items: Vector::empty<Collection>() }) } public fun exists_at(at: address): bool { // native fun exists_at<T: key>(addr: address): bool; exists<Collection>(at) } } ``` ## Read and Modify Resource ``` module Collection { use 0x1::Signer; use 0x1::Vector; struct Item has store, drop {} struct Collection has key, store { items: vector<Item> } public fun size(account: &signer): u64 aquires Collection { let owner = Signer::address_of(account); let collection = borrow_global<Collection>(owner); Vector::length(&collection.items) } public fun add_item(account: &signer) aquires T { // native fun borrow_global_mut<T: key>(addr: address): &mut T; let collection = borrow_global_mut<T>(Signer::address_of(account)); Vector::push_back(&mut collection.items, Item {}); } ``` Aquires keyword put after func ret value, explicitly defines resources aquired by it. ## Take and Destroy Resouce ``` module Collection { public fun destroy(account: &signer) aquires Collection { // native fun move_from<T: key>(addr: address): T; let collection = move_from<Collection>(Signer::address_of(account)); // MUST use. So we use `_` to "use" it. let Collection { items: _ } = collections; // resource destroyed. } ``` ## Friends Used to declare modules that are trusted by the current module. Trusted can call any function defined with `public(friend)` visibility. Declare friend mod: `friend Address::Name`, `friend mod_alias` Rules: - Mod cannot declare itself as friend - Friend mods must be known by the compiler - Friend mods must be within same account address - Friend relationships cannot create cyclic mod dependencies - Mod friend list cannot have duplicates ## Packages ``` a_move_package ├── Move.toml (required) ├── sources (required) ├── examples (optional, test & dev mode) ├── scripts (optional) ├── doc_templates (optional) └── tests (optional, test mode) ``` `Move.toml`: ``` [package] name = <string> version = "<uint>.<uint>.<uint>" license* = <string> authors* = [<string>] [addresses] <addr_name> = "_" | "<hex_address>" [dependencies] <string> = { local = <string>, addr_subst* = { (<string> = (<string> | "<hex_addr>"))+ }} <string> = { git = "<git_url>", subdir=<dir_path>, rev = <commit>, addr_subst* = ... } [dev-addresses] <addr_name> = "_" | "<hex_addr>" [dev-dependencies] ... ``` ## Unit Tests Three annotations: - `#[test]` - `#[test_only]` - `#[expected_failure]` The `#[test]` and `#[test_only]` can be used with or without args. Use Args: ``` #[test(arg = @0xC0FFEE)] fun t_sig(arg: signer) {} ```