Move Syntax Fundamentals
Move is a resource-oriented programming language designed for safe smart contract development. Sui uses Move with extensions for the object model, programmable transaction blocks, and the Sui-specific framework modules. This page covers the core language syntax. For the full language reference, see The Move Book.
What is a Move module?
A module is the basic unit of code organization in Move. Each module belongs to a package and contains struct definitions, function declarations, and constants. Modules are published onchain as part of a package and cannot be modified after publication (use package upgrades for updates). See The Move Book: Modules.
Which primitive types does Move have?
Move provides the following primitive types:
| Type | Description |
|---|---|
bool | Boolean (true or false) |
u8, u16, u32, u64, u128, u256 | Unsigned integers of various widths |
address | A 32-byte address value representing an account or object location on Sui |
See The Move Book: Primitive Types.
Structs
Structs define custom data types with named fields. On Sui, structs with the key ability represent objects and must have id: UID as their first field. See The Move Book: Structs.
What are Move abilities?
Abilities control what operations you can perform on a type. Every struct has a set of abilities declared at definition time:
| Ability | Grants |
|---|---|
key | The type can be stored as a Sui object (requires id: UID as first field) |
store | The type can be stored inside other objects or transferred using public_transfer |
copy | The type can be copied (duplicated in memory) |
drop | The type can be discarded when it goes out of scope |
A struct with key but without store must be explicitly transferred or destroyed. This is the foundation of resource safety in Move. See The Move Book: Abilities.
Functions
Functions contain the executable logic of your module. Move has 3 visibility levels:
public: Callable from any module. When you upgrade a package, the upgrade policy determines which types of changes are allowed to existing public functions (see package upgrades).public(package): Callable only from modules in the same package. As withpublicfunctions, the upgrade policy determines which types of changes are allowed.entry: Callable directly from a transaction but not from other Move modules.
Private functions (no visibility modifier) are callable only within the defining module. See The Move Book: Functions and Visibility Modifiers.
How do generics work in Move?
Generics let you write type-parameterized functions and structs. You declare type parameters in angle brackets and optionally constrain them with ability requirements. For example, <T: store + drop> declares a type parameter T that must have both the store and drop abilities. See The Move Book: Generics.
Control flow
Move supports if/else expressions, while loops, loop (infinite loop with break), and for loops (Move 2024 edition). The return keyword exits a function early. abort halts execution with an error code. See The Move Book: Control Flow.
Enums and pattern matching
Move 2024 edition introduces enums and match expressions. Enums define a type with multiple variants, each potentially holding different data. match expressions destructure enum values and execute variant-specific logic. See The Move Book: Enums and Match.
Vectors, options, and strings
- Vector (
vector<T>): A growable, ordered collection of elements of a single type. Usevector::push_backto add an element to the end,vector::pop_backto remove the last element, andvector::lengthto get the number of elements. See The Move Book: Vector. - Option (
Option<T>): Represents a value that might be absent. Useoption::some(value)andoption::none()to construct, andoption::is_some(),option::extract()to access. See The Move Book: Option. - String (
String): UTF-8 encoded text. Usestring::utf8(bytes)to create from a byte vector. See The Move Book: String.
Constants
Constants are module-level values that do not change. Declare them with const at the top of a module. Error constants (prefixed with E) are the convention for abort codes. See The Move Book: Constants.
References and ownership
Move uses ownership-based memory safety. Each value has exactly one owner. When you pass a value to a function, ownership transfers to that function. To avoid transferring ownership, pass a reference:
&T: Immutable reference (read-only access)&mut T: Mutable reference (read and write access)
To pass a reference when calling a function, borrow the value with &value for an immutable reference or &mut value for a mutable reference. References prevent copying or moving the original value while the reference is active. See The Move Book: References and Ownership and Scope.
Testing
Move supports inline unit tests using the #[test] attribute. Test functions run during sui move test and do not deploy onchain. Use #[test_only] for helper modules that exist only for testing. See The Move Book: Testing.
How do I destroy an object without the drop ability?
Every Sui object is a struct with the key ability and an id: UID first field. UID does not have the drop ability, so the Move compiler does not discard an object for you at the end of a function. To destroy the object, you unpack (destructure) the struct into its fields and then handle each field explicitly.
To destroy an object without drop:
- Write a
letbinding that names the struct type and lists its fields, for examplelet Test { id } = ...;. This consumes the struct and binds each field to a local variable. - Call
deleteon the extractedUID. Deleting aUIDrequires a call todeletebecauseUIDhas nodropability. - Use or return any other fields that the struct held.
The Sui framework uses this pattern in a test with an object that has only an id field:
public struct Test has key, store {
id: UID,
}
let Test { id } = ref.destroy();
id.delete();
When the struct has more fields than the ones you need, you can write .. in the pattern to skip the remaining fields. The Display object in the Sui framework destroys itself this way:
public(package) fun destroy<T: key>(display: Display<T>) {
let Display { id, .. } = display;
id.delete();
}
Unpacking also lets you return a field to the caller instead of deleting it. The destroy function in the borrow module unpacks a Referent and returns the value it held:
public fun destroy<T: key + store>(self: Referent<T>): T {
let Referent { id: _, value } = self;
value.destroy_some()
}