Basic Move Syntax
This section treats with basic Move syntax, comparing the language to Rust. This is merely intended to provide some guidance for booklet participants. More comprehensive guides can be found at move-book.com and move-language.github.io.
Allocation and the Move Memory Model
Move's resource-oriented language model leads to a unique memory model:
- Resources in Move are allocated and deallocated implicitly based on their defined lifespan and ownership semantics. This ensures proper resource management without the need for explicit memory allocation or deallocation.
- Resources are created and stored on the blockchain as part of a transaction, providing a persistent and tamper-resistant storage mechanism.
- Global storage in Move allows for the storage of resource data that can be accessed across multiple transactions and smart contracts.
While function ownership in Move is similar to Rust, it is less permissive and closer to being purely linear--restricting the set of possible borrows.
Expressions and Control Flow
Expressions
Move uses a similar expression-orientation to Rust. Expressions are evaluated to produce a value. Expressions can be used in many places, including:
#![allow(unused)] fn main() { fun eight() : u8 { 8 } }
if
Branching in Move is accomplished via if and else statements. There is not an else if statement.
#![allow(unused)] fn main() { if (a) { debug::print<u8>(&0); } else { debug::print<u8>(&99); }; }
if is an expression.
#![allow(unused)] fn main() { let value = if (true) { 8 } else {0}; // value set to 8 }
Move syntax for expressions and control flow shares similarities with Rust. Basic control flow constructs like if, while, and loop are present in Move as well. However, Move has a more limited set of expressions and control flow features compared to Rust.
while and loop
Move supports while and loop looping constructs. while loops while condition is true. loop loops infinitely. There is no for loop, both while and loop are roughly equally used as replacements.
#![allow(unused)] fn main() { // example of while loop while (i < 10) { Vector::push_back(&mut a, i); i = i + 1; }; }
Types
Primitives
- Move has the primitive types
boolean,u8,u64,u128,address, andsigner. - Move also supports hex- and byte-string literals.
#![allow(unused)] fn main() { let byte_val = b"hello, world!"; // byte string let hex_val = x"48656C6C6F210A"; // hex string }
- Integers can be type cast with the
askeyword. signerrepresents the sender of a transaction and is used for access control and authentication.signercan be converted to address withsigner::address_of.
Abilities
Type abilities in Move specify certain primitive memory behaviors and constraints for types. These abilities are perhaps most similar to different pointer types in Rust.
copy: Thecopyability allows for the type's value to be copied.drop: Thedropability enables the necessary cleanup actions when the type goes out of scope.store: Thestoreability allows the type's value to be stored inside a struct in global storage.key: Thekeyability allows the type's value to be used as a unique identifier or index in the global storage of the Move blockchain.- Conditional abilities allow types to have different behaviors based on conditions.
Generic and behavior
- Move supports generics for structs and functions.
- It's possible to achieve polymorphic behavior with generics and phantom types.
- Often you will want to nest generic structures inside of resources to achieve polymorphism. See the
LiquidityPoolgeneric structure below for an example.
#![allow(unused)] fn main() { // polymorphic coin fee obtainment from liquidswap. /// Get fee for specific pool. public fun get_fee<X, Y, Curve>(): u64 acquires LiquidityPool { assert!(coin_helper::is_sorted<X, Y>(), ERR_WRONG_PAIR_ORDERING); assert!(exists<LiquidityPool<X, Y, Curve>>(@liquidswap_pool_account), ERR_POOL_DOES_NOT_EXIST); let pool = borrow_global<LiquidityPool<X, Y, Curve>>(@liquidswap_pool_account); pool.fee } }
Resources, References, and Mutation
- You can create a reference with
&and&mut. - You cannot use the
mutkeyword during assignment, .e.g.,let mut value = ...will not compile. - Global storage operators
move_to,move_from,borrow_global_mut,borrow_global, andexistsin Move enable reading from and writing to resources stored in the blockchain's global storage. - The
acquireskeyword is used to specify which resources a function acquires ownership of a resource during execution.
#![allow(unused)] fn main() { module collection::collection { use std::signer; struct Item has store, drop {} struct Collection has key, store { items: vector<Item> } public fun add_item(account: &signer) acquires Collection { let collection = borrow_global_mut<Collection>(signer::address_of(account)); vector::push_back(&mut collection.items, Item {}); } } }
Move allows the creation of read-only references to resources, ensuring that functions cannot modify them. Here's a small code snippet demonstrating the use of Move syntax:
Misc. syntax
- The
publickeyword in Move indicates that a function can be invoked from outside the current module. - The
entrykeyword is used to declare functions that can be called via the RPC. - The
nativekeyword is used to declare functions that are implemented in the blockchain runtime or in an external module. - There are VM specific directives; in Movement we will address
#[inline],#[view],#[test_only], and#[test].