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, and signer.
  • 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 as keyword.
  • signer represents the sender of a transaction and is used for access control and authentication.
  • signer can be converted to address with signer::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: The copy ability allows for the type's value to be copied.
  • drop: The drop ability enables the necessary cleanup actions when the type goes out of scope.
  • store: The store ability allows the type's value to be stored inside a struct in global storage.
  • key: The key ability 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 LiquidityPool generic 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 mut keyword during assignment, .e.g., let mut value = ... will not compile.
  • Global storage operators move_to, move_from, borrow_global_mut, borrow_global, and exists in Move enable reading from and writing to resources stored in the blockchain's global storage.
  • The acquires keyword 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 public keyword in Move indicates that a function can be invoked from outside the current module.
  • The entry keyword is used to declare functions that can be called via the RPC.
  • The native keyword 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].