# The Move Book --- # The Move Book _by Damir Shamanaev, with contributions from the Move community_ This is The Move Book - a comprehensive guide to the Move programming language and the Sui blockchain. The book is intended for developers who are interested in learning about Move and building on Sui.
Move and the Sui framework keep evolving, and this book grows with them - new features bring new sections and updates to existing ones. If you have any feedback or suggestions, feel free to open an issue or a pull request on the [GitHub repository](https://github.com/MystenLabs/move-book).
> If you're looking for The Move Reference, you can find it [here](/reference). --- # Foreword This book is dedicated to Move, a smart contract language that captures the essence of safe programming with digital assets. Move is designed around the following values: 1. **Secure by default:** Insecure languages are a serious barrier both to accessible smart contract development and to mainstream adoption of digital assets. The first duty of a smart contract language is to prevent as many potential safety issues as possible (e.g. re-entrancy, missing access control checks, arithmetic overflow, ...) by construction. Any changes to Move should preserve or enhance its existing security guarantees. 2. **Expressive by nature:** Move must enable programmers to write any smart contract they can imagine. But we care as much about the way it _feels_ to write Move as we do about what Move allows you to do - the language should be rich enough that the features needed for a task are available, and minimal enough that the choice is obvious. The Move toolchain should be a productivity enhancer and a thought partner. 3. **Intuitive for all:** Smart contracts are only one part of a useful application. Move should understand the broader context of its usage and design with both the smart contract developer and the application developer in mind. It should be easy for developers to learn how to read Move-managed state, build Move powered transactions, and write new Move code. The core technical elements of Move are: - Safe, familiar, and flexible abstractions for digital assets via programmable _objects_. - A rich _ability_ system (inspired by linear types) that gives programmers extreme control of how values are created, destroyed, stored, copied, and transferred. - A _module_ system with strong encapsulation features to enable code reuse while maintaining this control. - _Dynamic fields_ for creating hierarchical relationships between objects. - _Programmable transaction blocks_ (PTBs) to enable atomic client-side composition of Move-powered APIs. Move was born in 2018 as part of Facebook's Libra project. It was publicly revealed in 2019; the first Move-powered network launched in 2020. As of April 2024, there are numerous Move-powered chains in production with several more in the works. Move is an embedded language with a platform-agnostic core, which means it takes on a slightly different personality in each chain that uses it. Creating a new programming language and bootstrapping a community around it is an ambitious, long term project. A language has to be an order of magnitude better than alternatives in relevant ways to have a chance, but even then the quality of the community matters more than the technical fundamentals. Move is a young language, but it's off to a good start in terms of both differentiation and community. A small, but fanatical group of smart contract programmers and core contributors united by the Move values are pushing the boundaries of what smart contracts can do, the applications they can enable, and who can (safely) write them. If that inspires you, read on! — Sam Blackshear, creator of Move --- # Before We Begin Move requires an environment to run and develop applications, and in this small chapter we will cover the prerequisites for the Move language: how to set up your IDE, how to install the compiler and what is Move 2024. If you are already familiar with these topics or have a CLI installed, you can skip this chapter and proceed to [the next one](../your-first-move/hello-world.md). --- # Install Sui Move is a compiled language, so you need to install a compiler to be able to write and run Move programs. The compiler is included into the Sui binary, which can be installed or downloaded using one of the methods below. ## Installing via suiup The best way to install Sui is by using [`suiup`](https://github.com/MystenLabs/suiup). It provides a simple way to install binaries and to manage different versions of binaries for different environments (e.g. `testnet` and `mainnet`). Installation instructions for `suiup` can be found [in the repository README](https://github.com/MystenLabs/suiup). To install Sui, run the following command: ```bash suiup install sui ``` ## Download Binary You can download the latest Sui binary from the [releases page](https://github.com/MystenLabs/sui/releases). The binary is available for macOS, Linux and Windows. For education purposes and development, we recommend using the `mainnet` version. ## Install Using Homebrew (MacOS) You can install Sui using the [Homebrew](https://brew.sh/) package manager. ```bash brew install sui ``` ## Install Using Chocolatey (Windows) You can install Sui using the [Chocolatey](https://chocolatey.org/install) package manager for Windows. ```bash choco install sui ``` ## Build Using Cargo (MacOS, Linux) You can install and build Sui locally by using the Cargo package manager (requires Rust) ```bash cargo install --git https://github.com/MystenLabs/sui.git sui --branch mainnet ``` Change the branch target here to `testnet` or `devnet` if you are targeting one of those. Make sure that your system has the latest Rust versions with the command below. ```bash rustup update stable ``` ## Troubleshooting For troubleshooting the installation process, please refer to the [Install Sui](https://docs.sui.io/guides/developer/getting-started/sui-install) Guide. --- # Install MVR [Move Registry (MVR)](https://moveregistry.com) is a package manager for Move. It allows anyone to publish and use published packages in new applications written in Move. Local binary allows searching packages in the registry as well as installing them as a part of the Sui CLI build process. ## Installing via suiup The best way to install MVR is by using [`suiup`](https://github.com/MystenLabs/suiup). Suiup provides an easy way to update and manage different versions of binaries. Installation instructions for `suiup` can be found [in the repository README](https://github.com/MystenLabs/suiup). To install Move Registry CLI, run the following command: ```bash suiup install mvr ``` After installation, Move Registry will be available as `mvr`. ## Download Binary You can download the latest MVR binary from the [releases page](https://github.com/MystenLabs/mvr/releases). The binary is available for macOS, Linux and Windows. Unlike [Sui](./install-sui.md), the MVR binary is not changing between environments and supports both `testnet` and `mainnet`. ## Install Using Cargo You can install and build MVR locally by using Cargo (requires Rust) ```bash cargo install --locked --git https://github.com/mystenlabs/mvr --branch release mvr ``` ## Troubleshooting For troubleshooting the installation process, please refer to the [Install MVR](https://docs.suins.io/move-registry/tooling/mvr-cli#installation) Guide. ## Using MVR To learn how to find packages in the registry and use them as dependencies in your projects, see the [Using Move Registry](./../guides/using-move-registry) guide. --- # Set Up Your IDE There are two most popular IDEs for Move development: VSCode and IntelliJ IDEA. Both of them provide basic features like syntax highlighting and error messages, though they differ in their additional features. Whatever IDE you choose, you'll need to use the terminal to run the [Move CLI](./install-sui.md). > **IntelliJ Plugin does not support Move 2024 edition, some syntax won't get highlighted.** ## VSCode - [VSCode](https://code.visualstudio.com/) is a free and open source IDE from Microsoft. - [Move (Extension)](https://marketplace.visualstudio.com/items?itemName=mysten.move) is a language server extension for Move maintained by [Mysten Labs](https://mystenlabs.com). - [Move Formatter](https://marketplace.visualstudio.com/items?itemName=mysten.prettier-move) - code formatter for Move, developed and maintained by [Mysten Labs](https://mystenlabs.com). - [Move Syntax](https://marketplace.visualstudio.com/items?itemName=damirka.move-syntax) a simple syntax highlighting extension for Move by [Damir Shamanaev](https://github.com/damirka/). ## IntelliJ IDEA - [IntelliJ IDEA](https://www.jetbrains.com/idea/) is a commercial IDE from JetBrains. - [Move Language Plugin](https://plugins.jetbrains.com/plugin/23301-sui-move-language) provides a Move on Sui language extension for IntelliJ IDEA by [MoveFuns](https://movefuns.org/). ## Emacs - [Emacs](https://www.gnu.org/software/emacs/) is a free and open source text editor. - [move-mode](https://github.com/amnn/move-mode) is a Move mode for Emacs by [Ashok Menon](https://github.com/amnn). ## Zed - [Zed](https://zed.dev/) is a next-generation code editor designed for high-performance collaboration with humans and AI. - [Move](https://github.com/Tzal3x/move-zed-extension) is a language server extension for Move maintained by [Tzal3x](https://github.com/Tzal3x). ## Github Codespaces The Web-based IDE from Github can be run right in the browser and provides almost a full-featured VSCode experience. - [Github Codespaces](https://github.com/features/codespaces) - [Move Syntax](https://marketplace.visualstudio.com/items?itemName=damirka.move-syntax) is also available in the extensions marketplace. - [Move Formatter](https://marketplace.visualstudio.com/items?itemName=mysten.prettier-move) is also available in the extensions marketplace. ## Other (CLI) Some of the tools listed above have CLI-supported versions. - [prettier-plugin-move](https://www.npmjs.com/package/@mysten/prettier-plugin-move) contains the TypeScript package for the Prettier@v3 plugin as well as the binary to run it in a terminal --- # Move 2024 Move 2024 is the current edition of the Move language maintained by Mysten Labs. All of the examples in this book are written in Move 2024. If you're used to the pre-2024 version of Move, refer to the [Move 2024 Migration Guide](./../guides/2024-migration-guide.md) to learn about the changes and improvements in the new edition. --- # Hello, World! In this chapter, you will learn how to create a new package, write a simple module, compile it, and run tests with the Move CLI. Make sure you have [installed Sui](./../before-we-begin/install-sui.md) and set up your [IDE environment](./../before-we-begin/ide-support.md). Run the command below to test if Sui has been installed correctly. ```bash # It should print the client version. E.g. sui-client 1.22.0-036299745. sui client --version ``` > Move CLI is a command-line interface for the Move language; it is built into the Sui binary and > provides a set of commands to manage packages, compile and test code. The structure of the chapter is as follows: - [Create a New Package](#create-a-new-package) - [Directory Structure](#directory-structure) - [Compiling the Package](#compiling-the-package) - [Running Tests](#running-tests) ## Create a New Package To create a new program, we will use the `sui move new` command followed by the name of the application. Our first program will be called `hello_world`. > Note: In this and other chapters, if you see code blocks with lines starting with `$` (dollar > sign), it means that the following command should be run in a terminal. The sign should not be > included. It's a common way of showing commands in terminal environments. ```bash $ sui move new hello_world ``` The `sui move` command gives access to the Move CLI - a built-in compiler, test runner and a utility for all things Move. The `new` command followed by the name of the package will create a new package in a new folder. In our case, the folder name is "hello_world". We can view the contents of the folder to see that the package was created successfully. ```bash $ ls -l hello_world Move.toml sources tests ``` ## Directory Structure Move CLI will create a scaffold of the application and pre-create the directory structure and all necessary files. Let's see what's inside. ```plaintext hello_world ├── Move.toml ├── sources │ └── hello_world.move └── tests └── hello_world_tests.move ``` ### Manifest The `Move.toml` file, known as the [package manifest](./../concepts/manifest.md), contains definitions and configuration settings for the package. It is used by the Move Compiler to manage package metadata, fetch dependencies, and register named addresses. We will explain it in detail in the [Concepts](./../concepts/index.md) chapter. > By default, the package features one named address - the name of the package. ```toml [addresses] hello_world = "0x0" ``` ### Sources The `sources/` directory contains the source files. Move source files have _.move_ extension, and are typically named after the module defined in the file. For example, in our case, the file name is _hello_world.move_ and the Move CLI has already placed commented out code inside: ```move /* /// Module: hello_world module hello_world::hello_world; */ ``` > The `/*` and `*/` are the comment delimiters in Move. Everything in between is ignored by the > compiler and can be used for documentation or notes. We explain all ways to comment the code in > the [Basic Syntax](./../move-basics/comments.md). The commented out code is a module definition, it starts with the keyword `module` followed by a named address (or an address literal), and the module name. The module name is a unique identifier for the module and has to be unique within the package. The module name is used to reference the module from other modules or transactions. ### Tests The `tests/` directory contains package tests. The compiler excludes these files in the regular build process but uses them in _test_ and _dev_ modes. The tests are written in Move and are marked with the `#[test]` attribute. Tests can be grouped in a separate module (then it's usually called _module_name_tests.move_), or inside the module they're testing. Modules, imports, constants and functions can be annotated with `#[test_only]`. This attribute is used to exclude modules, functions or imports from the build process. This is useful when you want to add helpers for your tests without including them in the code that will be published onchain. The _hello_world_tests.move_ file contains a commented out test module template: ```move /* #[test_only] module hello_world::hello_world_tests; // uncomment this line to import the module // use hello_world::hello_world; const ENotImplemented: u64 = 0; #[test] fun test_hello_world() { // pass } #[test, expected_failure(abort_code = hello_world::hello_world_tests::ENotImplemented)] fun test_hello_world_fail() { abort ENotImplemented } */ ``` ### Other Folders Additionally, Move CLI supports the `examples/` folder. The files there are treated similarly to the ones placed under the `tests/` folder - they're only built in the _test_ and _dev_ modes. They are to be examples of how to use the package or how to integrate it with other packages. The most popular use case is for documentation purposes and library packages. ## Compiling the Package Move is a compiled language, and as such, it requires the compilation of source files into Move Bytecode. It contains only necessary information about the module, its members, and types, and excludes comments and some identifiers (for example, for constants). To demonstrate these features, let's replace the contents of the _sources/hello_world.move_ file with the following: ```move /// The module `hello_world` under named address `hello_world`. /// The named address is set in the `Move.toml`. module hello_world::hello_world; // Imports the `String` type from the Standard Library use std::string::String; /// Returns the "Hello World!" as a `String`. public fun hello_world(): String { "Hello, World!" } ``` During compilation, the code is built, but not run. A compiled package only includes functions that can be called by other modules or in a transaction. We will explain these concepts in the [Concepts](./../concepts/index.md) chapter. But now, let's see what happens when we run the _sui move build_. ```bash # run from the `hello_world` folder $ sui move build # alternatively, if you didn't `cd` into it $ sui move build --path hello_world ``` It should output the following message on your console. ```plaintext UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING hello_world ``` During the compilation, Move Compiler automatically creates a build folder where it places all fetched and compiled dependencies as well as the bytecode for the modules of the current package. > If you're using a versioning system, such as Git, build folder should be ignored. For example, you > should use a `.gitignore` file and add `build` to it. ## Running Tests Before we get to testing, we should add a test. Move Compiler supports tests written in Move and provides the execution environment. The tests can be placed in both the source files and in the `tests/` folder. Tests are marked with the `#[test]` attribute and are automatically discovered by the compiler. We explain tests in depth in the [Testing](./../move-basics/testing.md) section. Replace the contents of the `tests/hello_world_tests.move` with the following content: ```move #[test_only] module hello_world::hello_world_tests; use std::unit_test::assert_eq; use hello_world::hello_world; #[test] fun test_hello_world() { assert_eq!(hello_world::hello_world(), "Hello, World!"); } ``` Here we import the `hello_world` module, and call its `hello_world` function to test that the output is indeed the string "Hello, World!". Now, that we have tests in place, let's compile the package in the test mode and run tests. Move CLI has the `test` command for this: ```bash $ sui move test ``` The output should be similar to the following: ```plaintext INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING hello_world Running Move unit tests [ PASS ] 0x0::hello_world_tests::test_hello_world Test result: OK. Total tests: 1; passed: 1; failed: 0 ``` If you're running the tests outside of the package folder, you can specify the path to the package: ```bash $ sui move test --path hello_world ``` You can also run a single or multiple tests at once by specifying a string. All the tests names containing the string will be run: ```bash $ sui move test test_hello ``` ## Next Steps In this section, we explained the basics of a Move package: its structure, the manifest, the build, and test flows. [On the next page](./hello-sui), we will write an application and see how the code is structured and what the language can do. ## Further Reading - [Package Manifest](./../concepts/manifest.md) section - Package in [The Move Reference](./../../reference/packages) --- # Hello, Sui! In the [previous section](./hello-world) we created a new package and demonstrated the basic flow of creating, building, and testing a Move package. In this section, we will write a simple application that uses the storage model and can be interacted with. To do this, we will create a simple todo list application. ## Create a New Package Following the same flow as in [Hello, World!](./hello-world), we will create a new package called `todo_list`. ```bash $ sui move new todo_list ``` ## Add the Code To speed things up and focus on the application logic, we will provide the code for the todo list application. Replace the contents of the _sources/todo_list.move_ file with the following code: > Note: while the contents may seem overwhelming at first, we will break it down in the following > sections. Try to focus on what's at hand right now. ```move /// Module: todo_list module todo_list::todo_list; use std::string::String; /// List of todos. Can be managed by the owner and shared with others. public struct TodoList has key, store { id: UID, items: vector } /// Create a new todo list. public fun new(ctx: &mut TxContext): TodoList { let list = TodoList { id: object::new(ctx), items: vector[] }; (list) } /// Add a new todo item to the list. public fun add(list: &mut TodoList, item: String) { list.items.push_back(item); } /// Remove a todo item from the list by index. public fun remove(list: &mut TodoList, index: u64): String { list.items.remove(index) } /// Delete the list and the capability to manage it. public fun delete(list: TodoList) { let TodoList { id, items: _ } = list; id.delete(); } /// Get the number of items in the list. public fun length(list: &TodoList): u64 { list.items.length() } ``` ## Build the Package To make sure that we did everything correctly, let's build the package by running the `sui move build` command. If everything is correct, you should see the output similar to the following: ```bash $ sui move build UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING todo_list ``` If there are no errors following this output, you have successfully built the package. If there are errors, make sure that: - The code is copied correctly - The file name and the package name is correct There are not many other reasons for the code to fail at this stage. But if you are still having issues, try looking up the structure of the package in [this location](https://github.com/MystenLabs/move-book/tree/main/packages/todo_list). ## Set Up an Account > If you already have an account set up, you can skip this step. To publish and interact with the package, we need to set up an account. While developing, the best option for doing so is to run your own [Local Network](https://docs.sui.io/guides/developer/getting-started/local-network). For now you just need to run `RUST_LOG="off,sui_node=info" sui start --with-faucet --force-regenesis`. The Sui Local Network will run on port 9000 of your machine, so make sure that the port isn't being used by any other application. If you are doing it for the first time, you will need to create a new account. To do this, run the `sui client` command, then the CLI will prompt you with multiple questions. The answers are marked below with `>`: ```bash $ sui client Config file ["/path/to/home/.sui/sui_config/client.yaml"] doesn't exist, do you want to connect to a Sui Full node server [y/N]? > y Sui Full node server URL (Defaults to Sui Testnet if not specified) : > http://127.0.0.1:9000 Environment alias for [http://127.0.0.1:9000] : > localnet Select key scheme to generate keypair (0 for ed25519, 1 for secp256k1, 2: for secp256r1): > 0 ``` After you have answered the questions, the CLI will generate a new keypair and save it to the configuration file. You can now use this account to interact with the network. To check that we have the account set up correctly, run the `sui client active-address` command: ```bash $ sui client active-address 0x.... ``` The command will output the address of your account, it starts with `0x` followed by 64 characters. ## Requesting Coins In _devnet_ and _testnet_ environments, the CLI provides a way to request coins to your account, so you can interact with the network. To request coins, run the `sui client faucet` command: ```bash $ sui client faucet Request successful. It can take up to 1 minute to get the coin. Run sui client gas to check your gas coins. ``` After waiting a little bit, you can check that the Coin object was sent to your account by running the `sui client balance` command: ```bash $ sui client balance ╭────────────────────────────────────────╮ │ Balance of coins owned by this address │ ├────────────────────────────────────────┤ │ ╭──────────────────────────────────╮ │ │ │ coin balance (raw) balance │ │ │ ├──────────────────────────────────┤ │ │ │ Sui 1000000000 1.00 SUI │ │ │ ╰──────────────────────────────────╯ │ ╰────────────────────────────────────────╯ ``` Alternatively, you can query _objects_ owned by your account, by running the `sui client objects` command. The actual output will be different, because the object ID is unique, and so is digest, but the structure will be similar: ```bash $ sui client objects ╭───────────────────────────────────────────────────────────────────────────────────────╮ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ 0x4ea1303e4f5e2f65fc3709bc0fb70a3035fdd2d53dbcff33e026a50a742ce0de │ │ │ │ version │ 4 │ │ │ │ digest │ nA68oa8gab/CdIRw+240wze8u0P+sRe4vcisbENcR4U= │ │ │ │ objectType │ 0x0000..0002::coin::Coin │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ``` Now that we have the account set up and the coins in the account, we can interact with the network. We will start by publishing the package to the network. ## Publish To publish the package to the network, we will use the `sui client publish` command. The command will automatically build the package and use its bytecode to publish in a single transaction. > We are using the `--gas-budget` argument during publishing. It specifies how much gas we are > willing to spend on the transaction. We won't touch on this topic in this section, but it's > important to know that every transaction in Sui costs gas, and the gas is paid in SUI coins. > It is worth noting that `--gas-budget` is not a required parameter. When you do not set it, > there will be a default consumption limit. The `gas-budget` is specified in _MISTs_. 1 SUI equals 10^9 MISTs. For the sake of demonstration, we will use 100,000,000 MISTs, which is 0.1 SUI. ```bash # run this from the `todo_list` folder $ sui client publish --gas-budget 100000000 # alternatively, you can specify path to the package $ sui client publish --gas-budget 100000000 todo_list ``` The output of the publish command is rather lengthy, so we will show and explain it in parts. ```bash $ sui client publish --gas-budget 100000000 UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING todo_list Successfully verified dependencies onchain against source. Transaction Digest: GpcDV6JjjGQMRwHpEz582qsd5MpCYgSwrDAq1JXcpFjW ``` As you can see, when we run the `publish` command, the CLI first builds the package, then verifies the dependencies onchain, and finally publishes the package. The output of the command is the transaction digest, which is a unique identifier of the transaction and can be used to query the transaction status. ### Transaction Data The section titled `TransactionData` contains the information about the transaction we just sent. It features fields like `sender`, which is your address, the `gas_budget` set with the `--gas-budget` argument, and the Coin we used for payment. It also prints the Commands that were run by the CLI. In this example, the commands `Publish` and `TransferObject` were run - the latter transfers a special object `UpgradeCap` to the sender. ```table ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Transaction Data │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Sender: 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 │ │ Gas Owner: 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 │ │ Gas Budget: 100000000 MIST │ │ Gas Price: 1000 MIST │ │ Gas Payment: │ │ ┌── │ │ │ ID: 0x4ea1303e4f5e2f65fc3709bc0fb70a3035fdd2d53dbcff33e026a50a742ce0de │ │ │ Version: 7 │ │ │ Digest: AXYPnups8A5J6pkvLa6RekX2ye3qur66EZ88mEbaUDQ1 │ │ └── │ │ │ │ Transaction Kind: Programmable │ │ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Commands │ │ │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 0 Publish: │ │ │ │ ┌ │ │ │ │ │ Dependencies: │ │ │ │ │ 0x0000000000000000000000000000000000000000000000000000000000000001 │ │ │ │ │ 0x0000000000000000000000000000000000000000000000000000000000000002 │ │ │ │ └ │ │ │ │ │ │ │ │ 1 TransferObjects: │ │ │ │ ┌ │ │ │ │ │ Arguments: │ │ │ │ │ Result 0 │ │ │ │ │ Address: Input 0 │ │ │ │ └ │ │ │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` ### Transaction Effects Transaction Effects contains the status of the transaction, the changes that the transaction made to the state of the network and the objects involved in the transaction. ```table ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Transaction Effects │ ├───────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Digest: GpcDV6JjjGQMRwHpEz582qsd5MpCYgSwrDAq1JXcpFjW │ │ Status: Success │ │ Executed Epoch: 411 │ │ │ │ Created Objects: │ │ ┌── │ │ │ ID: 0x160f7856e13b27e5a025112f361370f4efc2c2659cb0023f1e99a8a84d1652f3 │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ Version: 8 │ │ │ Digest: 8y6bhwvQrGJHDckUZmj2HDAjfkyVqHohhvY1Fvzyj7ec │ │ └── │ │ ┌── │ │ │ ID: 0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe │ │ │ Owner: Immutable │ │ │ Version: 1 │ │ │ Digest: Ein91NF2hc3qC4XYoMUFMfin9U23xQmDAdEMSHLae7MK │ │ └── │ │ Mutated Objects: │ │ ┌── │ │ │ ID: 0x4ea1303e4f5e2f65fc3709bc0fb70a3035fdd2d53dbcff33e026a50a742ce0de │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ Version: 8 │ │ │ Digest: 7ydahjaM47Gyb33PB4qnW2ZAGqZvDuWScV6sWPiv7LTc │ │ └── │ │ Gas Object: │ ┌── │ │ │ ID: 0x4ea1303e4f5e2f65fc3709bc0fb70a3035fdd2d53dbcff33e026a50a742ce0de │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ Version: 8 │ │ │ Digest: 7ydahjaM47Gyb33PB4qnW2ZAGqZvDuWScV6sWPiv7LTc │ │ └── │ │ Gas Cost Summary: │ │ Storage Cost: 10404400 MIST │ │ Computation Cost: 1000000 MIST │ │ Storage Rebate: 978120 MIST │ │ Non-refundable Storage Fee: 9880 MIST │ │ │ Transaction Dependencies: │ │ 7Ukrc5GqdFqTA41wvWgreCdHn2vRLfgQ3YMFkdks72Vk │ │ 7d4amuHGhjtYKujEs9YkJARzNEn4mRbWWv3fn4cdKdyh │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` ### Events If there were any _events_ emitted, you would see them in this section. Our package does not use events, so the section is empty. ```table ╭─────────────────────────────╮ │ No transaction block events │ ╰─────────────────────────────╯ ``` ### Object Changes These are the changes to _objects_ that transaction has made. In our case, we have _created_ a new `UpgradeCap` object which is a special object that allows the sender to upgrade the package in the future, _mutated_ the Gas object, and _published_ a new package. Packages are also objects on Sui. ```table ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Object Changes │ ├──────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Created Objects: │ │ ┌── │ │ │ ObjectID: 0x160f7856e13b27e5a025112f361370f4efc2c2659cb0023f1e99a8a84d1652f3 │ │ │ Sender: 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ ObjectType: 0x2::package::UpgradeCap │ │ │ Version: 8 │ │ │ Digest: 8y6bhwvQrGJHDckUZmj2HDAjfkyVqHohhvY1Fvzyj7ec │ │ └── │ │ Mutated Objects: │ │ ┌── │ │ │ ObjectID: 0x4ea1303e4f5e2f65fc3709bc0fb70a3035fdd2d53dbcff33e026a50a742ce0de │ │ │ Sender: 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ ObjectType: 0x2::coin::Coin<0x2::sui::SUI> │ │ │ Version: 8 │ │ │ Digest: 7ydahjaM47Gyb33PB4qnW2ZAGqZvDuWScV6sWPiv7LTc │ │ └── │ │ Published Objects: │ │ ┌── │ │ │ PackageID: 0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe │ │ │ Version: 1 │ │ │ Digest: Ein91NF2hc3qC4XYoMUFMfin9U23xQmDAdEMSHLae7MK │ │ │ Modules: todo_list │ │ └── │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` ### Balance Changes This last section contains changes to SUI Coins, in our case, we have _spent_ around 0.015 SUI, which in MIST is 10,500,000. You can see it under the _amount_ field in the output. ```table ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Balance Changes │ ├───────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ┌── │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ CoinType: 0x2::sui::SUI │ │ │ Amount: -10426280 │ │ └── │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` ### Alternative Output It is possible to specify the `--json` flag during publishing to get the output in JSON format. This is useful if you want to parse the output programmatically or store it for later use. ```bash $ sui client publish --gas-budget 100000000 --json ``` ### Using the Results After the package is published onchain, we can interact with it. To do this, we need to find the address (object ID) of the package. It's under the `Published Objects` section of the `Object Changes` output. The address is unique for each package, so you will need to copy it from the output. In this example, the address is: ```table 0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe ``` Now that we have the address, we can interact with the package. In the next section, we will show how to interact with the package by sending transactions. ## Sending Transactions To demonstrate the interaction with the `todo_list` package, we will send a transaction to create a new list and add an item to it. Transactions are sent via the `sui client ptb` command, it allows using the [Transaction Blocks](./../concepts/what-is-a-transaction) at full capacity. The command may look big and complex, but we go through it step by step. ### Prepare the Variables Before we construct the command, let's store the values we will use in the transaction. Replace the `0x4....` with the address of the package you have published. And `MY_ADDRESS` variable will be automatically set to your address from the CLI output. ```bash $ export PACKAGE_ID=0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe $ export MY_ADDRESS=$(sui client active-address) ``` ### Building the Transaction in CLI Now to building an actual transaction. The transaction will consist of two parts: we will call the `new` function in the `todo_list` package to create a new list, and then we will transfer the list object to our account. The transaction will look like this: ```bash $ sui client ptb \ --gas-budget 100000000 \ --assign sender @$MY_ADDRESS \ --move-call $PACKAGE_ID::todo_list::new \ --assign list \ --transfer-objects "[list]" sender ``` In this command, we are using the `ptb` subcommand to build a transaction. Parameters that follow it define the actual commands and actions that the transaction will perform. The first two calls we make are utility calls to set the sender address to the command inputs and set the gas budget for the transaction. ```bash # sets the gas budget for the transaction --gas-budget 100000000 \n # registers a variable "sender=@..." --assign sender @$MY_ADDRESS \n ``` Then we perform the actual call to a function in the package. We use the `--move-call` followed by the package ID, the module name, and the function name. In this case, we are calling the `new` function in the `todo_list` package. ```bash # calls the "new" function in the "todo_list" package under the $PACKAGE_ID address --move-call $PACKAGE_ID::todo_list::new ``` The function that we defined actually returns a value, which we want to store. We use the `--assign` command to give a name to the returned value. In this case, we are calling it `list`. And then we transfer the object to our account. ```bash --move-call $PACKAGE_ID::todo_list::new \ # assigns the result of the "new" function to the "list" variable (from the previous step) --assign list \ # transfers the object to the sender --transfer-objects "[list]" sender ``` Once the command is constructed, you can run it in the terminal. If everything is correct, you should see the output similar to the one we had in previous sections. The output will contain the transaction digest, the transaction data, and the transaction effects. The section that we want to focus on is the "Object Changes". More specifically, the "Created Objects" part of it. It contains the object ID, the type and the version of the `TodoList` that you have created. We will use this object ID to interact with the list. ```bash ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Object Changes │ ├───────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Created Objects: │ │ ┌── │ │ │ ObjectID: 0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553 │ │ │ Sender: 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ ObjectType: 0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe::todo_list::TodoList │ │ │ Version: 22 │ │ │ Digest: HyWdUpjuhjLY38dLpg6KPHQ3bt4BqQAbdF5gB8HQdEqG │ │ └── │ │ Mutated Objects: │ │ ┌── │ │ │ ObjectID: 0xe5ddeb874a8d7ead328e9f2dd2ad8d25383ab40781a5f1aefa75600973b02bc4 │ │ │ Sender: 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 │ │ │ Owner: Account Address ( 0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1 ) │ │ │ ObjectType: 0x2::coin::Coin<0x2::sui::SUI> │ │ │ Version: 22 │ │ │ Digest: DiBrBMshDiD9cThpaEgpcYSF76uV4hCoE1qRyQ3rnYCB │ └── │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` In this example the object ID is `0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553`. And the owner should be your account address. We achieved this by transferring the object to the sender in the last command of the transaction. Another way to test that you have successfully created the list is to check the account objects. ```bash $ sui client objects ``` It should have an object that looks similar to this: ```table ╭ ... ╮ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ 0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553 │ │ │ │ version │ 22 │ │ │ │ digest │ /DUEiCLkaNSgzpZSq2vSV0auQQEQhyH9occq9grMBZM= │ │ │ │ objectType │ 0x468d..29fe::todo_list::TodoList │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ | ... | ``` ### Passing Objects to Functions The TodoList that we created in the previous step is an object that you can interact with as its owner. You can call functions defined in the `todo_list` module on this object. To demonstrate this, we will add an item to the list. First, we will add just one item, and in the second transaction we will add 3 and remove another one. Double check that you have variables set up [from the previous step](#prepare-the-variables), and then add one more variable for the list object. ```bash $ export LIST_ID=0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553 ``` Now we can construct the transaction to add an item to the list. The command will look like this: ```bash $ sui client ptb \ --gas-budget 100000000 \ --move-call $PACKAGE_ID::todo_list::add @$LIST_ID "'Finish the Hello, Sui chapter'" ``` In this command, we are calling the `add` function in the `todo_list` package. The function takes two arguments: the list object and the item to add. The item is a string, so we need to wrap it in single quotes. The command will add the item to the list. If everything is correct, you should see the output similar to the one we had in previous sections. Now you can check the list object to see if the item was added. ```bash $ sui client object $LIST_ID ``` The output should contain the item that you have added. The `items` field in the object will show the list of items. A JSON representation of the object can be obtained by adding the `--json` flag: ```bash $ sui client object $LIST_ID --json ``` ```json { "objectId": "0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553", "version": "24", "digest": "FGcXH8MGpMs5BdTnC62CQ3VLAwwexYg2id5DKU7Jr9aQ", "type": "0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe::todo_list::TodoList", "owner": { "AddressOwner": "0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1" }, "previousTransaction": "EJVK6FEHtfTdCuGkNsU1HcrmUBEN6H6jshfcptnw8Yt1", "storageRebate": "1558000", "content": { "dataType": "moveObject", "type": "0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe::todo_list::TodoList", "hasPublicTransfer": true, "fields": { "id": { "id": "0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553" }, "items": ["Finish the Hello, Sui chapter"] } } } ``` ### Chaining Commands You can chain multiple commands in a single transaction. This shows the power of Transaction Blocks! Using the same list object, we will add three more items and remove one. The command will look like this: ```bash $ sui client ptb \ --gas-budget 100000000 \ --move-call $PACKAGE_ID::todo_list::add @$LIST_ID "'Finish Concepts chapter'" \ --move-call $PACKAGE_ID::todo_list::add @$LIST_ID "'Read the Move Basics chapter'" \ --move-call $PACKAGE_ID::todo_list::add @$LIST_ID "'Learn about Object Model'" \ --move-call $PACKAGE_ID::todo_list::remove @$LIST_ID 0 ``` If previous commands were successful, this one should not be any different. You can check the list object to see if the items were added and removed. The JSON representation is a bit more readable! ```bash sui client object $LIST_ID --json ``` ```json { "objectId": "0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553", "version": "25", "digest": "EDTXDsteqPGAGu4zFAj5bbQGTkucWk4hhuUquk39enGA", "type": "0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe::todo_list::TodoList", "owner": { "AddressOwner": "0x091ef55506ad814920adcef32045f9078f2f6e9a72f4cf253a1e6274157380a1" }, "previousTransaction": "7SXLGBSh31jv8G7okQ9mEgnw5MnTfvzzHEHpWf3Sa9gY", "storageRebate": "1922800", "content": { "dataType": "moveObject", "type": "0x468daa33dfcb3e17162bbc8928f6ec73744bb08d838d1b6eb94eac99269b29fe::todo_list::TodoList", "hasPublicTransfer": true, "fields": { "id": { "id": "0x20e0bede16de8a728ab25e228816b9059b45ebea49c8ad384e044580b2d3e553" }, "items": [ "Finish Concepts chapter", "Read the Move Basics chapter", "Learn about Object Model" ] } } } ``` Commands don't have to be in the same package or operate on the same object. Within a single transaction block, you can interact with multiple packages and objects. This is a powerful feature that allows you to build complex interactions onchain! ## Conclusion In this guide, we have shown how to publish a package on the Move blockchain and interact with it using the Sui CLI. We have demonstrated how to create a new list object, add items to it, and remove them. We have also shown how to chain multiple commands in a single transaction block. This guide should give you a good starting point for building your own applications on the Sui blockchain! --- # Concepts In this chapter you will learn about the basic concepts of Sui and Move: what a package is and how to interact with it, what an account and a transaction are, and how data is stored on Sui. While this chapter is not a complete reference - refer to the [Sui Documentation](https://docs.sui.io) for that - it will give you a good understanding of the concepts required to write Move programs on Sui. --- # Package Move is a language for writing smart contracts - programs that are stored and run on the blockchain. A single program is organized into a package. A package is published on the blockchain and is identified by an [address](./address). A published package can be interacted with by sending [transactions](./what-is-a-transaction) calling its functions. It can also act as a dependency for other packages. > To create a new package, use the `sui move new` command. To learn more about the command, run > `sui move new --help`. A package consists of modules - separate scopes that contain functions, types, and other items. ``` package 0x... module a struct A1 fun hello_world() module b struct B1 fun hello_package() ``` ## Package Structure Locally, a package is a directory with a `Move.toml` file and a `sources` directory. The `Move.toml` file - called the "package manifest" - contains metadata about the package, and the `sources` directory contains the source code for the modules. A package usually looks like this: ``` sources/ my_module.move another_module.move ... tests/ ... examples/ using_my_module.move Move.toml ``` The `tests` directory is optional and contains tests for the package. Code placed into the `tests` directory is not published onchain and is only available in tests. The `examples` directory can be used for code examples, and is also not published onchain. ## Published Package During development, a package doesn't have an address yet, and `0x0` is used in its place. Once a package is published, it gets a single unique [address](./address) on the blockchain containing its modules' bytecode. A published package becomes _immutable_ and can be interacted with by sending transactions. ``` 0x... my_module: another_module: ``` While the published bytecode can never be changed, a package can be _upgraded_: an upgrade publishes a new version of the package at a new address, leaving the old version intact. We touch on the implications throughout the book: the [Package Upgrades](./../programmability/package-upgrades) section explains the mechanics, and the [Upgradeability Practices](./../guides/upgradeability-practices) guide covers how to design for upgrades. ## Further Reading - [Package Manifest](./manifest) - [Address](./address) - [Packages](./../../reference/packages) in the Move Reference. --- # Package Manifest The `Move.toml` is a manifest file that describes the [package](./packages) and its dependencies. It is written in [TOML](https://toml.io/en/) format and contains multiple sections, the most important of which are `[package]`, `[dependencies]` and `[addresses]`. ```toml [package] name = "my_project" edition = "2024" [dependencies] example = { git = "https://github.com/example/example.git", subdir = "path/to/package", rev = "framework/testnet" } ``` ## Sections ### Package The `[package]` section is used to describe the package. None of the fields in this section are published onchain, but they are used in tooling and release management; they also specify the Move edition for the compiler. - `name` - the name of the package when it is imported; - `edition` - the edition of the Move language; currently, the only valid value is `2024`; ### Dependencies The `[dependencies]` section is used to specify the dependencies of the project. Each dependency is specified as a key-value pair, where the key is the name of the dependency, and the value is the dependency specification. The dependency specification can be a git repository URL or a path to the local directory. ```toml # git repository example = { git = "https://github.com/example/example.git", subdir = "path/to/package", rev = "framework/testnet" } # local directory my_package = { local = "../my-package" } ``` Packages also import named addresses from their dependencies. For example, the Sui dependency adds the `std` and `sui` addresses to the project, usable in the code in place of the full `0x1` and `0x2` addresses. Starting with version 1.45 of the Sui CLI, the Sui system packages (`std`, `sui`, `system`, `bridge`, and `deepbook`) are automatically added as dependencies if none of them are explicitly listed. ### Resolving Version Conflicts with Override Sometimes dependencies have conflicting versions of the same package. For example, if you have two dependencies that use different versions of the Example package, you can override the dependency in the `[dependencies]` section. To do so, add the `override` field to the dependency. The version of the dependency specified in the `[dependencies]` section will be used instead of the one specified in the dependency itself. ```toml [dependencies] example = { override = true, git = "https://github.com/example/example.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } ``` ## TOML Styles The TOML format supports two styles for tables: inline and multiline. The examples above are using the inline style, but it is also possible to use the multiline style. You wouldn't want to use it for the `[package]` section, but it can be useful for the dependencies. ```toml # Inline style [dependencies] example = { override = true, git = "https://github.com/example/example.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } MyPackage = { local = "../my-package" } ``` ```toml # Multiline style [dependencies.example] override = true git = "https://github.com/example/example.git" subdir = "crates/sui-framework/packages/sui-framework" rev = "framework/testnet" [dependencies.my_package] local = "../my-package" ``` ## Further Reading - [Move Package Management](https://docs.sui.io/develop/manage-packages/move-package-management) in the Sui Docs. - [Packages](./../../reference/packages) in the Move Reference. --- # Address An address is a unique identifier of a location on the blockchain. It is used to identify [packages](./packages), [accounts](./what-is-an-account), and [objects](./../object/object-model). An address has a fixed size of 32 bytes and is usually represented as a hexadecimal string prefixed with `0x`. Addresses are case insensitive. ```move 0xe51ff5cd221a81c3d6e22b9e670ddf99004d71de4f769b0312b68c7c4872e2f1 ``` The address above is an example of a valid address. It is 64 characters long (32 bytes) and prefixed with `0x`. Sui also has reserved addresses that are used to identify standard packages and objects. Reserved addresses are typically simple values that are easy to remember and type. For example, the address of the Standard Library is `0x1`. Addresses shorter than 32 bytes are padded with zeros to the left. ```move 0x1 = 0x0000000000000000000000000000000000000000000000000000000000000001 ``` Here are some examples of reserved addresses: - `0x1` - address of the Move Standard Library (alias `std`) - `0x2` - address of the Sui Framework (alias `sui`) - `0x6` - address of the system `Clock` object > You can find all reserved addresses in > [Appendix B: Reserved Addresses](../appendix/reserved-addresses). ## Further Reading - [Address type](../move-basics/address) in Move - [sui::address module](https://docs.sui.io/references/framework/sui/address) --- # Account An account is a way to identify a user. An account is generated from a private key, and is identified by an address. An account can own objects, and can send transactions. Every transaction has a sender, and the sender is identified by an [address](./address). An account does not need to be created or registered anywhere: it exists as soon as a keypair is generated, and any valid address can receive objects without prior setup. There is no onchain record of "all accounts" - an address with no objects and no transaction history is indistinguishable from one that was never used. Sui supports multiple signature schemes for accounts: ed25519, ECDSA (over the secp256k1 and secp256r1 curves), passkeys (device authenticators such as Face ID, Touch ID, or a hardware security key, based on the WebAuthn standard), multisig (an account controlled by a combination of keys), and zkLogin, which derives an account from a Web2 login. This _cryptographic agility_ gives Sui unusual flexibility in how accounts are created and controlled. ## Further Reading - [Cryptography in Sui](https://blog.sui.io/wallet-cryptography-specifications/) in the [Sui Blog](https://blog.sui.io) - [Keys and Addresses](https://docs.sui.io/guides/developer/transactions/transaction-auth/auth-overview) in the [Sui Docs](https://docs.sui.io) - [Signatures](https://docs.sui.io/guides/developer/cryptography/signing) in the [Sui Docs](https://docs.sui.io) - [Passkey](https://docs.sui.io/develop/cryptography/passkeys) in the [Sui Docs](https://docs.sui.io) --- # Transaction A transaction is the fundamental way to interact with a blockchain. Transactions are used to change the state of the blockchain, and they are the only way to do so. On Sui, a transaction can call functions in published packages, deploy new packages, and upgrade existing ones. ## Transaction Structure > Every transaction explicitly specifies the objects it operates on! Transactions consist of: - a sender - the [account](./what-is-an-account) that _signs_ the transaction; - a list (or a chain) of commands - the operations to be executed; - command inputs - the arguments for the commands: either `pure` - simple values like numbers or strings, or `object` - objects that the transaction will access; - a gas object - the `Coin` object used to pay for the transaction; - a gas price and budget - the cost of the transaction. ## Inputs Transaction inputs are the arguments for the transaction, and come in two types: - Pure arguments: These are mostly [primitive types](../move-basics/primitive-types) with some extra additions. A pure argument can be: - [`bool`](../move-basics/primitive-types#booleans). - [unsigned integer](../move-basics/primitive-types#integer-types) (`u8`, `u16`, `u32`, `u64`, `u128`, `u256`). - [`address`](../move-basics/address). - [`std::string::String`](../move-basics/string), UTF8 strings. - [`std::ascii::String`](../move-basics/string#ascii-strings), ASCII strings. - [`vector`](../move-basics/vector), where `T` is a pure type. - [`std::option::Option`](../move-basics/option), where `T` is a pure type. - [`sui::object::ID`](../storage/uid-and-id), typically points to an object. See also [What is an Object](../object/object-model). - Object arguments: These are objects or references of objects that the transaction will access. An object argument needs to be either a shared object, a frozen object, or an object that the transaction sender owns for the transaction to be successful. For more see [Object Model](../object). ## Commands Sui transactions may consist of multiple commands. Each command is a single built-in command (like publishing a package) or a call to a function in an already published package. The commands are executed in the order they are listed in the transaction, and they can use the results of the previous commands, forming a chain. Transaction either succeeds or fails as a whole. Any [`public`](../move-basics/visibility#public-visibility) function can be called as a command: making a function `public` is all it takes for users to call it in a transaction, and it is the default way to expose functionality in Move. (There is also the [`entry`](../move-basics/visibility#entry-modifier) modifier, which creates functions callable _only_ as transaction commands - a deliberately restricted option, covered in the [Entry Functions](../move-advanced/entry-functions) section.) Schematically, a transaction looks like this (in pseudo-code): ``` Inputs: - sender = 0xa11ce Commands: - payment = SplitCoins(Gas, [ 1000 ]) - item = MoveCall(0xAAA::market::purchase, [ payment ]) - TransferObjects(item, sender) ``` In this example, the transaction consists of three commands: 1. `SplitCoins` - a built-in command that splits a new coin from the passed object, in this case, the `Gas` object; 2. `MoveCall` - a command that calls a function `purchase` in a package `0xAAA`, module `market` with the given arguments - the `payment` object; 3. `TransferObjects` - a built-in command that transfers the object to the recipient. ## Transaction Effects Transaction effects are the changes that a transaction makes to the blockchain state. More specifically, a transaction can change the state in the following ways: - use the gas object to pay for the transaction; - create, update, or delete objects; - emit events; The result of the executed transaction consists of different parts: - Transaction Digest - the hash of the transaction which is used to identify the transaction; - Transaction Data - the inputs, commands and gas object used in the transaction; - Transaction Effects - the status and the "effects" of the transaction, more specifically: the status of the transaction, updates to objects and their new versions, the gas object used, the gas cost of the transaction, and the events emitted by the transaction; - Events - the custom [events](./../programmability/events) emitted by the transaction; - Object Changes - the changes made to the objects, including the _change of ownership_; - Balance Changes - the changes made to the aggregate balances of the account involved in the transaction. ## Further Reading - [Transactions](https://docs.sui.io/concepts/transactions) in the Sui Documentation. - [Programmable Transaction Blocks](https://docs.sui.io/concepts/transactions/prog-txn-blocks) in the Sui Documentation. - [Using Address Balances](https://docs.sui.io/onchain-finance/asset-custody/address-balances/using-address-balances) in the Sui Documentation - paying gas and moving funds without a `Coin` object. --- # Move Basics This chapter covers the foundations of the Move language: the syntax, the type system, and the concepts that every Move program is built from. It focuses on the language itself and mostly sets the blockchain aside - everything here applies to any Move program, and the features specific to storage and Sui are covered right after, starting with the [Object Model](./../object/) chapter. The sections build on one another and are meant to be read in order: - **How code is organized:** [modules](./module), [comments](./comments), [primitive types](./primitive-types), the [address type](./address), [expressions](./expression), and [functions](./function). - **Defining custom types:** [structs](./struct), and the [ability system](./abilities-introduction) that controls what values of a type can do - starting with [drop](./drop-ability). - **Reusing existing code:** [imports](./importing-modules) and the [Standard Library](./standard-library) with its core types - [vector](./vector), [Option](./option), and [String](./string). - **Writing logic:** [control flow](./control-flow), [enums with pattern matching](./enum-and-match), [struct methods](./struct-methods), and [visibility modifiers](./visibility). - **The core of Move's safety story:** [ownership and scope](./ownership-and-scope), the [copy ability](./copy-ability), [constants](./constants) and [aborting execution](./assert-and-abort), and [references](./references). - **Abstraction tools:** [generics](./generics), [macro functions](./macros), [internal permits](./internal-permit), [type reflection](./type-reflection), and, finally, [testing](./testing). Every code sample in this chapter comes from a compiling, tested package. Most samples are excerpts placed inside test functions, so you can copy any of them into the package created in the [Hello World](./../your-first-move/hello-world) chapter and run them with `sui move test`. --- # Module A module is the base unit of code organization in Move. Modules are used to group and isolate code, and all members of the module are private to the module by default. This makes the module a boundary of trust: as later sections will show, only the module that defines a type can create, modify, and destroy its values. In this section you will learn how to define a module, declare its members, and access it from other modules. ## Module Declaration Modules are declared using the `module` keyword followed by the package address and the module name, separated by `::`, then a semicolon and the module body. The module name should be in `snake_case` - all lowercase letters with underscores between words. Module names must be unique in the package. Usually, a single file in the `sources/` folder contains a single module. The file name should match the module name - for example, a `donut_shop` module should be stored in the `donut_shop.move` file. You can read more about coding conventions in the [Coding Conventions](./../guides/code-quality-checklist) section. > If you need to declare more than one module in a file, you must use [Module Block](#module-block) > syntax. ```move // Module label. module book::my_module; // module body ``` ## Address and Named Address The module address can be specified in two ways: as an address _literal_ (which does not require the `@` prefix) or as a package name declared in the [Package Manifest](./../concepts/manifest). ```move module 0x0::address_literal { /* ... */ } module book::named_address { /* ... */ } ``` Package section in the Move.toml: ```toml [package] name = "book" edition = "2024" ``` ## Module Members Module members are declared inside the module body. To illustrate this, let's define a simple module with an import, a constant, a struct, and a function: ```move module book::my_module_with_members; // import - brings the `my_module` module into scope use book::my_module; // a constant - an immutable, module-private value const CONST: u8 = 0; // a struct - a custom data type public struct Struct {} // a function - a unit of executable code fun function() { /* function body */ } ``` Each member starts with its own keyword: `use` brings other modules into scope ([Importing Modules](./importing-modules)), `const` defines a value that never changes ([Constants](./constants)), `struct` declares a custom data type ([Struct](./struct)), and `fun` declares a function ([Function](./function)). Don't worry about the details yet - each of these has a dedicated section in this chapter; for now, it is enough to recognize the keywords and know that all of them live at the module level. ## Module Block The pre-2024 edition of Move required the body of the module to be a _module block_ - the contents of the module surrounded by curly braces `{}`. The block syntax is still supported, and the only reason to prefer it over the _label_ syntax shown above is declaring more than one module in a file - which is rarely needed, and not a recommended practice. ```move module book::my_block_module_with_members { // import use book::my_module; // a constant const CONST: u8 = 0; // a struct public struct Struct {} // method alias public use fun function as Struct.struct_fun; // function fun function(_: &Struct) { /* function body */ } } // module block allows multiple module definitions in the // same file but this is not a recommended practice module book::another_module_in_the_file { // ... } ``` ## Further Reading - [Modules](./../../reference/modules) in the Move Reference. --- # Comments Comments are a way to add notes or document your code. They are ignored by the compiler and don't result in Move bytecode. You can use comments to explain what your code does, add notes to yourself or other developers, temporarily remove a part of your code, or generate documentation. There are three types of comments in Move: line comments, block comments, and doc comments. ## Line Comment You can use a double slash `//` to comment out the rest of the line. Everything after `//` will be ignored by the compiler. ```move module book::comments_line; // let's add a note to everything! fun some_function_with_numbers() { let a = 10u8; // let b = 10 this line is commented and won't be executed let b = 5; // here comment is placed after code a + b; // result is 15, not 10! } ``` ## Block Comment Block comments are used to comment out a block of code. They start with `/*` and end with `*/`. Everything between `/*` and `*/` will be ignored by the compiler. You can use block comments to comment out a single line or multiple lines. You can even use them to comment out a part of a line. ```move module book::comments_block; fun /* you can comment everywhere */ go_wild() { /* here there everywhere */ let a = 10; let b = /* even here */ 10; /* and again */ a + b; } /* you can use it to remove certain expressions or definitions fun empty_commented_out() { } */ ``` This example is a bit extreme, but it shows all the ways that you can use block comments. ## Doc Comment Documentation comments are special comments that are used to generate documentation for your code. They are similar to line comments but start with three slashes `///` and are placed before the definition of the item they document - a module, a struct, a function, or a constant. ```move /// Module has documentation! module book::comments_doc; /// This is a 0x0 address constant! const AN_ADDRESS: address = @0x0; /// This is a struct! public struct AStruct { /// This is a field of a struct! a_field: u8, } /// This function does something! /// And it's documented! fun do_something() {} ``` Documentation tooling collects doc comments of the public members into reference pages - the [standard library and framework documentation](https://docs.sui.io/references/framework) linked throughout this book is generated exactly this way. A well-written doc comment states what the function does, and under which conditions it aborts. ## Whitespace Unlike some languages, whitespace (spaces, tabs, and newlines) has no impact on the meaning of the program. --- # Primitive Types Move is a statically typed language: every value has a type, known at compilation time. This section introduces the simplest of them - the built-in _primitive_ types: booleans and unsigned integers. Together with [addresses](./address), covered in the next section, they are the material every other type is built from. > The code samples in this chapter are excerpts: expressions like the ones below live inside a > function - usually a [test function](./testing) - in a module, which we omit for brevity. To try > a sample yourself, place it inside a `#[test]` function of the package created in the > [Hello World](./../your-first-move/hello-world) chapter and run `sui move test`. ## Variables and Assignment Variables are declared with the `let` keyword, and they are _immutable_ by default: once a value is assigned, it cannot be replaced. A variable that needs to change is declared with `let mut`, and only then can it be reassigned with the `=` operator: ```move // The type annotation is optional when it can be inferred. let x: bool = true; let y = 10u8; // A `mut` variable can be reassigned with the `=` operator. let mut z: u8 = 42; z = 43; ``` The type annotation - the `: u8` after the name - is optional wherever the compiler can infer the type from the value or from later use; writing it out is a matter of clarity, not necessity. A variable name can also be reused by declaring it again, which is called _shadowing_. Unlike reassignment, shadowing creates a new variable, so it works on immutable variables and can change the type: ```move let x: u8 = 42; // The new `x` replaces the previous one, and may // even have a different type. let x: u16 = (x as u16) + 1; ``` ## Booleans The `bool` type has exactly two values - the keywords `true` and `false` - and the compiler always infers it, so a `bool` never needs a type annotation. Booleans combine with the logical operators `&&` (and), `||` (or), and `!` (not), where `&&` and `||` short-circuit: the right-hand side is not evaluated if the left-hand side already decides the result. ```move // The type of a boolean is always inferred. let is_ready = true; let is_done = false; // Logical operators: `&&` (and), `||` (or), and `!` (not). let in_progress = is_ready && !is_done; ``` Booleans store flags and drive conditions - the `if` and `while` expressions covered in the [Control Flow](./control-flow) section. ## Integer Types Move has six integer types, differing only in size - and all of them _unsigned_: there are no negative integers in Move, and no dedicated signed types.
| Type | Size (bits) | Maximum Value | | ------ | ----------- | ------------------------------ | | `u8` | 8 | `255` | | `u16` | 16 | `65_535` | | `u32` | 32 | `4_294_967_295` | | `u64` | 64 | `18_446_744_073_709_551_615` | | `u128` | 128 | 2128 − 1 | | `u256` | 256 | 2256 − 1 |
The workhorse is `u64` - token amounts, sizes, and indices all use it. Integer literals are written in decimal (`42`), with optional underscores for readability (`1_000_000`), or in hexadecimal with the `0x` prefix (`0x2A`): ```move let small: u8 = 42; let medium: u16 = 1_000; // underscores improve readability let large: u256 = 100_000_000_000; let hex: u64 = 0x2A; // hexadecimal literal, 42 ``` While `true` and `false` are unambiguously booleans, a literal like `42` could be any of the six integer types. The compiler infers the type from how the value is used, defaulting to `u64`; when inference is not enough - or when being explicit reads better - the type can be given as an annotation or as a literal suffix: ```move // Both are equivalent. let x: u8 = 42; let x = 42u8; ``` ### Operations Move supports the standard arithmetic operations for integers: addition, subtraction, multiplication, division, and modulus (remainder). None of them can produce a value outside the range of the type - instead of wrapping around, the operation aborts:
| Syntax | Operation | Aborts If | | ------ | ------------------- | ---------------------------------------- | | + | addition | Result is too large for the integer type | | - | subtraction | Result is less than zero | | \* | multiplication | Result is too large for the integer type | | % | modulus (remainder) | The divisor is 0 | | / | truncating division | The divisor is 0 |
Division is _truncating_: there are no fractional values, and any remainder is discarded, so `7 / 2` is `3`. Integers can also be compared with `==`, `!=`, `<`, `>`, `<=`, and `>=`, producing a `bool`: ```move let a = 10u8; let b = 20u8; // Comparison produces a `bool`; the operands must be of the same type. let is_less = a < b; // true let is_equal = a == b; // false ``` In every operation and comparison, the types of the operands _must match_ - there is no implicit conversion between integer types, and adding a `u8` to a `u64` is a compilation error. To operate on different types, one of the operands has to be explicitly cast. > For more operations, including bitwise operations, refer to the > [Move Reference](./../../reference/primitive-types/integers#bitwise). ### Casting with `as` The `as` operator converts an integer from one type to another. Note that an expression with a cast often needs parentheses around it to prevent ambiguity: ```move let x: u8 = 42; let y: u16 = x as u16; let z = 2 * (x as u16); // ambiguity requires parentheses ``` Casting _up_ to a larger type always succeeds. Casting _down_ must fit: unlike languages that silently truncate the value, Move aborts when it is out of range: ```move let x: u16 = 300; let y = x as u8; // ABORTS! 300 does not fit into `u8` ``` A common use of upcasting is making room for an intermediate result that would not fit into the original type: ```move // The same values that would overflow `u8` arithmetic // fit comfortably once upcast to `u16`. let x: u8 = 255; let y: u8 = 255; let z: u16 = (x as u16) + ((y as u16) * 2); ``` ### Overflow and Underflow As the operations table shows, arithmetic in Move never wraps around. An operation whose result does not fit into the type - too large, or below zero - aborts at runtime: ```move let x = 255u8; let y = 1u8; let z = x + y; // ABORTS! The result does not fit into `u8` ``` This is a deliberate safety feature. Silent overflow is a classic source of smart contract bugs - a balance that wraps around to zero, or a check that passes because a value quietly became small. Move turns every such case into a loud failure that reverts the transaction. ## Further Reading - [Bool](./../../reference/primitive-types/bool) in the Move Reference. - [Integer](./../../reference/primitive-types/integers) in the Move Reference. - [std::u64](https://docs.sui.io/references/framework/std/u64) module documentation - every integer type has a helper module (`std::u8` through `std::u256`) with functions like `min`, `max`, `sqrt`, and more. --- # Address Type Move uses a special type called [address](./../concepts/address) to represent addresses - the 32-byte values that identify accounts, packages, and objects on the blockchain. In an expression, an address literal starts with the `@` symbol, followed either by a hexadecimal number or by an identifier: ```move // address literal let value: address = @0x1; // named address registered in Move.toml let value = @std; let other = @sui; ``` The hexadecimal number is interpreted as a 32-byte value, with the missing leading bytes filled with zeros - so `@0x2` is a shorthand for the address ending in `...0002`. The identifier is looked up in the [Move.toml](./../concepts/manifest) file and replaced with the corresponding address by the compiler; if it is not found there, compilation fails. > Some addresses are reserved by the system: for example, the [Standard Library](./standard-library) > lives at `0x1` and the Sui Framework at `0x2`. The full list is in > [Appendix B: Reserved Addresses](./../appendix/reserved-addresses). ## Conversion Sui Framework offers a set of helper functions to work with addresses. Given that the address type is a 32-byte value, it can be converted to a `u256` type and vice versa. It can also be converted to and from a `vector` type. > The examples below use the [vector](./vector) and [String](./string) types, which are covered > later in this chapter - for now, it is enough to know that a conversion to and from bytes and > text exists. Example: Convert an address to a `u256` type and back. ```move use sui::address; let addr_as_u256: u256 = @0x1.to_u256(); let addr = address::from_u256(addr_as_u256); ``` Example: Convert an address to a `vector` type and back. ```move use sui::address; let addr_as_u8: vector = @0x1.to_bytes(); let addr = address::from_bytes(addr_as_u8); ``` Example: Convert an address into a string. ```move use sui::address; use std::string::String; let addr_as_string: String = @0x1.to_string(); ``` ## Further Reading - [Address](./../../reference/primitive-types/address) in the Move Reference. - [sui::address](https://docs.sui.io/references/framework/sui/address) module documentation. --- # Expression In programming languages, an expression is a unit of code that returns a value. In Move, almost everything is an expression, with the sole exception of the `let` statement, which is a declaration. In this section, we cover the types of expressions and introduce the concept of scope. > Expressions are sequenced with semicolons `;`. If there's "no expression" after the semicolon, the > compiler will insert a _unit_ `()` - a value that represents an empty expression. ## Literals In the [Primitive Types](./primitive-types) section, we introduced the basic types of Move. And to illustrate them, we used literals. A literal is a notation for representing a fixed value in source code. Literals can be used to initialize variables or directly pass fixed values as arguments to functions. Move has the following literals: - Boolean values: `true` and `false` - Integer values: `0`, `1`, `123123` - Hexadecimal values: Numbers prefixed with 0x to represent integers, such as `0x0`, `0x1`, `0x123` - Byte vector values: Prefixed with `b`, such as `b"bytes_vector"` - Byte values: Hexadecimal literals prefixed with `x`, such as `x"0A"` - String values: Double-quoted text, such as `"hello"`. Unlike other literals, the type of a string literal is inferred from the context - it can be a `vector` or one of the two standard string types. Strings are covered in detail in the [String](./string) section. ```move let b = true; // true is a literal let n = 1000; // 1000 is a literal let h = 0x0A; // 0x0A is a literal let v = b"hello"; // b"hello" is a byte vector literal let x = x"0A"; // x"0A" is a byte vector literal let c = vector[1, 2, 3]; // vector[] is a vector literal let s: std::string::String = "hello"; // "hello" is a string literal ``` ## Operators Arithmetic, logical, and bitwise operators are used to perform operations on values. Since these operations produce values, they are considered expressions. The integer operators - and when they abort - are listed in the [Primitive Types](./primitive-types#operations) section. ```move let sum = 1 + 2; // 1 + 2 is an expression let sum = (1 + 2); // the same expression with parentheses let is_true = true && false; // true && false is an expression let is_true = (true && false); // the same expression with parentheses ``` ## Blocks A block is a sequence of statements and expressions enclosed in curly braces `{}`. It returns the value of the last expression in the block (note that this final expression must not have an ending semicolon). A block is an expression, so it can be used anywhere an expression is expected. ```move // block with an empty expression, however, the compiler will // insert an empty expression automatically: `let none = { () }` // let none = {}; // block with let statements and an expression. let sum = { let a = 1; let b = 2; a + b // last expression is the value of the block }; // block is an expression, so it can be used in an expression and // doesn't have to be assigned to a variable. { let a = 1; let b = 2; a + b; // not returned - semicolon. // compiler automatically inserts an empty expression `()` }; ``` A block also delimits _scope_: a variable declared inside a block exists only until the block's closing brace. What exactly happens to values when their scope ends is an important question in Move, and the [Ownership and Scope](./ownership-and-scope) section is devoted to it. ## Function Calls We go into detail about functions in the very next section - [Functions](./function). Here, it is enough to say that a function call is an expression: it calls a function and returns the value of the last expression in the function body, provided the last expression does not have a terminating semicolon. ```move fun add(a: u8, b: u8): u8 { a + b } #[test] fun some_other() { let sum = add(1, 2); // not returned due to the semicolon. // compiler automatically inserts an empty expression `()` as return value of the block } ``` ## Control Flow Expressions Control flow expressions are used to control the flow of the program. They are also expressions, so they return a value. We cover control flow expressions in the [Control Flow](./control-flow) section. Here's a very brief overview: ```move // if is an expression, so it returns a value; if there are 2 branches, // the types of the branches must match. if (bool_expr) expr1 else expr2; // while is an expression, but it returns `()`. while (bool_expr) { expr; }; // loop is an expression, but returns `()` as well. loop { expr; break }; ``` ## Further Reading - [Equality](./../../reference/equality) in the Move Reference. - [Control Flow](./../../reference/control-flow) in the Move Reference. --- # Functions Functions are the building blocks of Move programs. They are called from [user transactions](./../concepts/what-is-a-transaction) and from other functions and group executable code into reusable units. Functions can take arguments and return a value. They are declared with the `fun` keyword at the module level. Just like any other module member, by default they're private and can only be accessed from within the module; making them visible to other modules is the topic of the [Visibility Modifiers](./visibility) section, later in this chapter. ```move module book::math; #[test_only] use std::unit_test::assert_eq; /// Function takes two arguments of type `u64` and returns their sum. /// The `public` visibility modifier makes the function accessible from /// outside the module. public fun add(a: u64, b: u64): u64 { a + b } #[test] fun test_add() { let sum = add(1, 2); assert_eq!(sum, 3); } ``` In this example, we define a function `add` that takes two arguments of type `u64` and returns their sum. The `test_add` function, located in the same module, is a test function that calls `add`. The test uses the `assert_eq!` macro to compare the result of `add` with the expected value. If the two values differ, the execution is aborted automatically. ## Function Declaration > In Move, functions are typically named using the `snake_case` convention. This means function > names should be all lowercase, with words separated by underscores. Examples include > `do_something`, `add`, `get_balance`, `is_authorized`, and so on. A function is declared with the `fun` keyword followed by the function name (a valid Move identifier), a list of arguments in parentheses, and a return type. The function body is a [block](./expression#blocks), and, like in any block, the last expression without a semicolon is the function's return value. The `return` keyword allows returning early - it is covered with the other [control flow](./control-flow) expressions. ```move fun return_nothing() { // empty expression, function returns `()` } ``` ## Accessing Functions Just like other module members, functions can be imported and accessed using a path. The path consists of the module path and the function name, separated by ::. For example, if you have a function named `add` in the `math` module within the `book` package, its full path would be `book::math::add`. If the module has already been imported - imports are covered in the [Importing Modules](./importing-modules) section - you can access it directly as `math::add`, as in the following example: ```move module book::use_math; use book::math; fun call_add() { // function is called via the path let sum = math::add(1, 2); } ``` ## Multiple Return Values Move functions can return multiple values, which is particularly useful when you need to return more than one piece of data from a function. The return type is specified as a tuple of types, and the return value is provided as a tuple of expressions: ```move fun get_name_and_age(): (vector, u8) { ("John", 25) } ``` The result of a function call with a tuple return has to be unpacked into variables via the `let (tuple)` syntax: ```move // Tuple must be destructured to access its elements. // Name and age are declared as immutable variables. let (name, age) = get_name_and_age(); assert_eq!(name, "John"); assert_eq!(age, 25); ``` If any of the declared values need to be declared as mutable, the `mut` keyword is placed before the variable name: ```move // declare name as mutable, age as immutable let (mut name, age) = get_name_and_age(); ``` If some of the returned values are not needed, they can be ignored with the `_` symbol: ```move // ignore the name, only use the age let (_, age) = get_name_and_age(); ``` ## Further Reading - [Functions](./../../reference/functions) in the Move Reference. --- # Custom Types with Struct A _struct_ is a user-defined type that groups related values into a single unit, giving a name to both the group and each value inside it. If you are familiar with object-oriented languages, a struct is similar to an object's data attributes. Instead of passing around a loose title, artist, and release year, an application can define a `Record` type and handle all three as one value. Custom types are the backbone of a Move program: they describe the application's data, and - as later sections will show - the module that defines a type controls everything that can be done with its values. In this section we introduce the struct definition and how to use it. ## Defining a Struct To define a custom type, use the `public struct` keywords followed by the name of the type, and a block of fields. Each field is defined with the `field_name: field_type` syntax, and field definitions must be separated by commas. The fields can be of any type, including other structs. > Move does not support recursive structs, meaning a struct cannot contain itself as a field. ```move use std::string::String; /// A struct representing an artist. public struct Artist { /// The name of the artist. name: String, } /// A struct representing a music record. public struct Record { /// The title of the record. title: String, /// The artist of the record. Uses the `Artist` type. artist: Artist, /// The year the record was released. year: u16, /// Whether the record is a debut album. is_debut: bool, /// The edition of the record, if defined. edition: Option, } ``` In the example above, we define an `Artist` struct with a single field, and a `Record` struct with five fields. The `title` field is of type [`String`](./string), the `artist` field uses the custom `Artist` type we just defined, the `year` field is of type `u16`, the `is_debut` field is of type `bool`, and the `edition` field is of type [`Option`](./option) to represent that the edition is optional. The `String` type is not built into the language - it is defined in the [Standard Library](./standard-library) and brought into scope with the `use` statement at the top of the example; imports are covered in the [Importing Modules](./importing-modules) section. The angle brackets in `Option` denote a _type parameter_: `Option` is an `Option` that holds a `u16`. Type parameters are covered in the [Generics](./generics) section. > A struct definition can also declare _abilities_ - properties that relax the default restrictions > on values of the type. They are listed with the `has` keyword, either before the fields - > `public struct Foo has copy, drop { ... }` - or after them, terminated with a semicolon - > `public struct Foo { ... } has copy, drop;`. Abilities are introduced in the > [Abilities Introduction](./abilities-introduction) section. ## Creating an Instance We described the _definition_ of a struct. Now let's see how to create an instance of one. Creating an instance of a struct is called _packing_, and it is done with the `StructName { field1: value1, field2: value2, ... }` syntax. The fields can be set in any order, but all of them must be set - a struct cannot be partially initialized. > The examples on this page live inside a [test function](./testing) in the same module that defines > the structs - as we are about to see, structs can only be created and taken apart within their > module. The `assert_eq!` used throughout is a _macro_ - hence the `!` in the name - that compares > two values and fails if they differ; it is covered in the [Testing](./testing) section. ```move let mut artist = Artist { name: "The Beatles", }; ``` In the example above, we create an instance of the `Artist` struct and set the `name` field to the string "The Beatles". The value `"The Beatles"` is a _string literal_: the compiler sees that the `name` field expects a `String` and infers the type of the literal automatically. Strings are covered in more detail in the [String](./string) section. Move also offers a shorthand: if a local variable has the same name as the field, the field name can be given just once. This is called _field name punning_. ```move let name: String = "Queen"; // The local variable `name` has the same name as the field, so // instead of `Artist { name: name }` we can write: let queen = Artist { name }; ``` ## Accessing Fields To access the fields of a struct, use the `.` (dot) operator followed by the field name. Fields can be read, and, if the variable is declared as `mut`, assigned a new value. ```move // Read the `name` field of the `Artist` struct. assert_eq!(artist.name, "The Beatles"); // Mutate the `name` field. Requires `artist` to be declared as `mut`. artist.name = "Led Zeppelin"; // Check that the `name` field has been mutated. assert_eq!(artist.name, "Led Zeppelin"); ``` Accessing fields this way works only in the module that defines the struct. To understand why, let's take a closer look at struct visibility. ## Field Visibility As you may have noticed, every struct is declared with the `public` modifier - it is required, and declaring a struct without it is a compilation error. The `public` modifier makes the struct _type_ visible to other modules: it can be [imported](./importing-modules), used in type definitions, and in function signatures. However, the _contents_ of a struct always stay internal to the module that defines it. Unlike some languages, Move has no per-field visibility modifiers - there is no way to mark a field public. Outside of the defining module it is impossible to: - read or write the fields of a struct; - create ("pack") an instance of a struct; - destroy ("unpack") an instance of a struct. This is a feature, not a limitation. It means the module has full control over how its types are created, used, and destroyed, and no external code can violate the rules the module sets. In the [Object Model](./../object/) chapter, we show how this property is used to model assets and enforce guarantees on them. > Note that just because a struct field is not accessible from other modules does not mean its value > is confidential - it is always possible to read the contents of an onchain object from outside of > Move. You should never store unencrypted secrets inside of objects. ## Getters and Setters Because fields are only accessible inside the defining module, the module needs to expose public functions if other modules should read or update them. A function that returns the value of a field is conventionally called a _getter_, and a function that updates a field is called a _setter_. A getter typically takes a [reference](./references) to the struct and returns the field value: ```move /// Returns the name of the artist. A "getter" for the `name` field. public fun name(artist: &Artist): String { artist.name } ``` A setter takes a mutable reference to the struct and the new value: ```move /// Updates the name of the artist. A "setter" for the `name` field. public fun set_name(artist: &mut Artist, name: String) { artist.name = name; } ``` Both functions can then be called with the `.` operator, just like field access: ```move // Call the setter and then the getter defined above. artist.set_name("Pink Floyd"); assert_eq!(artist.name(), "Pink Floyd"); ``` Because these functions are `public`, any module that imports `Artist` can call them. Note the parentheses: `artist.name()` is a function call and works anywhere the function is visible, while the field access `artist.name` would not compile outside the defining module. > The `public fun` syntax defines a public function; functions are covered in detail in the > [Functions](./function) section. The `&` and `&mut` in the signatures are references - they allow > a function to read or modify a value without taking ownership of it. We cover them in the > [References](./references) section, and the dot-call syntax in the > [Struct Methods](./struct-methods) section. While getters are very common, setters are defined less often, and usually with extra checks. The choice of which functions to expose is what defines the interface of the type - the module decides what external code can and cannot do with its structs. ## Unpacking a Struct Structs are non-discardable by default: a struct value cannot simply be left behind at the end of a function - code that does so will not compile. Every created value must be used: either stored (for example, placed inside another struct, or kept in onchain storage, as shown in the [Using Objects](./../storage/) chapter) or _unpacked_. Unpacking a struct means deconstructing it into its fields, and it is the mirror image of packing: the `let` keyword, followed by the struct name and the field names to bind. ```move // Unpack the `Artist` struct, binding the value of the `name` // field to a new variable `name`. let Artist { name } = artist; ``` In the example above we unpack the `Artist` struct and create a new variable `name` with the value of the `name` field. The struct value no longer exists after this line - it has been broken up into its parts. If a field is not needed, it can be ignored by binding it to the underscore `_`. However, since the struct itself cannot be discarded, all of its fields must still be listed in the pattern: ```move // Unpack the `Artist` struct and ignore the `name` field. let Artist { name: _ } = queen; ``` For structs with many fields, listing every ignored field gets verbose. The `..` pattern - the _rest_ pattern - matches all of the remaining fields at once: ```move let record = Record { title: "Abbey Road", artist: Artist { name: "The Beatles" }, year: 1969, is_debut: false, edition: option::none(), }; // Unpack the `Record`, keeping `title` and `artist`, and // ignoring all of the other fields with `..`. let Record { title, artist, .. } = record; assert_eq!(title, "Abbey Road"); // The `artist` binding holds a non-discardable `Artist` value, // so it, in turn, must be unpacked as well. let Artist { name: _ } = artist; ``` In the example above, we pack a full `Record` - the `option::none()` call creates an empty `Option` value, see the [Option](./option) section - and then unpack it, keeping the `title` and `artist` fields and ignoring the rest with `..`. Note that ignoring a field - whether with `_` or `..` - discards its value, which is only allowed for values that can be discarded. Simple values like `String`, `u16`, and `bool` can be discarded freely, but `Artist` cannot - which is why the example unpacks the `artist` binding as well instead of ignoring it. Which values can be discarded and which cannot is determined by _abilities_, explained in the next sections - [Abilities Introduction](./abilities-introduction) and [Ability: Drop](./drop-ability). ## Positional Structs So far, every struct on this page had named fields. Move also supports _positional_ structs, whose fields have no names and are identified by their position instead. A positional struct is defined with parentheses instead of curly braces, and the definition has no body - it ends right after the field list: ```move /// The duration of a record: minutes and seconds. public struct Duration(u64, u64) ``` Abilities can be placed before or after the fields here as well; in the post-fix form they follow the parentheses: `public struct Duration(u64, u64) has copy, drop;`. Positional structs are packed and unpacked with parentheses as well, and their fields are accessed with the `.` operator followed by the field index, starting at zero: ```move // Pack a positional struct - parentheses instead of curly braces. let duration = Duration(3, 5); // Access the fields by their position, starting at 0. assert_eq!(duration.0, 3); assert_eq!(duration.1, 5); // Unpack the struct, binding each field by its position. let Duration(minutes, seconds) = duration; ``` Positional structs are a good fit when field names would not add anything to what the type name already says - typically in small wrapper types with one or two fields. All of the rules described on this page still apply to them: fields are only accessible within the defining module, and a value must be used - stored or unpacked. For structs with more fields, named fields are usually the better choice. ## Further Reading - [Structs](./../../reference/structs) in the Move Reference. --- # Abilities: Introduction Move has a unique type system in which each type declares what its values are allowed to do. In the [previous section](./struct), every instance of `Artist` and `Record` had to be used: stored, passed on, or unpacked - discarding a value, or copying it, was not an option. This is not accidental strictness. By default, a Move value can only be created, moved around, and taken apart; everything beyond that is a privilege the type must be granted explicitly. These privileges are called _abilities_. ## What are Abilities? Abilities are permissions on a type. They are declared as a part of the struct definition, and the compiler rejects any operation the type is not permitted to perform. An ability does not add any functionality to the type itself - it unlocks behavior that is otherwise a compile error. There are four abilities in Move. Two of them control what can happen to a value during execution: - `copy` - the value can be _duplicated_; - `drop` - the value can be _discarded_; and two control storage: - `key` - the value can be a unit of storage - on Sui, an _object_; - `store` - the value can be stored _inside_ other values in storage. This "deny by default" design is what allows Move types to model assets faithfully: a type without `copy` cannot be duplicated, and a type without `drop` cannot be lost - guarantees that a language with ordinary, freely copyable values cannot give. > Throughout the book you will see sections named `Ability: `, each covering one ability in > detail: how it works, and when to use it. ## Abilities Syntax Abilities are set in the struct definition using the `has` keyword followed by a comma-separated list of abilities: ```move /// This struct has the `copy` and `drop` abilities. public struct VeryAble has copy, drop { /// The fields must support the abilities of the struct: /// `u64` has `copy` and `drop` (and more). value: u64, } ``` The two declared abilities change how instances of `VeryAble` behave. Compare the following code to the pack-and-unpack ceremony from the [previous section](./struct): ```move let a = VeryAble { value: 10 }; // `copy`: `a` is copied into `b` - both are usable afterwards. let b = a; assert_eq!(a.value, b.value); // `drop`: neither value has to be stored or unpacked; both are // silently discarded at the end of the function. ``` Now, let's take a quick tour of all four abilities, one at a time. ## `drop`: Discarding Values The `drop` ability allows an instance to be _discarded_: assigned to an unused variable, ignored with the `_` wildcard, or simply left behind when the scope ends. In other words, `drop` makes a type behave the way values behave in most other programming languages. It belongs on types that represent plain _data_, and its absence protects types that represent _assets_. The [next section](./drop-ability) is dedicated to it. ## `copy`: Duplicating Values The `copy` ability allows an instance to be _duplicated_, implicitly by the compiler or explicitly with the `copy` keyword. All the primitive types - integers, `bool`, `address` - behave as if they have it. Note that `copy` almost always comes together with `drop`: a value that can be duplicated but not discarded would force every one of its copies to be used. The details are covered in the [Ability: Copy](./copy-ability) section. ## `key`: Objects and Storage The `key` ability marks a type as a _unit of storage_: an instance can be written to the blockchain state and later found by its unique identifier - its "key". On Sui, a struct with the `key` ability is called an _object_, and it is required to have an `id: UID` as its first field. Objects are the heart of the Sui programming model, and the whole [Object Model](./../object/) chapter is dedicated to them, followed by [Ability: Key](./../storage/key-ability) covering the ability itself. ## `store`: Storing Inside Objects The `store` ability allows an instance to be stored _inside_ other structs that end up in storage. While `key` makes a type a top-level record in the blockchain state, `store` permits a type to be a _part_ of one. It is explained in the [Ability: Store](./../storage/store-ability) section. ## Abilities Come from Fields An ability is a promise about the whole value, including its contents - so a struct can only be granted an ability that all of its field types support. A struct with `copy` requires every field to have `copy`, and the same holds for `drop` and `store`; `key` requires every field to have `store`. The compiler enforces this at the definition site, and the following code will not compile: ```move public struct NoAbilities {} public struct Wrapper has copy, drop { inner: NoAbilities, // ^ error! The struct was declared with the ability 'copy' // so all fields require the ability 'copy' } ``` > All of the built-in types except [references](./references) have the `copy`, `drop`, and `store` > abilities, and references have `copy` and `drop`. Container types like [`vector`](./vector) and > [`Option`](./option) support `copy`, `drop`, and `store` _conditionally_ - a vector can only be > copied if its elements can. ## No Abilities A struct without abilities cannot be discarded, copied, or stored in storage. We call such a struct a _Hot Potato_. A lighthearted name, but it is a good way to remember that a struct without abilities is like a hot potato - it can only be passed around and requires special handling. The Hot Potato is one of the most powerful patterns in Move, and we go into more detail about it in the [Hot Potato Pattern](./../programmability/hot-potato-pattern) chapter. ## Further Reading - [Type Abilities](./../../reference/abilities) in the Move Reference. --- # Abilities: Drop In most programming languages, doing nothing with a value is not a problem: an unused variable may trigger a warning at most, and is forgotten the moment it goes out of scope. In Move, as we saw in the [Struct](./struct#unpacking-a-struct) section, the default is the opposite: a struct value must be _used_ - stored somewhere, passed on, or unpacked - and a program that silently discards a value does not compile. The `drop` ability - the simplest ability of the four - is the opt-out from this rule. A struct with `drop` is allowed to be _ignored_ or _discarded_: bound to a variable that is never read, ignored with the `_` wildcard, or simply left behind when its scope ends. In other words, `drop` makes a Move type behave the way values behave in most other languages: ```move module book::drop_ability; /// This struct has the `drop` ability. public struct IgnoreMe has drop { a: u8, b: u8, } /// This struct does not have the `drop` ability. public struct NoDrop {} #[test] // Create an instance of the `IgnoreMe` struct and ignore it. // Even though we constructed the instance, we don't need to unpack it. fun test_ignore() { let no_drop = NoDrop {}; let _ = IgnoreMe { a: 1, b: 2 }; // no need to unpack // The value must be unpacked for the code to compile. let NoDrop {} = no_drop; // OK } ``` In the example above, the `IgnoreMe` instance is assigned to `_` and never unpacked - the code compiles because `IgnoreMe` has the `drop` ability. The `NoDrop` instance cannot be treated this way: the only two options are to keep it or to unpack it, and the test unpacks it in the last line. > The `drop` ability only permits _discarding_ a value. It does not permit copying it or storing > it - those are governed by the separate [`copy`](./copy-ability) and > [`store`](./../storage/store-ability) abilities. ## When to Use `drop` A good rule of thumb: `drop` belongs on types that represent _data_, and its absence protects types that represent _assets_ or _obligations_. Configuration values, metadata, intermediate results of a computation - none of these are worth protecting, and forcing the programmer to explicitly destroy each one would be pure ceremony. Giving such types the `drop` ability keeps the code clean. Collection types are a good example: because `vector` has `drop` (when its contents do), a vector of numbers can simply be forgotten when it is no longer needed. The absence of `drop`, on the other hand, is one of the defining features of Move's type system. A coin, a ticket, a receipt, an obligation to repay - a value like this must never silently vanish, and a type without `drop` gives that guarantee at the compiler level: whoever holds the value is _forced_ to do something meaningful with it. The compiler-enforced handling of values is the foundation of the [Hot Potato pattern](./../programmability/hot-potato-pattern) mentioned in the [previous section](./abilities-introduction#no-abilities), and we explore the full rules of how values move between scopes in the [Ownership and Scope](./ownership-and-scope) section. > A struct with `drop` as its single ability is called a _Witness_. We explain the concept of a > _Witness_ in the [Witness and Abstract Implementation](./../programmability/witness-pattern) > section. ## Types with the `drop` Ability All native types in Move have the `drop` ability. This includes: - [`bool`](./../move-basics/primitive-types#booleans) - [unsigned integers](./../move-basics/primitive-types#integer-types) - [`vector`](./../move-basics/vector) when `T` has `drop` - [`address`](./../move-basics/address) All of the types defined in the standard library have the `drop` ability as well. This includes: - [`Option`](./../move-basics/option) when `T` has `drop` - [`String`](./../move-basics/string) - [`TypeName`](./../move-basics/type-reflection) Note the pattern in the list: a container type like `vector` or `Option` can only be dropped when its contents can. If the elements of a vector are protected from being discarded, the vector holding them is protected too - otherwise dropping the container would be a loophole for dropping the contents. ## Further Reading - [Type Abilities](./../../reference/abilities) in the Move Reference. --- # Importing Modules Move achieves high modularity and code reuse by allowing module imports. Modules within the same package can import each other, and a new package can depend on already existing packages and use their modules too. This section will cover the basics of importing modules and how to use them in your own code. ## Importing a Module Modules defined in the same package can import each other. The `use` keyword is followed by the module path, which consists of the package address (or alias) and the module name separated by `::`. ```move module book::module_one; /// Struct defined in the same module. public struct Character has drop {} /// Simple function that creates a new `Character` instance. public fun new(): Character { Character {} } ``` Another module defined in the same package can import the first module using the `use` keyword. ```move module book::module_two; use book::module_one; // importing module_one from the same package /// Calls the `new` function from the `module_one` module. public fun create_and_ignore() { let _ = module_one::new(); } ``` > Note: Any item (struct, function, constant, etc.) that you want to import from another module must > be marked with the `public` (or `public(package)` - see [visibility modifiers](./visibility)) > keyword to make it accessible outside its defining module. For example, the `Character` struct and > the `new` function in `module_one` are marked public so they can be used in `module_two`. ## Importing Members You can also import specific members from a module. This is useful when you only need a single function or a single type from a module. The syntax is the same as for importing a module, but you add the member name after the module path. ```move module book::more_imports; use book::module_one::new; // imports the `new` function from the `module_one` module use book::module_one::Character; // importing the `Character` struct from the `module_one` module /// Calls the `new` function from the `module_one` module. public fun create_character(): Character { new() } ``` ## Grouping Imports Imports can be grouped into a single `use` statement using curly braces `{}`. This allows for cleaner and more organized code when importing multiple members from the same module or package. ```move module book::grouped_imports; // imports the `new` function and the `Character` struct from // the `module_one` module use book::module_one::{new, Character}; /// Calls the `new` function from the `module_one` module. public fun create_character(): Character { new() } ``` Importing function names is less common in Move, since the function names can overlap and cause confusion. A recommended practice is to import the entire module and use the module path to access the function. Types have unique names and should be imported individually. To import both the module itself and some of its members in one group import, use the `Self` keyword, which stands for the module: ```move module book::self_imports; // imports the `Character` struct, and the `module_one` module use book::module_one::{Self, Character}; /// Calls the `new` function from the `module_one` module. public fun create_character(): Character { module_one::new() } ``` ## Resolving Name Conflicts When importing multiple members from different modules, it is possible to have name conflicts. For example, if you import two modules that both have a function with the same name, you will need to use the module path to access the function. It is also possible to have modules with the same name in different packages. To resolve the conflict and avoid ambiguity, Move offers the `as` keyword to rename the imported member. ```move module book::conflict_resolution; // `as` can be placed after any import, including group imports use book::module_one::{Self as mod, Character as Char}; /// Calls the `new` function from the `module_one` module. public fun create(): Char { mod::new() } ``` ## Adding an External Dependency Move packages can depend on other packages; the dependencies are listed in the [Package Manifest](./../concepts/manifest) file called `Move.toml`. Package dependencies are defined in the [Package Manifest](./../concepts/manifest) as follows: ```ini title="Move.toml" [dependencies] Example = { git = "https://github.com/Example/example.git", subdir = "path/to/package", rev = "v1.2.3" } Local = { local = "../my_other_package" } ``` The `dependencies` section contains an entry for each package dependency. The key of the entry is the name of the package (`Example` or `Local` in the example), and the value is either a git import table or a local path. The git import contains the URL of the package, the subdirectory where the package is located, and the revision of the package. The local path is a relative path to the package directory. The compiler automatically fetches (and later refetches) the listed dependencies when building the package, and all of their dependencies become available to your package as well. > Starting with version 1.45 of the sui CLI, the system packages are automatically included as > dependencies for all packages if they are not present in `Move.toml`. Therefore, `MoveStdlib`, > `Sui`, `System`, `Bridge`, and `Deepbook` are all available without an explicit import. ## Importing a Module from Another Package Normally, packages define their addresses in the `[addresses]` section. You can use aliases instead of full addresses. For example, instead of using `0x2::coin` to reference the Sui `coin` module, you can use `sui::coin`. The `sui` alias is defined in the Sui Framework package's manifest. Similarly, the `std` alias is defined in the Standard Library package and can be used instead of `0x1` to access standard library modules. To import a module from another package, use the `use` keyword followed by the module path. The module path consists of the package address (or alias) and the module name, separated by `::`. ```move module book::imports; use std::string; // std = 0x1, string is a module in the standard library use sui::coin; // sui = 0x2, coin is a module in the Sui Framework ``` > Note: Module address names come from the `[addresses]` section of the manifest file (`Move.toml`), > not the names used in the `[dependencies]` section. ## Further Reading - [Uses and Aliases](./../../reference/uses) in the Move Reference. --- # Standard Library The Move Standard Library provides functionality for native types and operations. It is a standard collection of modules that do not interact with storage, but provide basic tools for working with and manipulating data. It is the only dependency of the [Sui Framework](./../programmability/sui-framework), and is imported together with it. ## Most Common Modules In this book we go into detail about most of the modules in the Standard Library, however, it is also helpful to give an overview of the features, so that you can get a sense of what is available and which module implements it.
| Module | Description | Chapter | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------ | | [std::string](https://docs.sui.io/references/framework/std/string) | Provides basic string operations | [String](./string) | | [std::ascii](https://docs.sui.io/references/framework/std/ascii) | Provides basic ASCII operations | - | | [std::option](https://docs.sui.io/references/framework/std/option) | Implements `Option` | [Option](./option) | | [std::vector](https://docs.sui.io/references/framework/std/vector) | Native operations on the vector type | [Vector](./vector) | | [std::internal](https://docs.sui.io/references/framework/std/internal) | Provides the `Permit` type for module-authorized calls | [Internal Permit](./internal-permit) | | [std::bcs](https://docs.sui.io/references/framework/std/bcs) | Contains the `bcs::to_bytes()` function | [BCS](./../programmability/bcs) | | [std::address](https://docs.sui.io/references/framework/std/address) | Contains a single `address::length` function | [Address](./address) | | [std::type_name](https://docs.sui.io/references/framework/std/type_name) | Allows runtime _type reflection_ | [Type Reflection](./type-reflection) | | [std::hash](https://docs.sui.io/references/framework/std/hash) | Hashing functions: `sha2_256` and `sha3_256` | - | | [std::debug](https://docs.sui.io/references/framework/std/debug) | Contains debugging functions, which are available in only in **test** mode | - | | [std::unit_test](https://docs.sui.io/references/framework/std/unit_test) | The `assert_eq!` and `assert_ref_eq!` macros for **test** code | [Testing](./testing) | | [std::bit_vector](https://docs.sui.io/references/framework/std/bit_vector) | Provides operations on bit vectors | - | | [std::uq32_32](https://docs.sui.io/references/framework/std/uq32_32) | Fixed-point arithmetic: the `UQ32_32` type | - | | [std::uq64_64](https://docs.sui.io/references/framework/std/uq64_64) | Fixed-point arithmetic: the `UQ64_64` type | - | | [std::fixed_point32](https://docs.sui.io/references/framework/std/fixed_point32) | The `FixedPoint32` type; deprecated in favor of `std::uq32_32` | - |
## Integer Modules The Move Standard Library provides a set of functions associated with integer types. These functions are split into multiple modules, each associated with a specific integer type. The modules should not be imported directly, as their functions are available on every integer value. > All of the modules provide the same set of functions: `min`, `max`, `diff`, > `divide_and_round_up`, `sqrt`, `pow`, and `to_string`; checked conversions to smaller types - > `try_as_u8`, `try_as_u16`, and so on; and macros, such as `max_value!` and the iteration > helpers `do!` and `range_do!`.
| Module | Description | | -------------------------------------------------------------- | ----------------------------- | | [std::u8](https://docs.sui.io/references/framework/std/u8) | Functions for the `u8` type | | [std::u16](https://docs.sui.io/references/framework/std/u16) | Functions for the `u16` type | | [std::u32](https://docs.sui.io/references/framework/std/u32) | Functions for the `u32` type | | [std::u64](https://docs.sui.io/references/framework/std/u64) | Functions for the `u64` type | | [std::u128](https://docs.sui.io/references/framework/std/u128) | Functions for the `u128` type | | [std::u256](https://docs.sui.io/references/framework/std/u256) | Functions for the `u256` type |
## Exported Addresses The Standard Library exports a single named address - `std = 0x1`. This is where the `std` alias used throughout the book is defined. ## Implicit Imports Some modules are imported implicitly and are available in the module without the explicit `use` import. For the Standard Library, these modules and types include: - std::vector - std::option - std::option::Option - std::internal Note that `std::internal` is imported as a module, not a member: its members keep the module prefix, as in `internal::Permit` and `internal::permit()` - no `use` statement required. See the [Internal Permit](./internal-permit) section for how it is used. ## Importing std without Sui Framework The Move Standard Library can be imported to the package directly. However, `std` alone is not enough to build a meaningful application, as it does not provide any storage capabilities and can't interact with the onchain state. ```toml MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "framework/mainnet" } ``` ## Source Code The source code of the Move Standard Library is available in the [Sui repository](https://github.com/MystenLabs/sui/tree/main/crates/sui-framework/packages/move-stdlib/sources). --- # Vector A `vector` is the built-in way to store collections of elements in Move. It is an ordered, growable collection, similar to arrays or lists in other programming languages, and it is a building block for other types: the [`Option`](./option) and [`String`](./string) types introduced in the sections that follow are both backed by a vector. In this section, we introduce the `vector` type, its operations, and the macros that make working with it convenient. ## Vector Syntax The `vector` type is written using the `vector` keyword followed by the type of the elements in angle brackets. The type of the elements can be any valid Move type, including other vectors. Move also has a vector literal syntax that allows you to create vectors using the `vector` keyword followed by square brackets containing the elements (or no elements for an empty vector). ```move // An empty vector of bool elements. let empty: vector = vector[]; // A vector of u8 elements. let v: vector = vector[10, 20, 30]; // A vector of vector elements. let vv: vector> = vector[ vector[10, 20], vector[30, 40] ]; ``` The `vector` type is a built-in type in Move, and does not need to be imported from a module. Vector operations are defined in the `std::vector` module of the [Standard Library](./standard-library), which is implicitly imported and can be used directly without an explicit `use` statement. > In this section we call vector functions with the dot syntax, for example `v.length()` instead of > `vector::length(&v)`. This is the _receiver syntax_, available for standard library types out of > the box; we explain how it works in the [Struct Methods](./struct-methods) section. ## Reading Elements The most basic things to ask of a collection are its size and its elements. The `length` function returns the number of elements, `is_empty` tells whether there are none, and the index syntax `v[i]` accesses a single element. Indices start at zero, and accessing an index outside of bounds aborts execution: ```move let v: vector = vector[10, 20, 30]; // `length` returns the number of elements. assert_eq!(v.length(), 3); assert_eq!(v.is_empty(), false); // The index syntax borrows an element; for copyable // types the borrowed value can be read directly. assert_eq!(v[0], 10); // Accessing an index outside of bounds aborts: // v[3]; // ABORTS! ``` > The `v[i]` syntax is a shorthand for calling the `borrow` function - it yields a > [reference](./references) to the element, not the element itself. For copyable types, like the > integers above, the difference is invisible; for types that cannot be copied, taking an element > _out_ of a vector requires `pop_back`, `remove`, or `swap_remove` described below. The details of > this syntax are described in [Index Syntax](./../../reference/index-syntax) in the Move Reference. ## Adding and Removing Elements A mutable vector can grow and shrink. The most efficient operations work on the _end_ of the vector - `push_back` and `pop_back` - while `insert` and `remove` work at an arbitrary index and shift all of the elements after it: ```move let mut v = vector[10u8, 20, 30]; // `push_back` adds an element to the end of the vector. v.push_back(40); // [10, 20, 30, 40] // `pop_back` removes the last element and returns it. let last = v.pop_back(); // [10, 20, 30] assert_eq!(last, 40); // `insert` places an element at the given index, shifting // the elements after it to the right. v.insert(15, 1); // [10, 15, 20, 30] // `remove` takes an element out at the given index, shifting // the elements after it to the left. let removed = v.remove(2); // [10, 15, 30] assert_eq!(removed, 20); // The index syntax can also modify an element in place; the `&mut` // and `*` in this expression are explained in the References section. *(&mut v[0]) = 5; // [5, 15, 30] assert_eq!(v[0], 5); ``` The table below lists the most commonly used functions of the `std::vector` module; see the [module documentation][vector-stdlib] for the full list:
| Function | Description | Aborts If | | --------------- | -------------------------------------------------- | -------------------------- | | `length` | Returns the number of elements | - | | `is_empty` | Returns `true` if the vector has no elements | - | | `push_back` | Adds an element to the end | - | | `pop_back` | Removes and returns the last element | The vector is empty | | `insert` | Inserts an element at the index, shifting the rest | The index is out of bounds | | `remove` | Removes and returns the element at the index | The index is out of bounds | | `swap_remove` | Swaps the element with the last one and removes it | The index is out of bounds | | `swap` | Swaps the elements at two indices | An index is out of bounds | | `contains` | Returns `true` if the vector contains the element | - | | `index_of` | Returns `(true, index)` if the element is found | - | | `append` | Moves all elements from another vector to the end | - | | `reverse` | Reverses the order of the elements | - | | `destroy_empty` | Destroys an empty vector | The vector is not empty |
> Note that `remove` shifts every element after the removed one, which makes it more expensive the > longer the vector is. If the order of elements does not matter, `swap_remove` does the same job in > constant time. ## Vector Macros Reading, transforming, or aggregating every element of a vector is such a common task that the standard library provides a set of _macros_ for it. Macro names end with a `!` and take a _lambda_ (an inline function written as `|argument| expression`) which the macro applies to the elements. Under the hood a macro expands into a regular loop at compilation time, so using one costs nothing extra at runtime: ```move let v = vector[1u64, 2, 3, 4]; // `count!` returns the number of elements matching the condition. let even_count = v.count!(|n| *n % 2 == 0); assert_eq!(even_count, 2); // `map!` transforms each element, returning a new vector. let doubled = v.map!(|n| n * 2); assert_eq!(doubled, vector[2, 4, 6, 8]); // `fold!` collapses the vector into a single value, // in this case - the sum of all elements. let sum = v.fold!(0, |acc, n| acc + n); assert_eq!(sum, 10); // `do!` calls the function on each element of the vector. let mut total = 0u64; v.do!(|n| total = total + n); assert_eq!(total, 10); ``` Other commonly used macros include `filter!`, `any!`, `all!`, `find_index!`, and `tabulate!` - each of them replaces a hand-written loop with a single expressive line. The full list is available in the [module documentation][vector-stdlib], and macros in general are covered later in this chapter, in the [Macro Functions](./macros) section. ## Destroying a Vector of Non-Droppable Types The `vector` type inherits its [abilities](./abilities-introduction) from its elements: a `vector` can only be [dropped](./drop-ability) if `T` can. A vector of types without the `drop` ability cannot be ignored, even when it is empty, and the compiler requires an explicit call to the `destroy_empty` function: ```move /// A struct without `drop` ability. public struct NoDrop {} #[test] fun test_destroy_empty() { // Initialize a vector of `NoDrop` elements. let v = vector[]; // While we know that `v` is empty, we still need to call // the explicit `destroy_empty` function to discard the vector. v.destroy_empty(); } ``` The `destroy_empty` function will fail at runtime if you call it on a non-empty vector. This is the resource model at work: if the elements of a vector represent assets, neither the assets nor the vector holding them can silently disappear - every element must be taken out and handled before the vector itself is destroyed. ## Further Reading - [Vector](./../../reference/primitive-types/vector) in the Move Reference. - [Index Syntax](./../../reference/index-syntax) in the Move Reference. - [Macro Functions](./../../reference/functions/macros) in the Move Reference. - [std::vector][vector-stdlib] module documentation. [vector-stdlib]: https://docs.sui.io/references/framework/std/vector --- # Option Some data is optional by nature: a user may or may not have a middle name, a lookup may or may not find a match. Move has no `null` or `undefined` value - a variable of type `String` always holds a string - so the absence of a value has to be expressed some other way. A first instinct might be to reserve a special value as a marker: an empty string for a missing middle name, a zero for a missing number. This works - until an empty string becomes valid input, and every function has to remember which values are "real" and which are placeholders. The standard library offers a better tool: the `Option` type, a concept Move borrows from Rust. ## The Option Type `Option` is a wrapper around a value of type `Element`, and it is always in one of two states, conventionally called `Some` and `None`: - `Some` - the option contains a value; - `None` - the option is empty. An option cannot be mistaken for the value it wraps: an `Option` is not a `String`, and the value has to be checked for and taken out before it can be used. The possibility of absence becomes part of the type, visible in every signature, instead of a convention every caller must remember. `Option` is defined in the [Standard Library](./standard-library) and, like `vector`, is [implicitly imported](./standard-library#implicit-imports) - it can be used in any module without a `use` statement. The `Element` type parameter makes it [generic](./generics): the same definition serves `Option`, `Option`, and any other element type. Here is the user record from the problem above, with the optional field expressed as an `Option`: ```move module book::user_registry; use std::string::String; /// A struct representing a user record. public struct User has drop { first_name: String, middle_name: Option, last_name: String, } /// Create a new `User` struct with the given fields. public fun register( first_name: String, middle_name: Option, last_name: String, ): User { User { first_name, middle_name, last_name } } ``` The type of the `middle_name` field says exactly what the special-value approach could not: the value may be absent, and no `String` - empty or otherwise - is reserved as a marker. The two cases are constructed with `option::some(value)` and `option::none()`: ```move // A user with a middle name... let ada = register( "Ada", option::some("King"), "Lovelace", ); // ...and a user without one. No reserved values, no guesswork. let grace = register( "Grace", option::none(), "Hopper", ); ``` ## Creating and Using an Option Once created, an option can be checked for a value, read, and emptied: ```move // `option::some` creates an option holding a value. let mut opt: Option = option::some("Alice"); // `option::none` creates an empty option. The element type has to // be specified when it cannot be inferred from use. let empty: Option = option::none(); // Checking the state of an option. assert_eq!(opt.is_some(), true); assert_eq!(empty.is_none(), true); // `borrow` reads the value without taking it out of the option. assert_ref_eq!(opt.borrow(), &"Alice"); // `extract` takes the value out, leaving the option empty. let inner = opt.extract(); assert_eq!(inner, "Alice"); assert_eq!(opt.is_none(), true); ``` > The `borrow` function yields a _reference_ to the value - a way to read it without taking it out > of the option. References are covered in the [References](./references#immutable-references) > section later in this chapter. The table below lists the most commonly used functions of the `std::option` module; see the [module documentation][option-stdlib] for the full list:
| Function | Description | Aborts If | | ---------------------- | ------------------------------------------------------ | ------------------------ | | `is_some` | Returns `true` if the option holds a value | - | | `is_none` | Returns `true` if the option is empty | - | | `contains` | Returns `true` if the option holds the given value | - | | `borrow` | Returns a reference to the value | The option is empty | | `borrow_mut` | Returns a mutable reference to the value | The option is empty | | `fill` | Places a value into an empty option | The option holds a value | | `extract` | Takes the value out, leaving the option empty | The option is empty | | `swap` | Replaces the value, returning the old one | The option is empty | | `destroy_some` | Destroys the option, returning the value | The option is empty | | `destroy_none` | Destroys an empty option | The option holds a value | | `destroy_with_default` | Destroys the option, returning the value or a default | - |
Like a `vector`, an `Option` inherits its abilities from the element type: an option of a non-[droppable](./drop-ability) type cannot be ignored, and must be destroyed explicitly with one of the `destroy_*` functions above. ## Option Macros Like the [vector macros](./vector#vector-macros), option macros replace the common check-then-extract sequences with a single expression: ```move // `destroy_or!` consumes the option, returning a default when empty. let value = option::some(10u8).destroy_or!(0); assert_eq!(value, 10); let missing = option::none().destroy_or!(0); assert_eq!(missing, 0); // `is_some_and!` tests the value against a condition. let is_big = option::some(10u8).is_some_and!(|n| *n > 5); assert_eq!(is_big, true); // `do!` runs the lambda only when there is a value. option::some(10u8).do!(|n| assert_eq!(n, 10)); ``` Other commonly used macros include `map!`, `filter!`, `extract_or!`, and `do_ref!` - the full list is available in the [module documentation][option-stdlib], and macros in general are covered later in this chapter, in the [Macro Functions](./macros) section. ## Under the Hood `Option` is defined as a struct with a single field: a `vector` of `Element`, which is always either empty (`None`) or holds exactly one value (`Some`): ```move module std::option; /// Abstraction of a value that may or may not be present. public struct Option has copy, drop, store { vec: vector } ``` > You might be surprised that `Option` is a struct containing a `vector` rather than an > [enum][enum-reference]. This is for historical reasons: `Option` was added to Move before the > language had support for enums. In Rust, where the type originates, `Option` _is_ an enum with > the `Some` and `None` _variants_ - Move keeps the terminology. The representation is an implementation detail: the functions and macros above cover regular use, and the `vec` field is never accessed directly. ## Further Reading - [std::option][option-stdlib] module documentation. [enum-reference]: ./../../reference/enums [option-stdlib]: https://docs.sui.io/references/framework/std/option --- # String While Move does not have a built-in type to represent strings, it does have two standard implementations for strings in the [Standard Library](./standard-library). The `std::string` module defines a `String` type and methods for UTF-8 encoded strings, and the second module, `std::ascii`, provides an ASCII `String` type and its methods. > Both types are named `String`, which may be confusing at first. When the distinction matters, we > refer to them by their module: `string::String` and `ascii::String`. In most application code, the > UTF-8 `string::String` is the type to use. ## Strings Are Bytes No matter which type of string you use, it is important to know that strings are just bytes. The wrappers provided by the `string` and `ascii` modules are just that: wrappers. They do provide safety checks and methods to work with strings, but at the end of the day, they are just vectors of bytes. ```move module book::custom_string; /// Anyone can implement a custom string-like type by wrapping a vector. public struct MyString { bytes: vector, } /// Implement a `from_bytes` function to convert a vector of bytes to a string. public fun from_bytes(bytes: vector): MyString { MyString { bytes } } /// Implement a `bytes` function to convert a string to a vector of bytes. public fun bytes(self: &MyString): &vector { &self.bytes } ``` Both standard string types follow this exact pattern - a struct holding a `vector`. What makes them different from a plain byte vector, and from each other, is the _guarantee_ they carry about the contents: - `ascii::String` guarantees that every byte is a valid ASCII character. ASCII is the oldest and simplest character encoding: it defines 128 characters - Latin letters, digits, and punctuation - and each character takes exactly one byte. - `string::String` guarantees that the bytes are valid UTF-8. UTF-8 is the modern standard encoding: it can represent any Unicode character - alphabets, hieroglyphs, emoji - using one to four bytes per character. UTF-8 is backward compatible with ASCII: every ASCII string is also a valid UTF-8 string, but not the other way around. ## String Literals A [literal](./expression#literals) is a value written directly in the source code. Move offers two syntaxes for writing strings: the string literal `"..."` and the byte string literal `b"..."`. The byte string always yields a `vector`, while the type of a string literal is _inferred_ from the context - it becomes whichever of the three byte-carrying types (`vector`, `string::String`, or `ascii::String`) the compiler expects in that spot: ```move // The type of a string literal is inferred from the context: // it can be a UTF-8 `String`... let hello: std::string::String = "Hello"; // ...an ASCII `String`... let ascii: std::ascii::String = "ASCII"; // ...or a plain vector of bytes. let bytes: vector = "Hello"; // A byte string literal always yields a `vector`. let bytes = b"Hello"; // So does a hex string literal: each pair of hex digits is one byte. let bytes: vector = x"48656C6C6F"; // "Hello" ``` The compiler also checks the contents of the literal against the expected type at compile time. A string literal used as an `ascii::String` must contain only ASCII characters, and the following code will not compile: ```move let s: std::ascii::String = "héllo"; // ^ error! 'é' is not a valid ASCII character ``` If the compiler cannot tell the type from the context, the literal defaults to `vector`, and a warning is emitted. This is also why a method cannot be called directly on a bare literal - `"Hello".to_string()` does not compile, because the compiler cannot infer the type of the literal before resolving the method. A `vector` is not yet a string - both string modules provide functions to convert bytes into strings at runtime, which we show below. ### Escape Sequences Some characters cannot be typed into a literal directly: a newline, a tab, or the `"` character itself, which would end the literal. Like most languages, Move uses the backslash `\` to _escape_ special characters. Arbitrary bytes can also be written as `\x` followed by two hex digits. ```move // Special characters are written with the `\` escape: `\n` - newline, // `\r` - carriage return, `\t` - tab, `\\` - backslash, `\"` - double // quote, and `\xHH` - a byte written as two hex digits. let escaped: std::string::String = "Quote: \"...\"\nNew line,\ttab, \\ and \x41 is 'A'"; ``` ## Working with UTF-8 Strings While there are two types of strings in the standard library, the `string` module should be considered the default. It has native implementations of many common operations, leveraging low-level, optimized runtime code for superior performance. In contrast, the `ascii` module is fully implemented in Move, relying on higher-level abstractions and making it less suitable for performance-critical tasks. ### Definition The `String` type in the `std::string` module is defined as follows: ```move module std::string; /// A `String` holds a sequence of bytes which is guaranteed to be in utf8 format. public struct String has copy, drop, store { bytes: vector, } ``` _See [full documentation for std::string][string-stdlib] module._ ### Creating a String A string literal, as shown above, is the most common way to create a `String`. Alternatively, an existing `vector` can be turned into a `String` at runtime with the `string::utf8` function, or its convenient alias `.to_string()` on the `vector` type. Both abort if the bytes are not valid UTF-8. ```move // the module is `std::string` and the type is `String` use std::string::{Self, String}; // strings are normally created using the `utf8` function // type declaration is not necessary, we put it here for clarity let hello: String = string::utf8(b"Hello"); // The `.to_string()` alias on the `vector` is more convenient let hello = b"Hello".to_string(); ``` > The Sui execution environment automatically converts byte vectors into `String` in transaction > inputs. As a result, in many cases, constructing a `String` directly within the > [transaction](./../concepts/what-is-a-transaction) is unnecessary. ### Common Operations The UTF-8 `String` provides a number of methods to work with strings. The most common operations on strings are: concatenation, slicing, searching, and getting the length. Additionally, for custom string operations, the `as_bytes()` method can be used to get the underlying byte vector. ```move let mut str: String = "Hello,"; let another: String = " World!"; // `append(String)` adds the content to the end of the string. str.append(another); assert_eq!(str, "Hello, World!"); // `substring(start, end)` copies a slice of the string. assert_eq!(str.substring(0, 5), "Hello"); // `index_of(&String)` returns the index of the first occurrence... assert_eq!(str.index_of(&"World"), 7); // ...or the length of the string if there is no occurrence. assert_eq!(str.index_of(&"Rust"), str.length()); // Strings can be compared with `==` and `!=`; the comparison is // done byte by byte. assert!(str == "Hello, World!"); // `length()` returns the number of bytes in the string. assert_eq!(str.length(), 13); // Methods can also be chained! Get the length of a substring. assert_eq!(str.substring(0, 5).length(), 5); // `is_empty()` returns true if the string is empty. assert_eq!(str.is_empty(), false); // `as_bytes()` returns the underlying byte vector for custom operations. let bytes: &vector = str.as_bytes(); ``` Note the behavior of `index_of` when there is no occurrence: instead of aborting or returning an `Option`, it returns the length of the string - an index just past the last byte. Also note what is _not_ on the list: Move has no string interpolation or formatting, and no way to split a string by a separator. Strings in a smart contract are typically stored and displayed, not parsed. > Older code may use the `sub_string` and `bytes` functions - they are deprecated aliases of > `substring` and `as_bytes`. ### Converting Numbers to Strings A common practical task is building a string out of numbers - for a name, a label, or an error message. Every unsigned integer type has a `to_string` method that converts the number into its decimal representation. ```move // Every unsigned integer type has a `to_string` method, which // converts the number into its decimal representation. assert_eq!(42u64.to_string(), "42"); assert_eq!(255u8.to_string(), "255"); assert_eq!(1000000u128.to_string(), "1000000"); ``` ### Safe UTF-8 Operations The default `utf8` method may abort if the bytes passed into it are not valid UTF-8. If you are not sure that the bytes you are passing are valid, you should use the `try_utf8` method instead. It returns an `Option`, which contains no value if the bytes are not valid UTF-8, and a string otherwise. > Hint: Functions with names starting with `try_*` typically return an `Option`. If the operation > succeeds, the result is wrapped in `Some`. If it fails, the function returns `None`. This naming > convention, commonly used in Move, is inspired by Rust. ```move // `try_utf8` returns `Some(String)` if the bytes are valid UTF-8... let hello = string::try_utf8(b"Hello"); assert_eq!(hello.is_some(), true); // ...and `None` if they are not. let invalid = string::try_utf8(b"\xFF"); assert_eq!(invalid.is_none(), true); // The `.try_to_string()` alias on `vector` does the same. let hello = b"Hello".try_to_string(); assert_eq!(hello.is_some(), true); ``` ### UTF-8 Limitations The `string` module does not provide a way to access individual characters in a string. This is because UTF-8 is a variable-length encoding, and the length of a character can be anywhere from 1 to 4 bytes. Similarly, the `length()` method returns the number of bytes in the string, not the number of characters. ```move // `length()` returns the number of bytes, not characters! let ascii_only: String = "hello"; // 5 characters, 5 bytes let accented: String = "héllo"; // 5 characters, 6 bytes let emoji: String = "🥳"; // 1 character, 4 bytes assert_eq!(ascii_only.length(), 5); assert_eq!(accented.length(), 6); assert_eq!(emoji.length(), 4); ``` Byte positions matter for methods that take indices, such as `substring` and `insert`. These methods validate character boundaries and abort if the specified index falls within the middle of a character: ```move #[test, expected_failure] fun test_substring_aborts_mid_character() { let s: std::string::String = "héllo"; // 'é' occupies bytes 1 and 2 - slicing through it aborts let _ = s.substring(0, 2); } ``` > One more consequence of "strings are bytes": two strings that look identical on screen may have > different byte representations. For example, "é" can be encoded as a single character or as "e" > followed by a combining accent mark - they render the same, but compare as different, because `==` > compares bytes, not what the reader sees. ## ASCII Strings The `ascii::String` type is a good fit for values that are known to be plain Latin letters, digits, and punctuation: tickers, symbols, identifiers, or URLs. For example, the [Sui Framework](./../programmability/sui-framework) uses `ascii::String` for the `symbol` field of the `CoinMetadata` type. What the ASCII encoding lacks in expressiveness, it makes up for in simplicity: every character is exactly one byte. This lifts the limitations of UTF-8 strings - `ascii::String` allows operating on individual characters (represented by the `ascii::Char` type), and offers methods that would be ambiguous for UTF-8, such as changing the case of a string. An ASCII string is created the same way as a UTF-8 one: with a string literal, or by converting a `vector` at runtime - this time with the `ascii::string` function or the `.to_ascii_string()` alias on `vector`. There is a `try_string` counterpart as well, following the same `try_*` convention described above. The two string types can be converted into one another. Since every ASCII string is also valid UTF-8, `to_string()` on an `ascii::String` always succeeds; the reverse conversion - `to_ascii()` - aborts if the string contains non-ASCII characters. ```move // The `.to_ascii_string()` alias on `vector` constructs an // `ascii::String`; it aborts if any byte is not valid ASCII. let hey = b"Hey".to_ascii_string(); // ASCII strings provide the same core operations as UTF-8 strings: // `length`, `append`, `insert`, `substring`, `index_of`, and so on. assert_eq!(hey.length(), 3); // As well as some unique ones, like changing the case... assert_eq!(hey.to_uppercase(), "HEY"); assert_eq!(hey.to_lowercase(), "hey"); // ...and checking if all characters are printable. assert_eq!(hey.all_characters_printable(), true); // An `ascii::String` can always be converted into a UTF-8 `String`, let hey_utf8 = hey.to_string(); // and a UTF-8 `String` - into ASCII, if its contents allow it. let hey_ascii = hey_utf8.to_ascii(); ``` _See [full documentation for std::ascii][ascii-stdlib] module._ ## Further Reading - [std::string][string-stdlib] module documentation. - [std::ascii][ascii-stdlib] module documentation. [string-stdlib]: https://docs.sui.io/references/framework/std/string [ascii-stdlib]: https://docs.sui.io/references/framework/std/ascii --- # Control Flow Control flow statements decide which code runs, how many times, and when to stop. They are used to make decisions, repeat a block of code, or exit a block of code early. Move includes the following control flow statements (explained in detail below): - [`if` and `if-else`](#conditional-statements) - making decisions on whether to execute a block of code - [`loop` and `while` loops](#repeating-statements-with-loops) - repeating a block of code - [`break` and `continue` statements](#exiting-a-loop-early) - exiting a loop early - [labeled control flow](#labeled-control-flow) - targeting an outer loop or block from a nested one - [`return`](#early-return) statement - exiting a function early ## Conditional Statements The `if` expression is used to make decisions in a program. It evaluates a [boolean](./primitive-types#booleans) expression and executes a block of code if the expression is true. Paired with `else`, it can execute a different block of code if the expression is false. The syntax for an `if` expression is: ```move if () ; if () else ; ``` Just like any other expression, `if` requires a semicolon if there are other expressions following it. The `else` keyword is optional, except when the resulting value is assigned to a variable, as all branches must return a value to ensure type safety. Let’s examine how an `if` expression works in Move with the following example: ```move #[test] fun test_if() { let x = 5; // `x > 0` is a boolean expression. if (x > 0) { let message: std::string::String = "X is bigger than 0"; std::debug::print(&message) }; } ``` Let's see how we can use `if` and `else` to assign a value to a variable: ```move #[test] fun test_if_else() { let x = 5; let y = if (x > 0) { 1 } else { 0 }; assert_eq!(y, 1); } ``` In this example, the value of the `if` expression is assigned to the variable `y`. If `x` is greater than 0, `y` is assigned the value 1; otherwise, it is assigned 0. The `else` block is required here because both branches of an `if` expression must have the same type. When the `else` is omitted, the false branch defaults to the unit value `()`, so assigning an `if` without an `else` to a variable is a type error: ```move let y = if (x > 0) 1; // ^^^^^^^^^^^^ ERROR! Expected 'u64', but found '()' - the missing // else branch defaults to the unit value `()`. ``` To choose between more than two branches, `if` expressions can be chained with `else if`. The branches are checked top to bottom, and the first one whose condition is true is taken: ```move // Returns a letter grade for a score from 0 to 100. fun grade(score: u8): vector { if (score >= 90) "A" else if (score >= 80) "B" else if (score >= 70) "C" else "F" } #[test] fun test_else_if() { assert_eq!(grade(95), "A"); assert_eq!(grade(82), "B"); assert_eq!(grade(40), "F"); } ``` Conditional expressions are among the most important control flow statements in Move. They evaluate user-provided input or stored data to make decisions. One key use case is in the [`assert!` macro](./assert-and-abort), which checks if a condition is true and aborts execution if it is not. We explore it in detail later in this chapter. ## Repeating Statements with Loops Loops are used to execute a block of code multiple times. Move has two built-in types of loops: `loop` and `while`. In many cases they can be used interchangeably, but usually `while` is used when the number of iterations is known in advance, and `loop` is used when the number of iterations is not known in advance or there are multiple exit points. Loops are useful for working with collections, such as vectors, or for repeating a block of code until a specific condition is met. However, take care to avoid infinite loops, which can exhaust gas limits and cause the transaction to abort. > In practice, hand-written loops are relatively rare in Move. Iterating over a collection is more > commonly expressed with the higher-level [macros](./macros) such as `do!`, `map!`, and `fold!`, > which are covered in the [Vector](./vector#vector-macros) chapter. The `loop` and `while` > constructs described here are the primitives those macros are built on, and remain the right tool > when the iteration does not fit a simple collection traversal. ## The `while` Loop The `while` statement executes a block of code repeatedly as long as the associated boolean expression evaluates to true. Just like we've seen with `if`, the boolean expression is evaluated before each iteration of the loop. Additionally, like conditional statements, the `while` loop is an expression and requires a semicolon if there are other expressions following it. The syntax for the `while` loop is: ```move while () { ; }; ``` Here is an example of a `while` loop with a very simple condition: ```move // This function iterates over the `x` variable until it reaches 10, the // return value is the number of iterations it took to reach 10. // // If `x` is 0, then the function will return 10. // If `x` is 5, then the function will return 5. fun while_loop(mut x: u8): u8 { let mut y = 0; // This will loop until `x` is 10. // And will never run if `x` is 10 or more. while (x < 10) { y = y + 1; x = x + 1; }; y } #[test] fun test_while() { assert_eq!(while_loop(0), 10); // 10 times assert_eq!(while_loop(5), 5); // 5 times assert_eq!(while_loop(10), 0); // loop never executed } ``` ## Infinite `loop` Now let's imagine a scenario where the boolean expression is always `true`. For example, if we literally passed `true` to the `while` condition. This is similar to how the `loop` statement functions, except that `while` evaluates a condition. ```move #[test, expected_failure(out_of_gas, location=Self)] fun test_infinite_while() { let mut x = 0; // This will loop forever. while (true) { x = x + 1; }; // This line will never be executed. assert_eq!(x, 5); } ``` An infinite `while` loop, or a `while` loop with an always `true` condition, is equivalent to a `loop`. The syntax for creating a `loop` is straightforward: ```move loop { ; }; ``` Let's rewrite the previous example using `loop` instead of `while`: ```move #[test, expected_failure(out_of_gas, location=Self)] fun test_infinite_loop() { let mut x = 0; // This will loop forever. loop { x = x + 1; }; // This line will never be executed. assert_eq!(x, 5); } ``` Infinite loops are rarely practical in Move, as every operation consumes gas, and an infinite loop will inevitably lead to gas exhaustion. If you find yourself using a loop, consider whether there might be a better approach, as many use cases can be handled more efficiently with other control flow structures. That said, `loop` might be useful when combined with `break` and `continue` statements to create controlled and flexible looping behavior. ## Exiting a Loop Early As we already mentioned, infinite loops are rather useless on their own. And that's where we introduce the `break` and `continue` statements. They are used to exit a loop early, and to skip the rest of the current iteration, respectively. Syntax for the `break` statement is (without a semicolon): ```move break ``` The `break` statement is used to stop the execution of a loop and exit it early. It is often used in combination with a conditional statement to exit the loop when a certain condition is met. To illustrate this point, let's turn the infinite `loop` from the previous example into something that looks and behaves more like a `while` loop: ```move #[test] fun test_break_loop() { let mut x = 0; // This will loop until `x` is 5. loop { x = x + 1; // If `x` is 5, then exit the loop. if (x == 5) { break // Exit the loop. } }; assert_eq!(x, 5); } ``` Almost identical to the `while` loop, right? The `break` statement is used to exit the loop when `x` is 5. If we remove the `break` statement, the loop will run forever, just like in the previous example. ## Skipping an Iteration The `continue` statement is used to skip the rest of the current iteration and start the next one. Similarly to `break`, it is used in combination with a conditional statement to skip the rest of an iteration when a certain condition is met. Syntax for the `continue` statement is (without a semicolon): ```move continue ``` The example below skips odd numbers and prints only even numbers from 0 to 10: ```move #[test] fun test_continue_loop() { let mut x = 0u64; // This will loop until `x` is 10. loop { x = x + 1; // If `x` is odd, then skip the rest of the iteration. if (x % 2 == 1) { continue // Skip the rest of the iteration. }; std::debug::print(&x); // If `x` is 10, then exit the loop. if (x == 10) { break // Exit the loop. } }; assert_eq!(x, 10) // 10 } ``` `break` and `continue` statements can be used in both `while` and `loop` loops. ## Labeled Control Flow By default, `break` and `continue` act on the innermost loop that encloses them. This is a problem when loops are nested: from inside an inner loop, there is no way to break out of the outer one. To solve this, Move lets you attach a _label_ to a loop and then tell `break` or `continue` exactly which one to target. A label is a name prefixed with a single quote, placed before the `loop` or `while` keyword. You can then write `break 'label` or `continue 'label` to jump to the labeled loop instead of the innermost one: ```move 'outer: loop { while (condition) { // Exits both loops at once. break 'outer; // Skips to the next iteration of the outer loop. continue 'outer; }; }; ``` Consider a search over a grid - a vector of rows, where each row is itself a vector. Once we find the value we are looking for, we want to stop scanning entirely, not just finish the current row. Labeling the outer loop lets the inner `while` loop abandon the whole search in one step: ```move // Searches a grid (a vector of rows) for `target`, returning `true` as // soon as it is found. The `'search` label lets the inner loop break out // of *both* loops at once. fun grid_contains(grid: &vector>, target: u8): bool { let mut row = 0; 'search: loop { // Ran out of rows without finding the target. if (row >= grid.length()) break false; let inner = &grid[row]; let mut col = 0; while (col < inner.length()) { if (inner[col] == target) { // Found it - break the outer `'search` loop directly, // skipping any remaining columns and rows. break 'search true }; col = col + 1; }; row = row + 1; } } #[test] fun test_grid_contains() { let grid = vector[ vector[1, 2, 3], vector[4, 5, 6], vector[7, 8, 9], ]; assert_eq!(grid_contains(&grid, 5), true); assert_eq!(grid_contains(&grid, 10), false); } ``` Notice that the `break` statements also carry a value: `break false` and `break 'search true`. A `loop` is an expression, so breaking out of it can produce a result - here, the boolean returned by the function. This is specific to `loop`: a `while` loop always evaluates to the unit value `()`, so its `break` cannot carry a value. Without the label, escaping both loops would require an extra flag variable and a second check in the outer loop. ### Labeled Blocks Labels are not limited to loops. A plain block `{ ... }` can also be labeled, and then exited early with `return 'label `. This is useful for computing a value with several possible early exits, without extracting the logic into a separate function: ```move // Classifies a number, exiting the `'result` block early with `return` // as soon as the answer is known. fun classify(x: u64): vector { 'result: { if (x == 0) return 'result "zero"; if (x % 2 == 0) return 'result "even"; "odd" } } #[test] fun test_labeled_block() { assert_eq!(classify(0), "zero"); assert_eq!(classify(4), "even"); assert_eq!(classify(7), "odd"); } ``` Here the `'result` block produces a value, and any of the `return 'result` statements can end it early. This becomes especially powerful together with the iteration [macros](./macros) mentioned above, where a labeled block lets a lambda break out of the iteration with a result. Two rules are worth remembering: - A label can only be placed on a `loop`, a `while`, or a block `{}` - **not** on an `if` expression. To label a conditional, label the block around it (an `if` branch is itself a block). - `break` and `continue` work only with _loop_ labels, while `return` works only with _block_ labels. Mixing them (for example `break` on a block label) is a compilation error. > The [Labeled Control Flow](./../../reference/control-flow/labeled-control-flow) chapter of the > Move Reference covers these forms in more detail, including their interaction with macros. ## Early Return The `return` statement is used to exit a [function](./function) early and return a value. It is often used in combination with a conditional statement to exit the function when a certain condition is met. The syntax for the `return` statement is: ```move return ``` Here is an example of a function that returns a value when a certain condition is met: ```move /// This function returns `true` if `x` is greater than 0 and not 5, /// otherwise it returns `false`. fun is_positive(x: u8): bool { if (x == 5) { return false }; if (x > 0) { return true }; false } #[test] fun test_return() { assert_eq!(is_positive(5), false); assert_eq!(is_positive(0), false); assert_eq!(is_positive(1), true); } ``` Unlike in many other languages, the `return` statement is not required for the last expression in a function. The last expression in a function block is automatically returned. However, the `return` statement is useful when we want to exit a function early if a certain condition is met. ## Further Reading - [Control Flow](./../../reference/control-flow) chapter in the Move Reference. --- # Enums and Match An enum is a user-defined data structure that, unlike a [struct](./struct), can represent multiple variants. Each variant can contain primitive types, structs, or other enums. However, recursive enum definitions - similar to recursive struct definitions - are not allowed. ## Definition An enum is defined using the `enum` keyword, followed by optional abilities and a block of variant definitions. Each variant has a tag name and may optionally include either positional values or named fields. An enum must have at least one variant; the shape of each variant is fixed at definition, and the total number of variants can be relatively large - up to 100. ```move module book::segment; use std::string::String; /// `Segment` enum definition. /// Defines various string segments. public enum Segment has copy, drop { /// Empty variant, no value. Empty, /// Variant with a value (positional style). String(String), /// Variant with named fields. Special { content: vector, encoding: u8, // Encoding tag. }, } ``` In the code sample above we defined a public `Segment` enum, which has the `drop` and `copy` abilities, and 3 variants: - `Empty`, which has no fields. - `String`, which contains a single positional field of type `String`. - `Special`, which uses named fields: `content` of type `vector` and `encoding` of type `u8`. ## Instantiating Enums are _internal_ to the module in which they are defined. This means an enum can only be constructed, read, and unpacked within the same module. [Similar to structs](./struct#creating-an-instance), enums are instantiated by specifying the type, the variant, and the values for any fields defined in that variant. ```move /// Constructs an `Empty` segment. public fun new_empty(): Segment { Segment::Empty } /// Constructs a `String` segment with the `str` value. public fun new_string(str: String): Segment { Segment::String(str) } /// Constructs a `Special` segment with the `content` and `encoding` values. public fun new_special(content: vector, encoding: u8): Segment { Segment::Special { content, encoding, } } ``` Depending on the use case, you may want to provide public constructors, or instantiate enums internally as a part of application logic. ## Using in Type Definitions The biggest benefit of using enums is the ability to represent varying data structures under a single type. To demonstrate this, let’s define a struct that contains a vector of `Segment` values: ```move /// A struct to demonstrate enum capabilities. public struct Segments(vector) has copy, drop; #[test] fun test_segments() { let _ = Segments(vector[ Segment::Empty, Segment::String("hello"), Segment::String(" move"), Segment::Special { content: "21", encoding: 1 }, ]); } ``` All variants of the Segment enum share the same type - `Segment` - which allows us to create a homogeneous vector containing instances of different variants. This kind of flexibility is not achievable with structs, as each struct defines a single, fixed shape. > `Segments` is a [positional struct](./struct#positional-structs) wrapping a single > `vector` field; note how its abilities are declared after the parentheses. ## Pattern Matching Unlike structs, enums require special handling when it comes to accessing the inner value or checking the variant. We simply cannot read the inner fields of an enum using the `.` (dot) syntax, because we need to make sure that the value we are trying to access is the right one. For that Move offers _pattern matching_ syntax. > This chapter doesn't intend to cover all the features of pattern matching in Move. Refer to the > [Pattern Matching](./../../reference/control-flow/pattern-matching) section in the Move Reference. Pattern matching allows conditioning the logic based on the _pattern_ of the value. It is performed using the `match` expression, followed by the matched value in parenthesis and the block of _match arms_, defining the pattern and expression to be performed if the pattern is right. Let's extend our example by adding a set of `is_variant`-like functions, so external packages can check the variant, starting with `is_empty`: ```move /// Whether this is an `Empty` segment. public fun is_empty(s: &Segment): bool { // Match is an expression, hence we can use it for return value. match (s) { Segment::Empty => true, Segment::String(_str) => false, Segment::Special { content: _, encoding: _ } => false, } } ``` The `match` keyword begins the expression, and `s` is the value being tested. Each match arm checks for a specific variant of the `Segment` enum. If `s` matches `Segment::Empty`, the function returns `true`; otherwise, it returns `false`. For variants with fields, we need to bind the inner structure to local variables (even if we don’t use them, marking unused values with `_` to avoid compiler warnings). ### Trick #1 - _any_ Condition The Move compiler infers the type of the value used in a `match` expression and ensures that the _match arms_ are exhaustive - that is, all possible variants or values must be covered. However, in some cases, such as matching on a primitive value or a collection like a vector, it's not feasible to list every possible case. For these situations, match supports a wildcard pattern (`_`), which acts as a default arm. This arm is executed when no other patterns match. We can demonstrate this by simplifying our `is_empty` function and replacing the non-`Empty` variants with a wildcard: ```move match (s) { Segment::Empty => true, _ => false, // Anything else returns `false`. } } ``` Similarly, we can use the same approach to define `is_special` and `is_string`: ```move /// Whether this is a `Special` segment. public fun is_special(s: &Segment): bool { match (s) { // Hint: the `..` ignores inner fields Segment::Special { .. } => true, _ => false, } } /// Whether this is a `String` segment. public fun is_string(s: &Segment): bool { match (s) { Segment::String(_) => true, _ => false, } } ``` ### Trick #2 - `try_into` Helpers With the addition of `is_variant` functions, we enabled external modules to check which variant an enum instance represents. However, this is often not enough - external code still cannot access the inner value of a variant due to enums being internal to their module. A common pattern for addressing this is to define `try_into` functions. These functions match on the value and return an `Option` containing the inner contents if the `match` succeeds. ```move /// Returns `Some(String)` if the `Segment` is `String`, `None` otherwise. public fun try_into_inner_string(s: Segment): Option { match (s) { Segment::String(str) => option::some(str), _ => option::none(), } } ``` This pattern safely exposes internal data in a controlled way, without the risk of an abort. ### Trick #3 - Matching on Primitive Values The `match` expression in Move can be used with values of any type - enums, structs, or primitives. To demonstrate this, let’s implement a `to_string` function that creates a new `String` from a `Segment`. In the case of the `Special` variant, we will match on the `encoding` field to determine how to interpret the content: `0` stands for UTF-8, and `1` for the stricter ASCII encoding. ```move /// Return a `String` representation of a segment. public fun to_string(s: &Segment): String { match (*s) { // Return an empty string. Segment::Empty => "", // Return the inner string. Segment::String(str) => str, // Return the decoded contents based on the encoding. Segment::Special { content, encoding } => { // Perform a match on the encoding; we support 0 - UTF-8 and 1 - ASCII. match (encoding) { // UTF-8 encoding, interpret content as a UTF-8 string. 0 => content.to_string(), // ASCII encoding - stricter, aborts on non-ASCII bytes. 1 => content.to_ascii_string().to_string(), // We have to provide a wildcard pattern, because values of `u8` are 0-255. // Abort execution if the encoding is unknown. _ => abort, } }, } } ``` This function demonstrates several key things: - Nested `match` expressions can be used for deeper logic branching. - Wildcards are essential for covering all possible values in primitive types like `u8`. - The function takes `s` by reference, but matching arms bind inner values _by value_. The `*s` expression makes this possible: the [dereference operator](./references#dereferencing) `*` copies the value behind the reference, which is allowed because `Segment` has the `copy` ability. - The wildcard arm uses `abort` without an abort code to reject unknown encodings - a _clean abort_, covered in the [Aborting Execution](./assert-and-abort) section. ## The Final Test Now we can finalize the test we started before using the features we have added. Let's create a scenario where we build enums into a vector. ```move // Note, that the module has changed! module book::segment_tests; use book::segment; use std::string::String; #[test] fun test_full_enum_cycle() { use std::unit_test::assert_eq; // Create a vector of different Segment variants. let segments = vector[ segment::new_empty(), segment::new_string("hello"), segment::new_special(" ", 0), // utf8 segment::new_string("move"), segment::new_special("!", 1), // ascii ]; // Aggregate all segments into the final string using `vector::fold!` macro. let result = segments.fold!("", |mut acc: String, segment| { // Do not append empty, only `Special` and `String`. if (!segment.is_empty()) { acc.append(segment.to_string()); }; acc }); // Check that the result is what's expected. assert_eq!(result, "hello move!"); } ``` This test demonstrates the full enum workflow: instantiating different variants, using public accessors, and performing logic with pattern matching. That should be enough to get you started! To learn more about enums and pattern matching, refer to the resources listed in the [further reading](#further-reading) section. ## Summary - Enums are user-defined types that can represent multiple variants under a single type. - Each variant can contain different types of data (primitives, structs, or other enums). - Enums are internal to their defining module and require pattern matching for access. - Pattern matching is done using the `match` expression, which: - Works with enums, structs, and primitive values; - Must handle all possible cases (be exhaustive); - Supports the `_` wildcard pattern for remaining cases; - Can return values and be used in expressions; - Common patterns for enums include `is_variant` checks and `try_into` helper functions. ## Further Reading - [Enums](./../../reference/enums) in the Move Reference - [Pattern Matching](./../../reference/control-flow/pattern-matching) in the Move Reference --- # Struct Methods Throughout the previous sections we have called functions on values with the dot operator: `v.length()`, `opt.is_some()`, `artist.name()`. This is the _receiver syntax_ - "receiver" refers to the instance that receives the method call - and this section explains how it works and how to control it. Methods make code that operates on a struct read naturally: the value comes first, the operation follows, and there is no need to import or spell out the function's module. ## Method Syntax The core rule: a function is callable with the `.` operator when its first argument is a struct defined in the _same module_ as the function. Such methods are automatically available everywhere the struct is used - this is exactly why `vector` and `Option` values could be called with the dot syntax as soon as we had them. If the type of the first argument is defined in another module, the function is not associated with the struct by default and must be called with the standard function call syntax - unless an _alias_ is declared, as shown below. ```move module book::hero; /// A struct representing a hero. public struct Hero has drop { health: u8, mana: u8, } /// Create a new Hero. public fun new(): Hero { Hero { health: 100, mana: 100 } } /// A method which casts a spell, consuming mana. public fun heal_spell(hero: &mut Hero) { hero.health = hero.health + 10; hero.mana = hero.mana - 10; } /// A method which returns the health of the hero. public fun health(hero: &Hero): u8 { hero.health } /// A method which returns the mana of the hero. public fun mana(hero: &Hero): u8 { hero.mana } #[test_only] use std::unit_test::assert_eq; #[test] // Test the methods of the `Hero` struct. fun test_methods() { let mut hero = new(); hero.heal_spell(); assert_eq!(hero.health(), 110); assert_eq!(hero.mana(), 90); } ``` ## Method Aliases Method aliases help avoid name conflicts when modules define multiple structs and their methods. They can also provide more descriptive method names for structs. Here's the syntax: ```move // for local method association use fun function_path as Type.method_name; // exported alias public use fun function_path as Type.method_name; ``` > Public aliases are only allowed for structs defined in the same module. For structs defined in > other modules, aliases can still be created but cannot be made public. In the example below, we changed the `hero` module and added another type - `Villain`. Both `Hero` and `Villain` have similar field names and methods. To avoid name conflicts, we prefixed methods with `hero_` and `villain_` respectively. However, using aliases allows these methods to be called on struct instances without the prefix: ```move module book::hero_and_villain; /// A struct representing a hero. public struct Hero has drop { health: u8, } /// A struct representing a villain. public struct Villain has drop { health: u8, } /// Create a new Hero. public fun new_hero(): Hero { Hero { health: 100 } } /// Create a new Villain. public fun new_villain(): Villain { Villain { health: 200 } } // Alias for the `hero_health` method. It will be imported automatically when // the module is imported. public use fun hero_health as Hero.health; public fun hero_health(hero: &Hero): u8 { hero.health } // Alias for the `villain_health` method. Will be imported automatically // when the module is imported. public use fun villain_health as Villain.health; public fun villain_health(villain: &Villain): u8 { villain.health } #[test_only] use std::unit_test::assert_eq; #[test] // Test the methods of the `Hero` and `Villain` structs. fun test_associated_methods() { let hero = new_hero(); assert_eq!(hero.health(), 100); let villain = new_villain(); assert_eq!(villain.health(), 200); } ``` In the test function, the `health` method is called directly on the `Hero` and `Villain` instances without the prefix, as the compiler automatically associates the methods with their respective structs. > Note: In the test function, `hero.health()` is calling the aliased method, not directly accessing > the private `health` field. While the `Hero` and `Villain` structs are public, their fields remain > private to the module. The method call `hero.health()` uses the public alias defined by > `public use fun hero_health as Hero.health`, which provides controlled access to the private > field. ## Aliasing a Method of an External Type Aliases are not limited to the module's own structs: a local (non-public) alias can attach a method name to a type from another module. Here we give the standard `String` type an extra method name, `num_bytes` - a more precise name for what its `length` function actually counts: ```move module book::string_alias; use std::string::String; /// Alias `std::string::length` as `String.num_bytes`. /// A local alias can be declared for any type, even an external one. use fun std::string::length as String.num_bytes; #[test_only] use std::unit_test::assert_eq; #[test] fun test_string_alias() { let s: String = "Hello"; // Same function, two names: the built-in method and our alias. assert_eq!(s.length(), 5); assert_eq!(s.num_bytes(), 5); } ``` The alias only exists within the module that declares it - which is exactly why it cannot be `public`: the module does not own the `String` type, so it cannot extend its interface for everyone else. ## Further Reading - [Method Syntax](./../../reference/method-syntax) in the Move Reference. --- # Visibility Modifiers Every module member has a visibility. By default, all module members are _private_ - meaning they are only accessible within the module they are defined in. However, you can add a visibility modifier to make a module member _public_ - visible outside the module, or _public(package)_ - visible in the modules within the same package. Additionally, a function can be marked with the _entry_ modifier, which allows a _non-public_ function to be called from a transaction. Unlike the rest, `entry` is not a visibility level - it can be combined with them, and it controls how the function interacts with transactions rather than with other modules. ## Internal Visibility A function or a struct defined in a module which has no visibility modifier is _private_ to the module. It can't be called from other modules. ```move module book::internal_visibility; // This function can be called from other functions in the same module fun internal() { /* ... */ } // Same module -> can call internal() fun call_internal() { internal(); } ``` The following code will not compile, because `internal` is private to `book::internal_visibility`: ```move module book::try_calling_internal; use book::internal_visibility; // Different module -> can't call internal() fun try_calling_internal() { internal_visibility::internal(); // ^ ERROR! [E04001]: restricted visibility // Invalid call to internal function // 'book::internal_visibility::internal' } ``` Note that just because a struct field is not visible from Move does not mean that its value is kept confidential — it is always possible to read the contents of an onchain object from outside of Move. You should never store unencrypted secrets inside of objects. ## Public Visibility A struct or a function can be made _public_ by adding the `public` keyword before the `fun` or `struct` keyword. ```move module book::public_visibility; // This function can be called from other modules public fun public_fun() { /* ... */ } ``` A public function can be imported and called from other modules. The following code will compile: ```move module book::try_calling_public; use book::public_visibility; // Different module -> can call public_fun() fun try_calling_public() { public_visibility::public_fun(); } ``` A `public` function can also be called directly from a [transaction](./../concepts/what-is-a-transaction). Making a function `public` is the default - and recommended - way to expose functionality to users: a public function can be a command in a transaction, be freely combined with other commands in it, and serve as a building block for other packages. No extra modifier is needed for any of this. ## Package Visibility A function with _package_ visibility can be called from any module within the same package, but not from modules in other packages. In other words, it is _internal_ to the package. ```move module book::package_visibility; public(package) fun package_only() { /* ... */ } ``` A package function can be called from any module within the same package: ```move module book::try_calling_package; use book::package_visibility; // Same package `book` -> can call package_only() fun try_calling_package() { package_visibility::package_only(); } ``` ## Entry Modifier As shown [above](#public-visibility), a `public` function is already callable from a [transaction](./../concepts/what-is-a-transaction) - `public` is the default and preferred way to make a function available, to transactions and other modules alike. The `entry` modifier serves the opposite goal: a function that can be called _only_ as a command in a transaction. Marking a _non-public_ function with `entry` keeps it out of reach of other modules' code, while permitting it as a transaction command - deliberately limiting who can call it and how. It is not a visibility level: an `entry` function keeps whatever visibility it is declared with. A function marked `entry` with no other modifier stays _private_ - callable as a transaction command and from its own module, and nothing else. ```move module book::entry_functions; // Can be called from a transaction, but not from other modules entry fun from_transaction_only() { /* ... */ } // Can be called from a transaction and from modules of the same package public(package) entry fun from_package_or_transaction() { /* ... */ } ``` Public functions can already be called from transactions, so `entry` adds nothing to a `public` function, and the compiler warns about the combination: ```text warning[Lint W99010]: unnecessary `entry` on a `public` function │ 7 │ public entry fun both() { } │ ^^^^^ `entry` on `public` is meaningless. In conjunction with `public`, │ `entry` adds no additional permissions or restrictions. ``` Any Move function can be marked `entry` - there are no restrictions on its signature. The value of the modifier lies in what it does for _non-public_ functions: they become callable as transaction commands while staying out of the module's API - and the transaction calling them accepts additional checks on the arguments it passes. That guarantee concerns _hot potatoes_ - values that must be consumed before a transaction ends: the arguments of a non-`public` `entry` function are statically guaranteed not to be entangled with any such outstanding obligation, which is what lets `entry` serve as a safe transaction boundary. The full rules, with a worked flash-loan example, are covered in [Entry Functions](./../move-advanced/entry-functions) in the Advanced Move Features chapter. To summarize: `entry` limits composability - in both directions. A non-public `entry` function is not part of the module's API, so other packages cannot call it or build on it; and inside a transaction, its arguments face restrictions that `public` function arguments do not. Reach for it when that is the point - when a function should be callable _only_ as a transaction command, or when it needs the argument guarantee. For everything else, `public` is the right choice. ## Native Functions Some functions in the [framework](./../programmability/sui-framework) and [standard library](./standard-library) are marked with the `native` modifier. These functions are natively provided by the Move VM and do not have a body in Move source code. To learn more about the native modifier, refer to the [Move Reference](./../../reference/functions?highlight=native#native-functions). ```move module std::type_name; public native fun get(): TypeName; ``` This is an example from `std::type_name`, learn more about this module in the [reflection chapter](./type-reflection). ## Further Reading - [Visibility](./../../reference/functions#visibility) in the Move Reference. --- # Ownership and Scope Ownership is the central concept of Move - it is even where the language got its name. Move is designed for digital assets, and its main promise is that a value cannot be duplicated and cannot be accidentally lost. The mechanism behind this promise is ownership, and it is enforced by the compiler: a program that breaks the rules does not compile. The rules are: - Every value has exactly one owner - the scope in which it is defined. - When a value is passed to a function, assigned to a new variable, or returned, it is _moved_ to a new owner, and the previous owner can no longer use it. - When a scope ends, every value it still owns must be either discardable or already moved out. The rest of this section walks through these rules one by one. If some of them seem strict - that is the point: the restrictions are what make it safe to treat a value in Move as an asset. ## Variable Scope A scope is the range of code in which a value is valid. A variable defined in a function is owned by that function's scope: it comes into scope at the declaration, and goes out of scope when the function ends. ```move public fun scope() { // `a` is not yet declared and cannot be used here let a = 1u8; // `a` comes into scope and is owned by `scope` // `a` can be used here } // scope ends; `a` goes out of scope ``` Nothing surprising so far - this is how local variables behave in most languages. Ownership becomes interesting when a value needs to leave its scope. ## Moving a Value To demonstrate the rules, we will use a small module with a `Coin` type and two functions - one that creates a coin and one that destroys it: ```move /// A struct representing a digital asset. Note that `Coin` has no /// abilities: its value cannot be copied and cannot be discarded. public struct Coin { value: u64 } /// Creates a new `Coin`. The new value is returned, and its ownership /// is transferred to the caller of the function. public fun mint(value: u64): Coin { Coin { value } } /// Takes ownership of a `Coin` and destroys it by unpacking. public fun spend(coin: Coin) { let Coin { value: _ } = coin; // the coin is destroyed here } ``` The `Coin` struct has no [abilities](./abilities-introduction), so the compiler places the strictest constraints on its values: they cannot be copied and cannot be discarded. A value like this can only change hands - which is exactly what we want from an asset. When a value is passed to a function, it is _moved_ into the function's scope. The function becomes the new owner, and the caller loses access to the value. This is called _move semantics_. ```move let coin = mint(100); // the test function owns the coin spend(coin); // ownership of the value moves into `spend` // `coin` can no longer be used here ``` Let's see what happens if we break the rule and try to use `coin` after it was moved: ```move #[test] fun test_move_semantics() { let coin = mint(100); spend(coin); // ownership of the value moves into `spend` spend(coin); // ERROR! `coin` was already moved } ``` The code above will not compile, and the compiler will point at the exact spot where the value was moved: ```text error[E06002]: use of unassigned variable ┌─ sources/ownership.move:12:11 │ 11 │ spend(coin); │ ---- │ │ │ The value of 'coin' was previously moved here. │ Suggestion: use 'copy coin' to avoid the move. 12 │ spend(coin); │ ^^^^ Invalid usage of previously moved variable 'coin'. ``` The compiler suggests using `copy coin`, but that only works for values that can be copied - and `Coin` cannot. There is no way to spend the same coin twice, and this guarantee is checked before the code ever runs. Assigning a value to a new variable is also a move. The value itself is not changed or copied - only its owner is: ```move let coin = mint(100); let new_owner = coin; // the value moves from `coin` to `new_owner` // `coin` can no longer be used here spend(new_owner); ``` ## Returning a Value Moves also work in the opposite direction: a function can return a value, moving it to the caller's scope. This is how the `mint` function from our example transfers ownership of a newly created coin to whoever called it. Combined with passing by value, this gives a full picture of a value's lifetime: `mint` creates the coin and hands it to the test function, which then hands it over to `spend`, which destroys it. At every point in the program, the coin has exactly one owner. ## Every Value Must Be Used What if a value is never passed on? Let's mint a coin and simply let the function end: ```move #[test] fun test_lose_a_coin() { let coin = mint(100); } // ERROR! `coin` still contains a value which cannot be discarded ``` The third rule kicks in: a scope cannot end while it still owns a value that is not discardable. ```text error[E06001]: unused value without 'drop' ┌─ sources/ownership.move:7:35 │ 4 │ public struct Coin { value: u64 } │ ---- To satisfy the constraint, the 'drop' ability would need to be added here · 7 │ let coin = mint(100); │ ---- ↑ The local variable 'coin' still contains a value. │ The value does not have the 'drop' ability and must │ be consumed before the function returns ``` Whether a value can be discarded is controlled by the `drop` ability, which we covered in the [Ability: Drop](./drop-ability) section. For a type like `Coin`, the absence of `drop` means a coin cannot be forgotten in a local variable and silently vanish - the code holding it is forced to do something with it. ## Copyable Types Some values do not need this level of protection. All primitive types - integers, `bool`, `address` - have the `copy` ability, and instead of being moved, they are copied when assigned or passed to a function: ```move let x = 10u64; let y = x; // `x` is copied into `y`, not moved // both `x` and `y` can be used after the assignment assert_eq!(x, y); ``` Copying is implicit for primitive types because they are small and cheap to duplicate. Custom types can also opt into this behavior by adding the `copy` ability, which we cover in the [Ability: Copy](./copy-ability) section. If needed, a copyable value can still be moved explicitly with the `move` keyword: ```move let x = 10u64; let y = move x; // explicitly move `x` instead of copying it // `x` can no longer be used here ``` ## Scopes and Blocks Besides the function's main scope, every block forms its own scope. Variables declared inside a block are owned by it and go out of scope when the block ends. Code inside a block can access the variables of the enclosing scope, but not the other way around: ```move let x = 1u8; { let y = 2u8; // `y` is owned by the block let z = x + y; // variables from the outer scope are accessible }; // block ends; `y` and `z` go out of scope // only `x` can be used here ``` A block is an expression, and its resulting value is moved out to the enclosing scope - the same move semantics as returning a value from a function: ```move let x = { let y = 2u8; y + 1 // the result of the block moves to `x` }; // `y` goes out of scope assert_eq!(x, 3); ``` ## Next Steps So far, the only way to let a function use a value was to give the ownership away. Doing that for every operation would be impractical - reading a field should not require handing over the whole value. Move solves this with _references_, which allow a function to borrow a value without taking ownership. We cover them in the [References](./references) section. ## Further Reading - [Local Variables and Scopes](./../../reference/variables) in the Move Reference. --- # Abilities: Copy In the [Ownership and Scope](./ownership-and-scope) section, we saw that primitive values are _copied_ rather than moved: assigning a number to a new variable leaves both variables usable. The `copy` ability is precisely what enables this behavior - and while it is built into the primitive types, it is _not_ the default for custom types. Move is designed to express digital assets and resources, and a resource that could be freely duplicated would not be much of a resource. Duplication is therefore something a type must explicitly opt into: ```move public struct Copyable has copy {} ``` Once a type has the `copy` ability, its values are copied wherever a move would otherwise happen and the original is still needed - implicitly, without any special syntax. The `copy` keyword can be used to spell the copy out explicitly: ```move let a = Copyable {}; // `a` is copied into `b` implicitly - both are usable afterwards. let b = a; // The `copy` keyword makes the copy explicit. let c = copy a; // `Copyable` does not have the `drop` ability, so every instance - // `a`, `b`, and `c` - has to be used. Here, we unpack all of them. let Copyable {} = a; let Copyable {} = b; let Copyable {} = c; ``` In the example above, `a` is copied into `b` implicitly - the compiler sees that `a` is used again afterwards, and copies the value instead of moving it. Then `a` is copied into `c` explicitly with the `copy` keyword. After the three assignments, there are three independent instances of `Copyable` - and each of them has to be dealt with separately. > Note the unpacking at the end of the example: `Copyable` has `copy`, but not `drop`, so every > instance - including each copy - must be used, and the test unpacks all three. Copying a value > never bypasses the usage rules; it just creates more values to which those rules apply. ## Copying and Drop As the example shows, `copy` without `drop` is a rather inconvenient combination: duplication is allowed, but every duplicate still demands explicit handling. This is why the two abilities almost always go together - a value that is cheap to duplicate is, in practice, always fine to discard. Types that carry plain data, rather than assets, typically declare both: ```move public struct Value has copy, drop {} ``` All of the primitive types behave as if they have `copy` and `drop`: they are copied on assignment and discarded without a second thought - with the compiler managing all of it. Copying is not the only way to let several parts of a program read the same value. In the [References](./references) section, we show how a value can be _borrowed_ instead, avoiding the duplication altogether; and how the [dereference operator](./references#dereferencing) `*` turns a reference back into a copy, which is only permitted for types with the `copy` ability. ## Types with the `copy` Ability All native types in Move have the `copy` ability. This includes: - [`bool`](./../move-basics/primitive-types#booleans) - [unsigned integers](./../move-basics/primitive-types#integer-types) - [`vector`](./../move-basics/vector) when `T` has `copy` - [`address`](./../move-basics/address) All of the types defined in the standard library have the `copy` ability as well. This includes: - [`Option`](./../move-basics/option) when `T` has `copy` - [`String`](./../move-basics/string) - [`TypeName`](./../move-basics/type-reflection) Just like with [`drop`](./drop-ability#types-with-the-drop-ability), container types are only copyable when their contents are: a `vector` can be duplicated only if duplicating `T` is allowed in the first place. ## Further Reading - [Type Abilities](./../../reference/abilities) in the Move Reference. --- # Constants Constants are immutable values that are defined at the module level. They often serve as a way to give names to static values that are used throughout a module. For example, if there's a default price for a product, you might define a constant for it. Constants are stored in the module's bytecode, and each time they are used, the value is copied. Like every module member, constants are private by default - and unlike functions or structs, they cannot be made public; the [config pattern](#using-the-config-pattern) below shows how to share them between modules. ```move module book::shop_price; use sui::{coin::Coin, sui::SUI}; /// Trying to purchase an item at an incorrect price. const EWrongPrice: u64 = 0; /// The price of an item in the shop. const ITEM_PRICE: u64 = 100; /// The owner of the shop, an address. const SHOP_OWNER: address = @0xa11ce; /// An item sold in the shop. public struct Item {} /// Purchase an item from the shop. public fun purchase(coin: Coin): Item { assert!(coin.value() == ITEM_PRICE, EWrongPrice); transfer::public_transfer(coin, SHOP_OWNER); Item {} } ``` ## Naming Convention Constants must start with a capital letter - this is enforced at the compiler level. For constants used as a value, the convention is to use all uppercase letters and underscores between words, which makes constants stand out from other identifiers in the code. An exception is made for [error constants](./assert-and-abort#error-constants), which are written as `E` followed by a CamelCase description, as in `ENoAccess`. ```move /// Price of the item used at the shop. const ITEM_PRICE: u64 = 100; /// Error constant. const EItemNotFound: u64 = 1; ``` ## Constants Are Immutable Constants can't be changed or assigned new values. As part of the package bytecode, they are inherently immutable. ```move module book::immutable_constants; const ITEM_PRICE: u64 = 100; // emits an error fun change_price() { ITEM_PRICE = 200; } ``` ## Using the Config Pattern A common use case for an application is to define a set of constants that are used throughout the codebase. But due to constants being private to the module, they can't be accessed from other modules. One way to solve this is to define a "config" module that exports the constants through public functions: ```move module book::config; const ITEM_PRICE: u64 = 100; const TAX_RATE: u64 = 10; const SHIPPING_COST: u64 = 5; /// Returns the price of an item. public fun item_price(): u64 { ITEM_PRICE } /// Returns the tax rate. public fun tax_rate(): u64 { TAX_RATE } /// Returns the shipping cost. public fun shipping_cost(): u64 { SHIPPING_COST } ``` This way other modules can import and read the constants, and the update process is simplified. If the constants need to be changed, only the config module needs to be updated during the package upgrade. ## Further Reading - [Constants](./../../reference/constants) in the Move Reference - [Coding conventions for constants](./../guides/code-quality-checklist#regular-constants-are-all_caps) --- # Aborting Execution A transaction can end in one of two ways: it either succeeds, and all of the changes it made are applied and committed to the blockchain, or it _aborts_, and none of the changes are applied. There is nothing in between: a transaction cannot partially succeed, and an abort in a deeply nested function call fails the entire transaction. This all-or-nothing model is what makes error handling in Move simple and predictable - a function never needs to undo its changes, because an abort undoes everything at once. > There is no catch mechanism in Move. An abort cannot be intercepted or recovered from: it always > fails the whole transaction. This is a design choice - it trades flexibility for simplicity and > makes it impossible to end up in a partially updated state. In this section, we look at the tools Move provides for aborting: the `abort` expression, the `assert!` macro, and the conventions for defining error codes and error messages. ## Abort The `abort` keyword stops execution immediately. It is normally given an _abort code_ - an [integer](./primitive-types) of type `u64` - which is returned to the caller of the transaction together with the identity of the module that aborted. Here is an example: ```move let user_has_access = false; // abort with a predefined constant if `user_has_access` is false if (!user_has_access) { abort 1 }; ``` The code above will, of course, abort with abort code `1`. Two properties of abort codes are worth internalizing early: - Abort codes are _local to the module_. Two different modules can both abort with code `1`, and they mean different things; the caller has to interpret the code together with the module that produced it. - An abort code carries no message. The blockchain records only the numeric code and the location of the abort - making the codes readable is up to the module author, which is what [error constants](#error-constants) and [error messages](#error-messages) below are for. ## Omitting the Abort Code The abort code can be omitted in the source - a bare `abort` expression is valid Move: ```move // `abort` can also be used without an explicit abort code. abort ``` Omitted does not mean absent, though: the caller still receives a `u64` abort code, derived automatically by the compiler. The derived code uses the clever-error encoding described in [Error Messages](#error-messages) below - it carries the module and the source line of the failure, with the constant name and value left empty. This form, sometimes called a _clean abort_, is a good fit for branches that are not expected to be reachable at all - such as the wildcard arm of a `match` expression covering values that cannot occur (see [Enums and Match](./enum-and-match)). Since the derived code points at the failure but says nothing about its _meaning_, for conditions that external callers can actually trigger, prefer an explicit code or an error message. ## assert! The `assert!` macro is a built-in macro that checks a condition and aborts if the condition is false. It is a shorthand for the `if` + `abort` combination you would otherwise write by hand, and it is by far the most common way to abort in Move code. The first argument is the condition; the second, optional, argument is the abort code - when it is omitted, a code is derived automatically, the same way as for a bare `abort`: ```move // aborts if `user_has_access` is `false` with abort code 0 assert!(user_has_access, 0); // expands to: if (!user_has_access) { abort 0 }; // the abort code can be omitted assert!(user_has_access); ``` A common practice is to place asserts at the beginning of a function - check everything first, then perform the changes. Because an abort reverts the whole transaction this is not required for safety, but it makes the function's requirements visible at a glance and avoids wasting [gas](./../concepts/what-is-a-transaction) on work that is bound to be thrown away. ## Error Constants A raw numeric code like `assert!(user_has_access, 1)` tells the reader nothing about what went wrong. To make error codes descriptive, it is a good practice to define them as [constants](./constants). Error constants follow their own naming convention - `E` followed by a CamelCase description - which sets them apart from regular `ALL_CAPS` constants: ```move /// Error code for when the user has no access. const ENoAccess: u64 = 0; /// Trying to access a field that does not exist. const ENoField: u64 = 1; /// Updates a record. public fun update_record(/* ... , */ user_has_access: bool, field_exists: bool) { // asserts are way more readable now assert!(user_has_access, ENoAccess); assert!(field_exists, ENoField); /* ... */ } ``` Error constants are regular `u64` constants and receive no special treatment from the compiler. However, following the convention makes the code self-documenting - `assert!(user_has_access, ENoAccess)` reads as a sentence - and a caller who receives the abort code can find the matching constant in the module's source. A well-written module defines an error constant for every abort scenario it can produce. ## Error Messages Move 2024 introduces _clever errors_ - error constants marked with the `#[error]` attribute. Unlike regular error constants, they can be of any type - most usefully `vector`, holding a human-readable error message: ```move #[error] const ENotAuthorized: vector = "The user is not authorized to perform this action"; #[error] const EValueTooLow: vector = "The value is too low, it should be at least 10"; /// Performs an action on behalf of the user. public fun update_value(user: &mut User, value: u64) { assert!(user.is_authorized, ENotAuthorized); assert!(value >= 10, EValueTooLow); user.value = value; } ``` The attribute does not change what an abort is: the transaction still fails with a `u64` abort code. What changes is the content of that code - the compiler packs into it the source line number of the abort (for a macro like `assert!`, the line of the call site) and references to the constant's name and value. Tooling that understands the format - the Sui CLI, explorers, SDKs - unpacks it and shows the full picture, along the lines of: ```text Error from 'book::assert_abort::update_value' (line 15), abort 'EValueTooLow': "The value is too low, it should be at least 10" ``` Error messages remove the need to look up the meaning of a numeric code, which matters most in public-facing applications, where the person reading the failure is often not the author of the module. The flip side of the encoding is that the numeric value of a clever abort code depends on the source layout: reformatting the module or adding a line changes it. Refer to these constants by name - never by their compiled numeric value. The exact layout of the encoding is described in [Clever Errors](./../../reference/abort-and-assert/clever-errors) in the Move Reference. ## Aborts in Tests Aborting is a behavior worth testing like any other. The `#[expected_failure]` attribute marks a test that is supposed to abort, and its `abort_code` argument asserts the exact code - the test fails if the function succeeds or aborts with a different code. We cover this attribute in more detail in the [Testing](./testing) section. ## Further Reading - [Abort and Assert](./../../reference/abort-and-assert) in the Move Reference. - [Clever Errors](./../../reference/abort-and-assert/clever-errors) in the Move Reference. - We suggest reading the [Better Error Handling](./../guides/better-error-handling) guide to learn about best practices for error handling in Move. --- # References In the [Ownership and Scope](./ownership-and-scope) section, we explained that when a value is passed to a function, it is _moved_ to the function's scope. This means that the function becomes the owner of the value, and the original scope (owner) can no longer use it. This is an important concept in Move, as it ensures that the value is not used in multiple places at the same time. However, there are use cases when we want to pass a value to a function but retain ownership. This is where references come into play. To illustrate this, let's consider a simple example - an application for a metro (subway) pass. We will look at 4 different scenarios where a card can be: 1. Purchased at a kiosk for a fixed price 2. Shown to an inspector to prove that the passenger has a valid pass 3. Used at the turnstile to enter the metro, and purchase a ride 4. Recycled after it's empty ## The Metro Pass Application The initial layout of the metro pass application is simple. We define the `Card` type and the `USES` [constant](./constants) that represents the number of rides on a single card. We also add [error constants](./assert-and-abort#error-constants) for the case when the card is empty and when the card is not empty. ```move /// Error code for when the card is empty. const ENoUses: u64 = 0; /// Error code for when the card is not empty. const EHasUses: u64 = 1; /// Number of uses for a metro pass card. const USES: u8 = 3; /// A metro pass card public struct Card { uses: u8 } /// Purchase a metro pass card. public fun purchase(/* pass a Coin */): Card { Card { uses: USES } } ``` ## Immutable References References are a way to _show_ a value to a function without giving up ownership. In our case, when we show the Card to the inspector, we don't want to give up ownership of it, and we don't allow the inspector to use up any of our rides. We just want to allow the _reading_ of the value of our Card and to prove its ownership. To do so, in the function signature, we use the `&` symbol to indicate that we are passing a _reference_ to the value, not the value itself. ```move /// Show the metro pass card to the inspector. public fun is_valid(card: &Card): bool { card.uses > 0 } ``` Because the function does not take ownership of the Card, it can _read_ its data but cannot _write_ to it, meaning it cannot modify the number of rides. Additionally, the function signature ensures that it cannot be called without a Card instance. This is an important property that allows the [Capability Pattern](./../programmability/capability), which we will cover in the next chapters. The `&` operator is not limited to function signatures: it is an expression that can be applied to any value or to a single field of a struct. The resulting reference can be stored in a local variable and passed on: ```move let card = purchase(); let card_ref = &card; // reference to the whole value let uses_ref = &card.uses; // reference to a single field ``` Creating a reference to a value is often referred to as "borrowing" the value. For example, the method to get a reference to the value wrapped by an `Option` is called `borrow`. ## Mutable Reference In some cases, we want to allow the function to modify the Card. For example, when using the Card at a turnstile, we need to deduct a ride. To achieve this, we use the `&mut` keyword in the function signature. ```move /// Use the metro pass card at the turnstile to enter the metro. public fun enter_metro(card: &mut Card) { assert!(card.uses > 0, ENoUses); card.uses = card.uses - 1; } ``` As you can see in the function body, the `&mut` reference allows mutating the value, and the function can spend rides. A mutable reference can be used anywhere an immutable one is expected: passing `&mut card` to the `is_valid` function is perfectly fine, the function will simply not be able to modify the value. The reverse is not true - an immutable reference can never be turned into a mutable one. ## The Borrow Checker References are compiled with the help of the _borrow checker_ - the part of the compiler that tracks every borrow and rejects programs which could use references unsafely. The rules it enforces are: - While a value is borrowed, it cannot be moved, passed by value, or destroyed; - There can be either a single mutable reference to a value, or any number of immutable references - never both at the same time; - A reference cannot outlive the value it points to. To see the borrow checker in action, let's try to break the first rule and recycle the card while the inspector is still looking at it: ```move let card = purchase(); let card_ref = &card; recycle(card); // ERROR! Invalid move of the local `card`: // the value is still being borrowed by `card_ref`. is_valid(card_ref); ``` The compiler rejects this program: as long as `card_ref` is alive and used, the value it points to must stay in place. The same mechanism prevents two mutable references from existing at once, or a value from being modified while it is immutably borrowed. Thanks to these rules, a reference in Move can never point at destroyed or moved-away data, and functions can trust their arguments without any runtime checks. ## Passing by Value Lastly, let's illustrate what happens when we pass the value itself to the function. In this case, the function takes the ownership of the value, making it inaccessible in the original scope. The owner of the Card can recycle it and thereby relinquish ownership to the function. ```move /// Recycle the metro pass card. public fun recycle(card: Card) { assert!(card.uses == 0, EHasUses); let Card { uses: _ } = card; } ``` In the `recycle` function, the Card is passed by value, transferring ownership to the function. This allows it to be [unpacked](./struct#unpacking-a-struct) and destroyed. ## Returning References A function can not only take references - it can also return them. This is exactly how _getters_, which we mentioned in the [struct section](./struct#getters-and-setters), provide access to the fields of a struct from other modules. Let's add one to the metro pass application: ```move /// Getter: a reference to the `uses` field, derived from /// the `card` reference taken as an argument. public fun uses(card: &Card): &u8 { &card.uses } ``` A returned reference must point into a value that the caller owns - in other words, it must be _derived_ from one of the reference parameters of the function. Returning a reference to a local value is impossible, since the local is destroyed when the function returns: ```move // Won't compile! public fun dangling(): &u8 { let x = 10; &x // ERROR! The local `x` is destroyed at the end of the function. } ``` Returning a mutable reference to a field is also possible - and it is a decision to make carefully, as it allows any caller to modify the field directly. The borrow checker rules apply to returned references just as they do to local borrows: while the returned reference is alive, the value it was derived from stays borrowed. ## References Cannot Be Stored References in Move are _ephemeral_: they exist as function arguments, local variables, and return values, but they can never be put into a struct. A field of a reference type is a compilation error, so no value can carry a reference beyond the end of a function call. If a struct needs to refer to another value long-term, it stores a copy of the data or an identifier of it, never a reference. This restriction has consequences you will meet throughout the book. It is why references have only the `copy` and `drop` [abilities](./abilities-introduction) and can never be stored; and why collection types hand out a fresh reference on every `borrow` call instead of keeping one. It is also the reason Move needs no _lifetime_ annotations for references - a reference can never escape the call in which it was created. ## Dereferencing A reference gives access to a value, but sometimes the code holding a reference needs a copy of the value itself. The _dereference operator_ `*` reads the value behind a reference and produces a copy of it - the original value stays where it was, untouched: ```move #[test] fun test_dereference() { let mut card = purchase(); // A reference to the `uses` field - a `u8` value. let uses_ref = &card.uses; // The dereference operator `*` copies the value behind the reference. let uses: u8 = *uses_ref; assert!(uses == 3); // Writing through a mutable reference is also a dereference. *(&mut card.uses) = 0; assert!(card.uses == 0); recycle(card); } ``` Because dereferencing copies, it is only allowed for types with the [copy ability](./copy-ability) - a unique asset cannot be duplicated by taking a reference to it and dereferencing. The `*(&mut ...) = value` form in the example is the flip side of the same operator: assigning through a mutable reference replaces the value behind it. You may also encounter the `*&` combination - borrow and immediately dereference - which is the idiomatic way to write an explicit copy of a field or variable. ## Borrowing in Practice: Method Calls To illustrate the full flow of the application, let's put all the pieces together in a test. This time we will use the [method syntax](./struct-methods) instead of plain function calls: ```move #[test] fun test_card_2024() { // declaring variable as mutable because we modify it let mut card = purchase(); card.enter_metro(); // modify the card but don't move it assert!(card.is_valid()); // read the card! card.enter_metro(); // modify the card but don't move it card.enter_metro(); // modify the card but don't move it card.recycle(); // move the card out of the scope } ``` Notice that not a single `&` appears in the test, yet references are doing all the work. When a function is called with the method syntax, the compiler borrows the receiver _automatically_, based on the signature of the function: `card.is_valid()` borrows `card` immutably as `&Card`, `card.enter_metro()` borrows it mutably as `&mut Card`, and `card.recycle()` passes the value as is, by value. This is why everyday Move code rarely spells out the borrow operator - most borrows happen implicitly at method call sites, following the same borrow checker rules described above. ## Summary - References allow showing a value to a function without giving up ownership: `&` for read-only access, `&mut` for read-write access. - The borrow checker enforces the safety rules: no moving of borrowed values, a single `&mut` _or_ any number of `&`, and no reference may outlive its value. - Functions can return references derived from their reference parameters - the basis of getters. - References cannot be stored in structs - they never outlive the function call. - Method calls borrow the receiver automatically, based on the function signature. ## Further Reading - [References](./../../reference/primitive-types/references) in the Move Reference. --- # Generics Generics are a way to define a type or function that can work with any type, instead of one specific type. You have already used generics in this chapter, perhaps without noticing: the [vector](./vector) type is generic - a single definition can hold elements of any type - and so is [Option](./option), which can wrap any value. Generics are the foundation of collections, abstract implementations, and many advanced features of Move. ## The Problem Generics Solve Suppose we need a type that wraps a single `u64` value. Simple enough: ```move public struct U64Container has drop { value: u64, } ``` But what if we also need to wrap a `bool`? And a `String`? And a struct of our own? Each version would be identical except for the type of the `value` field, and every function that works with containers would need to be duplicated for each version: ```move public struct BoolContainer has drop { value: bool } public struct StringContainer has drop { value: String } // ...a new struct for every type we want to store ``` Generics solve exactly this problem: we define the container _once_, with a placeholder instead of a concrete type, and the placeholder is filled in when the type is used. ## Generic Syntax To define a generic type or function, add a list of _type parameters_ enclosed in angle brackets (`<` and `>`) after the name. Multiple type parameters are separated by commas. ```move /// Container for any type `T`. public struct Container has drop { value: T, } /// Function that creates a new `Container` with a generic value `T`. public fun new(value: T): Container { Container { value } } ``` In the example above, `Container` is a generic type with a single type parameter `T`, and the `value` field of the container stores a value of type `T`. `T` is not a real type - it is a placeholder that stands for "some type, to be specified later". The `new` function is a generic function with the same type parameter, and it returns a `Container` with the given value. > By convention, type parameters are named with single capital letters - `T`, `U`, `K`, `V`. > However, any valid name can be used: the standard library, for example, names the type parameter > of `vector` `Element`. ## Using Generic Types When we create an instance of a generic type, the placeholder is replaced with a concrete type. Each replacement produces a distinct type: `Container`, `Container`, and `Container` all come from the same definition, but they are three different types. The concrete type can be spelled out explicitly, or, in most cases, inferred by the compiler: ```move #[test] fun test_container() { // these three lines are equivalent let container: Container = new(10); // type inference let container = new(10); // create a new `Container` with a `u8` value let container = new(10u8); assert_eq!(container.value, 10); // Value can be ignored only if it has the `drop` ability. let Container { value: _ } = container; } ``` The first three lines of the test are equivalent - each creates a `Container`. Because numeric literals have ambiguous types, we have to specify the type of the number somewhere: in the type annotation of the variable, in the explicit type argument of `new`, or in the literal itself. Once one of these is given, the compiler infers the rest. For values with unambiguous types, such as `bool` or `String`, no annotations are needed at all. ## Multiple Type Parameters A type or function can have more than one type parameter, separated by commas: ```move /// A pair of values of any type `T` and `U`. public struct Pair { first: T, second: U, } /// Function that creates a new `Pair` with two generic values `T` and `U`. public fun new_pair(first: T, second: U): Pair { Pair { first, second } } ``` In the example above, `Pair` is a generic type with two type parameters `T` and `U`, and the `new_pair` function creates a `Pair` with the given values. ```move #[test] fun test_generic() { // these three lines are equivalent let pair_1: Pair = new_pair(10, true); // type inference let pair_2 = new_pair(10, true); // create a new `Pair` with a `u8` and `bool` values let pair_3 = new_pair(10u8, true); assert_eq!(pair_1.first, 10); assert_eq!(pair_1.second, true); // Unpacking is identical. let Pair { first: _, second: _ } = pair_1; let Pair { first: _, second: _ } = pair_2; let Pair { first: _, second: _ } = pair_3; } ``` The order of type parameters matters. A `Pair` and a `Pair` are two different, incompatible types - even though they are built from the same definition and store the same data: ```move #[test] fun test_swap_type_params() { let pair1: Pair = new_pair(10u8, true); let pair2: Pair = new_pair(true, 10u8); // this line will not compile // assert_eq!(pair1, pair2); let Pair { first: pf1, second: ps1 } = pair1; // first1: u8, second1: bool let Pair { first: pf2, second: ps2 } = pair2; // first2: bool, second2: u8 assert_eq!(pf1, ps2); // 10 == 10 assert_eq!(ps1, pf2); // true == true } ``` Since the types of `pair1` and `pair2` differ, the comparison `pair1 == pair2` would not compile. The values can only be compared field-by-field, after unpacking. ## Why Generics? So far we have focused on the mechanics: how to define generic types and create their instances. The real power of generics is in defining shared data and behavior once, and letting a part of the type vary. Consider a `User` type where the `name` and `age` fields are always the same, but different applications need to attach different extra data: ```move /// A user record with name, age, and some generic metadata public struct User { name: String, age: u8, /// Varies depending on application. metadata: T, } ``` Functions defined for `User` work no matter what `metadata` is - they operate on the shared fields and don't need to know the concrete type of `T`: ```move /// Updates the name of the user. public fun update_name(user: &mut User, name: String) { user.name = name; } /// Updates the age of the user. public fun update_age(user: &mut User, age: u8) { user.age = age; } ``` ```move #[test] fun test_user() { // In this instance, the `metadata` field is a `u64`... let mut user1 = User { name: "Alice", age: 30, metadata: 1000u64, }; // ...and in this instance, it is a `bool`. let mut user2 = User { name: "Bob", age: 40, metadata: true, }; // The same functions work for both instances. user1.update_name("Alice II"); user2.update_name("Bob II"); assert_eq!(user1.name, "Alice II"); assert_eq!(user2.name, "Bob II"); let User { .. } = user1; let User { .. } = user2; } ``` In the test above, one `User` instance stores a `u64` as its metadata, and the other stores a `bool`, yet both are updated with the same `update_name` function, defined once. ## Phantom Type Parameters Sometimes a type parameter is needed only as a _label_, without storing any value of that type. Consider a `Coin` type: the actual data is just a numeric `value`, the same for every currency. However, a US Dollar coin and a Euro coin must never be mixed up - they should be different types in the eyes of the compiler. To express this, the type parameter is declared `phantom` - a parameter that does not appear in any field: ```move /// A generic type with a phantom type parameter. public struct Coin { value: u64 } ``` > Move requires every regular type parameter to be used in the fields of the struct. Since `T` is > not stored anywhere in `Coin`, it must be marked with the `phantom` keyword. Currencies can then be defined as empty structs - they carry no data and exist only to be used as labels: ```move public struct USD {} public struct EUR {} #[test] fun test_phantom_type() { let coin1: Coin = Coin { value: 10 }; let coin2: Coin = Coin { value: 20 }; // This line will not compile: `Coin` and `Coin` // are different types and cannot be mixed up. // let mixed: Coin = coin2; // Unpacking is identical because the phantom type parameter is not used. let Coin { value: _ } = coin1; let Coin { value: _ } = coin2; } ``` Even though `Coin` and `Coin` store identical data, they are different types, and a function expecting one will not accept the other. This pattern is used extensively in real applications: the `Coin` type in the [Sui Framework](./../programmability/sui-framework) is defined in exactly this way. ## Constraints on Type Parameters By default, a type parameter accepts _any_ type. However, sometimes the inner type must allow certain behaviors, such as being copied or discarded, and for that the type parameter can be constrained to have certain [abilities](./abilities-introduction). The syntax is `T: + `: ```move /// A generic type with a type parameter that has the `drop` ability. public struct Droppable { value: T, } /// A generic struct with a type parameter that has the `copy` and `drop` abilities. public struct CopyableDroppable { value: T, // T must have the `copy` and `drop` abilities } ``` A constraint is a promise the concrete type must keep: the Move compiler only allows instantiating `Droppable` with types that have the [drop](./drop-ability) ability, and `CopyableDroppable` with types that have both [copy](./copy-ability) and `drop`. A type without those abilities does not compile: ```move /// Type without any abilities. public struct NoAbilities {} #[test] fun test_constraints() { // Fails - `NoAbilities` does not have the `drop` ability // let droppable = Droppable { value: 10 }; // Fails - `NoAbilities` does not have the `copy` and `drop` abilities // let copyable_droppable = CopyableDroppable { value: 10 }; } ``` ## Further Reading - [Generics](./../../reference/generics) in the Move Reference. --- # Macro Functions Throughout this chapter, we have called quite a few functions whose names end with an exclamation mark: the `assert!` and `assert_eq!` macros in tests, and the [vector macros](./vector#vector-macros) such as `map!` and `fold!`. All of them are _macro functions_, and now that we know [functions](./function) and [generics](./generics), we have everything needed to understand how they work - and how to define our own. ## What is a Macro Function? A macro function looks and feels like a regular function, but it does not exist at runtime. Instead, the compiler _expands_ the macro: at every call site, the body of the macro is substituted inline, with the arguments plugged into it, and only then is the resulting code type checked and compiled. A macro call is easy to recognize - the macro name is always followed by the `!` mark. This compile-time expansion gives macros two abilities that regular functions do not have: - They can take _lambdas_ - inline blocks of code - as arguments. Move has no function values at runtime, but because a macro is expanded during compilation, the lambda simply becomes part of the generated code. - Their bodies are type checked _after_ expansion, per call site, which permits operations that regular [generics](./generics) cannot express - as we are about to see. ## Defining a Macro A macro is defined with the `macro fun` keywords. The parameters - including type parameters - are prefixed with the `$` sign, marking them as compile-time substitutions rather than runtime values: ```move /// Returns the larger of the two values. public macro fun max<$T>($a: $T, $b: $T): $T { let a = $a; let b = $b; if (a > b) a else b } ``` The `max` macro returns the larger of its two arguments. Note something remarkable about the body: it compares two values of the generic type `$T` with the `>` operator. A regular generic function could not do this - there is no ability constraint for "comparable", so `fun max(a: T, b: T)` would not compile. The macro sidesteps the problem entirely: by the time the body is type checked, `$T` is already replaced with a concrete type at each call site: ```move assert_eq!(max!(1, 2), 2); assert_eq!(max!(10u8, 5), 10); assert_eq!(max!(100u128, 200), 200); ``` > Also note the `let a = $a;` binding at the top of the body. A macro argument is substituted as an > _expression_, not as a computed value: every occurrence of `$a` in the body would evaluate the > argument expression again. Binding the argument to a local variable once is a good habit that > avoids surprising double evaluation. ## Lambda Arguments The real power of macros comes from lambda parameters. A lambda type is written as `|argument_types|` (or `|argument_types| -> return_type` when it returns a value), and the caller passes the lambda inline, using the `|arguments| expression` syntax: ```move /// Calls the `$f` lambda `$n` times, passing in the iteration number. public macro fun repeat($n: u64, $f: |u64|) { let n = $n; let mut i = 0; while (i < n) { $f(i); i = i + 1; } } ``` ```move let mut sum = 0; repeat!(4, |i| sum = sum + i); assert_eq!(sum, 6); // 0 + 1 + 2 + 3 ``` A lambda can read and even modify the variables of the enclosing scope - the `repeat!` call above updates the local variable `sum` on every iteration. This is exactly the mechanism behind the [vector macros](./vector#vector-macros): `v.do!(|el| ...)` is a macro with a lambda parameter, expanded into a plain loop at compilation time. ## Lazy Evaluation Because arguments are substituted rather than computed up front, an argument expression may be evaluated once, many times - or not at all. The `assert!` macro is a good illustration: in `assert!(condition, EMyError)`, the error code expression is only evaluated when the condition fails. This is a feature - the failure branch costs nothing on the happy path - but it is also the flip side of the double-evaluation caveat above: when writing your own macros, think about how many times each `$` parameter is actually used. > Expansion at the call site has one more visible effect: an abort raised inside a macro body > reports the line number of the macro _call_, not a line inside the macro definition. This is part > of the [clever error](./assert-and-abort#error-messages) encoding, and it is why a failing > `assert!` or `assert_eq!` points at the line in your code rather than somewhere in the standard > library - a good reason to prefer a macro over a regular function when writing assertion helpers. ## Macros in the Standard Library The [Standard Library](./standard-library) makes heavy use of macros, and they are the idiomatic way to work with its core types. We have already seen the [vector macros](./vector#vector-macros); `Option` and the integer types have their own sets: ```move // `Option` macros: `destroy_or!` unwraps the value with a default... let opt = option::some(10); assert_eq!(opt.destroy_or!(0), 10); // ...and `map!` transforms the inner value, if it is present. let doubled = option::some(5).map!(|x| x * 2); assert_eq!(doubled, option::some(10)); // Integer macros iterate over numbers without a `while` loop. let mut sum = 0u64; 10u64.do!(|i| sum = sum + i); assert_eq!(sum, 45); // 0 + 1 + ... + 9 // And the `assert_eq!` macro, used all over this book, is // defined in the `std::unit_test` module. ``` A quick overview of where to find them: - [std::vector](https://docs.sui.io/references/framework/std/vector) - `do!`, `map!`, `filter!`, `fold!`, `count!`, `any!`, `all!`, `tabulate!`, and more; - [std::option](https://docs.sui.io/references/framework/std/option) - `do!`, `map!`, `destroy_or!`, `extract_or!`, `is_some_and!`; - integer modules, e.g. [std::u64](https://docs.sui.io/references/framework/std/u64) - `do!`, `range_do!`, `max_value!`; - [std::unit_test](https://docs.sui.io/references/framework/std/unit_test) - `assert_eq!` and `assert_ref_eq!`, available in tests. This section covers the day-to-day use of macros; the full feature set - including method syntax for macros, `$` expressions in type positions, and hygiene rules - is described in the Move Reference. ## Further Reading - [Macro Functions](./../../reference/functions/macros) in the Move Reference. --- # Internal Permit In the [Custom Types with Struct](./struct#field-visibility) section we established a rule that holds everywhere in Move: only the module that defines a type can access its fields, pack it, and unpack it. This makes the defining module the sole authority over its type - all other code has to go through the functions the module chooses to expose. However, this authority seems to disappear the moment a generic function enters the picture. A public generic function can be called by _any_ module with _any_ type argument - the library that defines the function has no way of knowing whether the caller has any relation to the type it was called with. The `std::internal` module closes this gap: it provides a value that proves the call was authorized by the module that defines the type. ## The Problem Let's make the problem concrete. Suppose we want to build a type registry - a place where a type can be registered under a human-readable name. A natural requirement: a type may only be registered by the module that defines it, so no one can claim a name for someone else's type. A first attempt at the signature would look like this: ```move /// Registers the type `T` under the given `name`. public fun register(registry: &mut Registry, name: String) { /* ... */ } ``` This function cannot enforce our requirement. Move has no way to inspect the caller at runtime - there is no "get the calling module" function, and this is by design: what a function does must be fully determined by its arguments. But that phrasing also points at the solution: if authorization cannot be observed, it must be _passed in_ - as an argument that only the right module is able to produce. ## The Permit Type The `std::internal` module is tiny - it defines one struct and one function: ```move module std::internal; /// A privileged witness of the `T` type. /// Instances can only be created by the module that defines the type `T`. public struct Permit() has drop; /// Construct a new `Permit` for the type `T`. /// Can only be called by the module that defines the type `T`. public fun permit(): Permit { Permit() } ``` At first glance, there is nothing here: a public struct with no fields and a public function that anyone should be able to call. The important part is the claim in the comment - `permit()` can only be called by the module that defines `T`. Regular Move code cannot express such a restriction, and indeed it is not expressed in the code: it is a special rule, checked by the compiler and by the network when the package is published. We will see it in action in a moment. Two details of the definition are worth noting: - The type parameter is [phantom](./generics#phantom-type-parameters) - a `Permit` does not contain a `T`, so a permit can be created for a type without constructing an instance of it. - The only ability is `drop`: a permit can be discarded, but it cannot be copied and cannot be stored. Whoever receives a `Permit` holds a proof that cannot be duplicated or stashed away for later. ## Using a Permit To put the rule to work, a library function lists `Permit` as an argument. That is the entire recipe: since only the module defining `T` can create the value, receiving it _is_ the authorization. Here is the registry from our problem statement, fixed: ```move /// A registry where a type can be registered under a human-readable /// name, but only by the module that defines the type. module book::type_registry; use std::string::String; /// Stores the names of registered types. public struct Registry has drop { names: vector, } /// Creates a new, empty `Registry`. public fun new(): Registry { Registry { names: vector[] } } /// Registers the type `T` under the given `name`. The `Permit` /// argument proves that the call was authorized by the module /// that defines `T`. public fun register(registry: &mut Registry, _permit: internal::Permit, name: String) { registry.names.push_back(name); } /// Returns the number of registered types. public fun size(registry: &Registry): u64 { registry.names.length() } ``` The `register` function does not even look at the permit - the underscore in `_permit` says it is intentionally unused. Its type is the check. > `std::internal`, like `std::option` and `std::vector`, is > [imported implicitly](./standard-library#implicit-imports) - no `use` statement is needed. The > recommended style is to keep the module prefix: write `internal::Permit` in signatures and > `internal::permit()` at call sites, instead of importing `Permit` directly. On the other side, the module that defines a type creates a permit and passes it along: ```move /// A module that registers its own type in the `type_registry`. module book::registry_user; use book::type_registry::Registry; /// The type we are going to register. public struct MyApp() /// Registers `MyApp` in the given registry. The permit can only be /// created here - in the module that defines `MyApp`. public fun register_my_app(registry: &mut Registry) { let permit = internal::permit(); registry.register(permit, "My App"); } ``` The registration can now be exercised in a test: ```move let mut registry = type_registry::new(); register_my_app(&mut registry); assert_eq!(registry.size(), 1); ``` ## Breaking the Rule What stops a third module from creating a permit for `MyApp` and registering it under a misleading name? Let's try: ```move module book::registry_intruder; use book::registry_user::MyApp; use book::type_registry::Registry; public fun register_foreign_type(registry: &mut Registry) { let permit = internal::permit(); // ERROR! registry.register(permit, "Not My App"); } ``` The code above will not compile: ```text error[Sui E02011]: invalid internal permit call ┌─ sources/registry_intruder.move:7:18 │ 7 │ let permit = internal::permit(); │ ^^^^^^^^^^^^^^^^^^^^^^^^^ │ │ │ │ │ The type 'book::registry_user::MyApp' is not declared in the current module │ Invalid call to an internal function. The function 'std::internal::permit' is │ restricted to being called in the module that defines the type, 'book::registry_user' ``` The check does not stop at the compiler. The same rule is enforced by the bytecode verifier when a package is published onchain, so it cannot be bypassed by hand-crafting bytecode or using a modified compiler. A published `Permit` is a hard guarantee: if a function received one, the module defining `T` created it. Type parameters restricted this way are called _internal type parameters_, and `permit` is not the only function that has one: `sui::event::emit` and `sui::transfer::transfer`, which we cover in the [Events](./../programmability/events) and [Storage Functions](./../storage/storage-functions) sections, follow the same rule. What `std::internal` adds is a way for _any_ library to demand this guarantee: the special rule applies only to the creation of the permit, and from there it travels as an ordinary value to any function that lists it as an argument. ## Why It Works This Way The design of `Permit` follows a general Move principle: authority is represented by values, not by runtime checks. A function proves it is allowed to do something by _possessing_ a value that could only be created in an authorized place. This idea appears throughout Move and Sui - it is the basis of the [Witness](./../programmability/witness-pattern) and [Capability](./../programmability/capability) patterns - and `Permit` is its most compact form: a standard, zero-field witness meaning "the module that defines `T` approved this call". The abilities of `Permit` are chosen to keep that meaning precise. Without `copy`, a function that receives a permit cannot duplicate it; without `store`, it cannot be kept onchain and reused later. The authorization is valid for the current call and then gone - every privileged action requires the defining module to explicitly create a new permit. And because the type parameter is `phantom`, the proof is free: no instance of `T` is created, copied, or consumed to produce it. ## Further Reading - [std::internal](https://docs.sui.io/references/framework/std/internal) module documentation. - [Witness Pattern](./../programmability/witness-pattern) - the broader pattern behind `Permit`. --- # Type Reflection In programming languages, _reflection_ is the ability of a program to examine and modify its own structure and behavior. Move supports a limited form of reflection that lets you inspect the type of a value at runtime. This is handy when you need to store type information in a homogeneous collection, or when you want to check if a type comes from a particular package. Type reflection is implemented in the [Standard Library](./standard-library) module [`std::type_name`][type-name-stdlib]. Its main functions are `with_defining_ids` and `with_original_ids`, which capture the type as a `TypeName` value, and their lighter counterparts that return only the package address: ```move let defining_type_name: TypeName = type_name::with_defining_ids(); let original_type_name: TypeName = type_name::with_original_ids(); // Returns only "ID" of the package. let defining_package: address = type_name::defining_id(); let original_package: address = type_name::original_id(); ``` ## Defining IDs vs. Original IDs It is important to understand the difference between _defining ID_ and _original ID_. - Original ID is the first published ID of the package (before the first upgrade). - Defining ID is the package ID which introduced the reflected type, this property becomes crucial when new types are introduced in package upgrades. For example, suppose the first version of a package was published at `0xA` and introduced the type `Version1`. Later, in an upgrade, the package moved to address `0xB` and introduced a new type `Version2`. For `Version1`, the defining ID and original ID are the same. For `Version2`, however, they differ: the original ID is `0xA`, while the defining ID is `0xB`. ```move // Note: values `0xA` and `0xB` are used for illustration purposes only! // Don't attempt to run this code, as it will inevitably fail. module book::upgrade; // Introduced in initial version. // Defining ID: 0xA // Original ID: 0xA // // With Defining IDs: 0xA::upgrade::Version1 // With Original IDs: 0xA::upgrade::Version1 public struct Version1 has drop {} // Introduced in a package upgrade. // Defining ID: 0xB // highlight-important // Original ID: 0xA // // With Defining IDs: 0xB::upgrade::Version2 // highlight-important // With Original IDs: 0xA::upgrade::Version2 public struct Version2 has drop {} ``` ## In Practice The module is straightforward: the operations allowed on the resulting `TypeName` are limited to getting a string representation and extracting the module name and address of the type. ```move module book::type_reflection; use std::ascii::String; use std::type_name::{Self, TypeName}; /// A function that returns the name of the type `T` and its module and address. public fun do_i_know_you(): (String, String, String) { let type_name: TypeName = type_name::with_defining_ids(); // there's a way to borrow let str: &String = type_name.as_string(); let module_name: String = type_name.module_string(); let address_str: String = type_name.address_string(); // and a way to consume the value let str = type_name.into_string(); (str, module_name, address_str) } #[test_only] public struct MyType {} #[test_only] use std::unit_test::assert_eq; #[test] fun test_type_reflection() { let (type_name, module_name, _address_str) = do_i_know_you(); assert_eq!(module_name, "type_reflection"); } ``` ## Further Reading - [std::type_name][type-name-stdlib] module documentation. [type-name-stdlib]: https://docs.sui.io/references/framework/std/type_name --- # Testing Move has a built-in testing framework that lets you write unit tests alongside your code. Tests are functions marked with the `#[test]` attribute, excluded from the published bytecode, and run with the `sui move test` command. The framework supports expected failures via `#[expected_failure]` and test-only helpers via `#[test_only]`. ```move module book::testing; #[test_only] use std::unit_test::assert_eq; // test functions take no arguments and return nothing #[test] fun simple_test() { let sum = 2 + 2; assert_eq!(sum, 4); } #[test, expected_failure(abort_code = 0)] fun test_fail() { abort 0 } ``` A test passes if it runs to completion and fails if it aborts - which is exactly what the `assert_eq!` macro does when its two values differ. For arbitrary conditions there is the more general [`assert!`](./assert-and-abort) macro; both are the workhorses of Move tests. The second test above inverts the outcome: `#[expected_failure(abort_code = 0)]` makes the test pass only if it aborts with the given code, which is the way to test error conditions. ## Test-Only Code The `#[test_only]` attribute marks a module member - or an entire module - as compiled only for tests. Test helpers, mock constructors, and imports like the `std::unit_test` import above are marked this way: the published bytecode stays free of testing machinery, while tests get access to everything they need, including things the public API deliberately does not expose. ## Explore More This page only scratches the surface. The dedicated [Testing](./../testing/index.md) chapter walks through test scenarios, coverage reports, gas profiling, working with system objects, and best practices for writing tests you can actually trust in production. ## What's Next This page concludes the Move Basics chapter. You can now define modules and custom types, control whether values can be copied or discarded, pass them around by reference or by value, write logic with pattern matching, abstract it with generics and macros - and test all of it. What we have set aside so far is what makes Move on Sui special: the storage model. The [Object Model](./../object/) chapter picks up exactly there - it introduces _objects_, the Move structs that become onchain assets, and the chapters after it show how to store, own, and transfer them. ## Further Reading - [Unit Testing](./../../reference/unit-testing) in the Move Reference. --- # Object Model So far we have studied Move as a language: types, functions, and abilities, all operating on values that live and die within a single transaction. But a smart contract is only useful if its state persists. This chapter introduces the _Object Model_ - the answer Sui gives to the question of how data is stored, owned, and accessed onchain. The chapter focuses on theory and concepts, preparing you for a practical dive into storage operations and resource ownership. It reads best in order: - [Language for Digital Assets](./digital-assets) - why Move treats assets as first-class values, and which properties make an asset; - [Evolution of Move](./evolution-of-move) - how the original account-based storage model worked, and why Sui replaced it; - [What is an Object?](./object-model) - the object as the unit of storage: type, ID, owner, version, and digest; - [Ownership](./ownership) - the five ways an object can be owned, and what each of them allows; - [Fast Path and Consensus](./fast-path-and-consensus) - how ownership determines the way a transaction is executed. The chapters that follow build directly on these concepts: [Using Objects](./../storage) shows how objects are defined and managed in code, and [Advanced Programmability](./../programmability) covers the features built on top of them. > This chapter is a high-level overview of the concepts and principles behind the Object Model. For > a more detailed, protocol-level description, refer to the > [Sui Documentation](https://docs.sui.io/guides/developer/objects/object-model). --- # Move - Language for Digital Assets Smart-contract programming languages have historically focused on defining and managing digital assets. For example, the ERC-20 standard in Ethereum pioneered a set of standards to interact with digital currency tokens, establishing a blueprint for creating and managing digital currencies on the blockchain. Subsequently, the introduction of the ERC-721 standard marked a significant evolution, popularizing the concept of non-fungible tokens (NFTs), which represent unique, indivisible assets. These standards laid the groundwork for the complex digital assets we see today. However, Ethereum's programming model lacked a native representation of assets. From the outside, an ERC-20 token behaved like an asset, but inside the contract it existed only as entries in a ledger - a mapping of addresses to balances - with no value in the language that _is_ the asset. From the start, Move aimed to provide a first-class abstraction for assets, opening up new avenues for thinking about and programming assets. It is important to highlight which properties are essential for an asset: - **Ownership:** Every asset is associated with an owner, mirroring the straightforward concept of ownership in the physical world, just as you own a car, you can own a digital asset. Move enforces ownership in such a way that once an asset is _moved_, the previous owner completely loses any control over it. This mechanism ensures a clear and secure change of ownership. - **Non-copyable:** In the real world, unique items cannot be duplicated effortlessly. Move applies this principle to digital assets, ensuring they cannot be arbitrarily copied within the program. This property is crucial for maintaining the scarcity and uniqueness of digital assets, mirroring the intrinsic value of physical assets. - **Non-discardable:** Just as you cannot accidentally lose a house or a car without a trace, Move ensures that no asset can be discarded or lost in a program. Instead, assets must be explicitly transferred or destroyed. This property guarantees the deliberate handling of digital assets, preventing accidental loss and ensuring accountability in asset management. You have already met all three of these properties as language features. Ownership is enforced by [move semantics](./../move-basics/ownership-and-scope): passing a value by value _moves_ it, and the previous scope loses access. And the ability system controls the other two: a struct without the [`copy`](./../move-basics/copy-ability) ability cannot be duplicated, and a struct without the [`drop`](./../move-basics/drop-ability) ability cannot be thrown away. What looked like a set of restrictions in the [Move Basics](./../move-basics) chapter turns out to be the exact toolkit for modeling assets: a type with neither `copy` nor `drop` _must_ be explicitly handled - stored, transferred, or destroyed - every time it is created. ## Summary - Move was designed to provide a first-class abstraction for digital assets, enabling developers to create and manage assets natively. - Essential properties of digital assets include ownership, non-copyability, and non-discardability, which Move enforces in its design. - These properties map directly onto language features you already know: move semantics and the `copy` and `drop` abilities. - Move's asset model mirrors real-world asset management, ensuring secure and accountable asset ownership and transfer. ## Further Reading - [Move: A Language With Programmable Resources (pdf)](https://developers.diem.com/papers/diem-move-a-language-with-programmable-resources/2019-06-18.pdf) by Sam Blackshear, Evan Cheng, David L. Dill, Victor Gao, Ben Maurer, Todd Nowacki, Alistair Pott, Shaz Qadeer, Rain, Dario Russi, Stephane Sezer, Tim Zakian, Runtian Zhou\* --- # Evolution of Move Move was created at [Diem](https://www.diem.com/en-us) to manage digital assets, and its original storage model reflected the design of that blockchain. Storage was _account-based_: every piece of data - called a _resource_ - lived under an account address, and a module could store, read, and remove resources only under the accounts that interacted with it. In its original form, Move had dedicated global storage operators for this, and a resource could only be placed under an account if that account agreed to it by signing a transaction. This model had practical consequences that made everyday asset operations surprisingly hard: - There was no built-in _transfer_ operation. If Alice wanted to send an asset X to Bob, the module defining X had to implement transfer logic itself: Bob first had to publish an "empty" resource under his account (agreeing to receive the asset), and only then could Alice's transaction move the balance into it. Every module reinvented this dance. - Assets were stored per-type, per-account. Managing a heterogeneous collection - say, a single account holding many different kinds of items - required significant effort and preparation for each new type. - Because data lived under accounts, an asset did not have an identity of its own: there was no way to point at "this specific item" and follow it across owners. Sui addressed these challenges by redesigning the storage model around the assets themselves. In Sui, the unit of storage is not an account but an _object_ - a typed value with its own unique identifier and an owner recorded by the system. Ownership and _transfer_ became native operations: Alice can directly transfer asset X to Bob, without Bob preparing anything in advance, and Bob can hold any number of assets of any types. The global storage operators of the original Move are absent in Move on Sui - in the [Using Objects](./../storage) chapter, we will see that they are replaced by functions operating on objects. These changes laid the foundation for the Object Model, which we describe in the next section. ## Summary - Original Move used account-based global storage: resources lived under account addresses, there was no native transfer operation, and heterogeneous collections were hard to manage. - Sui redesigned storage around _objects_ - typed values with their own identity and system-tracked ownership - making transfer a native operation. - Move on Sui removes the global storage operators, replacing them with object storage functions. ## Further Reading - [Why We Created Sui Move](https://blog.sui.io/why-we-created-sui-move/) by Sam Blackshear --- # What is an Object? An _object_ is the unit of storage on Sui. Where the original Move stored data under accounts, Sui stores objects directly in the global state, each with its own identity, type, and owner recorded by the system. Objects support native storage operations like _transfer_ and _share_, and are designed to make the asset properties from the [previous sections](./digital-assets) - ownership, non-copyability, non-discardability - practical to work with. In Move code, an object is not a new kind of value - it is a regular [struct](./../move-basics/struct) with the `key` ability and a special `id` field: ```move /// A game character; a struct like any other, made an object /// by the `key` ability and the `id: UID` field. public struct Hero has key { id: UID, level: u8, } ``` Everything you know about structs still applies. What the object adds is the system-level metadata attached to it in storage. We cover the definition rules in detail in the [Using Objects](./../storage) chapter; here we focus on the properties every object has: - **Type:** Every object has a type, defining the structure and behavior of the object. Objects of different types cannot be mixed or used interchangeably, ensuring objects are used correctly according to their type system. - **Unique ID:** Each object has a unique identifier, distinguishing it from other objects. This ID is generated upon the object's creation and is immutable, so an object can be tracked and referenced across transactions and owners. This is the `id: UID` field in the definition above. - **Owner:** Every object is associated with an owner, who has control over changes to the object. An object can be owned exclusively by an account, owned by another object, shared with the whole network, made immutable, or held in the _party_ state - a middle ground between exclusive and shared ownership. We discuss all five ownership states in detail in the [Ownership](./ownership) section. Note that ownership does not control the confidentiality of an object — it is always possible to read the contents of an onchain object from outside of Move. You should never store unencrypted secrets inside of objects. - **Data:** Objects encapsulate their data, simplifying management and manipulation. The data structure and operations are defined by the object's type - the fields of the struct. - **Version:** Every object carries a version number, which the system increments each time a transaction modifies the object. The version protects against _replay_: a transaction refers to its input objects at specific versions, so the same transaction - or a stale reference to an already-changed object - cannot be executed twice. It plays the role a _nonce_ plays in account-based blockchains, but per object rather than per account. - **Digest:** Every object has a digest, which is a hash of the object's data. The digest is used to cryptographically verify the integrity of the object's data and ensure that it has not been tampered with. It is recalculated whenever the object's data changes. ## Summary - Objects are the unit of storage on Sui: typed values stored in the global state with system-tracked identity and ownership. - In Move code, an object is a struct with the `key` ability and an `id: UID` field. - Every object has a type, unique ID, owner, data, version, and digest. ## Further Reading - [Object Model](https://docs.sui.io/guides/developer/objects/object-model) in Sui Documentation. --- # Ownership Every object on Sui is in one of five ownership states: _single owner_, _shared_, _immutable (frozen)_, _object owner_, or _party_. Each model offers unique characteristics and suits different use cases, and - as we will see in the [next section](./fast-path-and-consensus) - the choice of ownership also determines how transactions touching the object are executed. See the [Storage Functions](../storage/storage-functions.md) section for details on how to change the owner or ownership type of an object. ## Account Owner (or Single Owner) The account owner, also known as the _single owner_ model, is the foundational ownership type in Sui. Here, an object is owned by a single account, granting that account exclusive control over the object within the behaviors associated with its type. This model embodies the concept of _true ownership_: only the owner can use the object in a transaction - whether to read it, modify it, or transfer it away - and nobody else can touch it. This level of ownership clarity is a significant advantage over other blockchain systems, where ownership definitions can be more ambiguous, and smart contracts may have the ability to alter or transfer assets without the owner's consent. Think of it like your mobile phone: you can unlock and operate it, and others cannot. Sui enforces this at the system level - there is no way to "crack the password" and use an object that belongs to someone else, so no one can use your assets unless you authorize it. ## Shared State The single owner model has its limitations. Consider a marketplace for digital assets: Alice owns an asset X and wants to list it for sale, so that Bob - or anyone else - can come and buy it. With only single-owner objects this is surprisingly hard to express: for the sale to happen without Alice's participation, the asset has to sit in a place that both the seller and any future buyer can access, and no single account can be its owner. To solve the problem of shared data access, Sui offers the _shared_ ownership model. A shared object belongs to the network: it can be read and modified by any account, and the rules of interaction are defined by the module that implements the object. Typical uses for shared objects are marketplaces, shared resources, escrows, and other scenarios where multiple accounts need access to the same state. ## Party Objects The newest ownership state, the _party_ object, sits between the two models above: like a single-owner object, it has an owner - an address whose permission is required to use it - but, like a shared object, transactions touching it are ordered by consensus. Today a party object is always owned by a single address; the state is designed to eventually support more complex configurations, with permissions split between multiple parties. Party objects trade away the speed of exclusive ownership for the flexibility of consensus ordering - useful for assets that are frequently touched by high-traffic services, where many independent transfers to and from the same owner may be in flight at once. For most applications, they are an advanced option rather than the starting point: begin with single-owner objects, and reach for party objects when a concrete need arises. > Party objects are listed here for the complete picture. Their transfer functions are covered in > [Appendix C: Transfer Functions](./../appendix/transfer-functions#party), and the > [`sui::party`](https://docs.sui.io/references/framework/sui/party) module documentation covers > the details. ## Immutable (Frozen) State Sui also offers the _frozen object_ model, where an object becomes permanently read-only. These immutable objects, while readable, cannot be modified, transferred, or deleted, providing a stable and constant state accessible to all network participants. Frozen objects are ideal for public data, reference materials, and other use cases where state permanence is desirable. ## Object Owner The last ownership model in Sui is the _object owner_: an object owned by another object. This feature allows creating complex relationships between objects, storing large heterogeneous collections, and implementing extensible and modular systems. Since transactions are initiated by accounts, a transaction accesses the parent object first, and reaches the child objects through it. A use case we love to mention is a game character. Alice can own the Hero object from a game, and the Hero can own items: also represented as objects, like a "Map", or a "Compass". Alice may take the "Map" from the "Hero" object, and then send it to Bob, or sell it on a marketplace. With object owner, it becomes very natural to imagine how the assets can be structured and managed in relation to each other. > There are two mechanisms behind parent-child relations, both covered later in the book: > [Dynamic Fields](./../programmability/dynamic-fields) and > [Transfer to Object](./../storage/transfer-to-object). ## Summary - **Single Owner:** Objects are owned by a single account, granting exclusive control over the object. - **Shared State:** Objects can be shared with the network, allowing multiple accounts to read and modify the object. - **Party:** Objects have a single owner but are sequenced through consensus - a newer, advanced option. - **Immutable State:** Objects become permanently read-only, providing a stable and constant state. - **Object Owner:** Objects can own other objects, enabling complex relationships and modular systems. ## Next Steps In the next section we will talk about transaction execution paths in Sui, and how the ownership models affect the transaction execution. --- # Fast Path and Consensus The Object Model allows for variable transaction execution paths, depending on the object's ownership type. The transaction execution path determines how the transaction is processed and validated by the network. In this section, we'll explore the different transaction execution paths in Sui and how they interact with the consensus mechanism. ## Concurrency Challenge At its core, blockchain technology faces a fundamental concurrency challenge: multiple parties may try to modify or access the same data simultaneously in a decentralized environment. This requires a system for sequencing and validating transactions to support the network's consistency. Sui addresses this challenge through a consensus mechanism, ensuring all nodes agree on the transactions' sequence and state. Consider a marketplace scenario where Alice and Bob simultaneously attempt to purchase the same asset. The network must resolve this conflict to prevent double-spending, ensuring that at most one transaction succeeds while the other is rightfully rejected. ## Fast Path However, not all transactions require the same level of validation. If Alice transfers an object she owns to Bob, no other party could have touched that object in the first place - Alice is its single owner. There is no conflict to resolve, so the network does not need to order this transaction against all other transactions in the network. Transactions that access only account-owned objects take the _fast path_: they skip full sequencing and are processed quickly. This is a direct payoff of the [single owner](./ownership#account-owner-or-single-owner) model - exclusive access removes the concurrency problem entirely. Immutable objects also qualify for the fast path. Since a [frozen object](./ownership#immutable-frozen-state) can never change, any number of transactions can read it concurrently without any ordering. ## Consensus Path Transactions that access _shared_ objects are the case consensus exists for: multiple parties may attempt to modify the same object at the same time, so the network must agree on the order of these modifications. Such transactions go through the _consensus path_ - they are sequenced by the consensus protocol before execution, which keeps the state consistent across all nodes. [Party objects](./ownership#party-objects) also take the consensus path, even though they have a single owner - that is precisely their trade-off: owner-only access with consensus ordering. An important detail: consensus on Sui orders transactions _per object_, not globally. Two transactions touching two unrelated shared objects do not compete with each other - only transactions accessing the _same_ shared object need to be ordered relative to each other. This is what allows Sui to execute non-conflicting transactions in parallel. A single transaction can mix inputs: if it accesses both owned and shared objects, it goes through consensus - the execution path is determined by the "slowest" input. This is worth keeping in mind when designing an application: whether your central state is a shared object or stays within owned objects directly affects how your users' transactions are executed. ## Objects Owned by Objects Lastly, objects owned by other objects follow the execution path of their parent - a child is only reachable through its parent, so accessing it means accessing the parent first. If the parent object is _shared_, working with the child requires consensus; if the parent is account-owned, the whole chain qualifies for the fast path. ## Summary - **Fast Path:** Transactions involving only account-owned or immutable objects are processed quickly without full consensus sequencing. - **Consensus Path:** Transactions involving shared or party objects are sequenced by consensus - per object, allowing non-conflicting transactions to run in parallel. - **Mixed Inputs:** A transaction touching both owned and shared objects goes through consensus. - **Objects Owned by Objects:** Child objects follow the execution path of their parent. ## Next Steps This concludes the conceptual tour of the Object Model: you know what an object is, who can own it, and how ownership shapes execution. The next chapter - [Using Objects](./../storage) - turns these concepts into code: how to define an object, and how to transfer, share, and freeze it from a Move module. --- # Using Objects The [Object Model][object-model] chapter introduced objects conceptually: the unit of storage, with an identity, an owner, and an ownership state that shapes execution. This chapter turns those concepts into code. You will learn how to define an object type, how to create and destroy objects, and how to move them between ownership states - transfer, freeze, and share. The sections build on each other and are meant to be read in order: - [Ability: Key](./key-ability) - the ability that turns a struct into an object; - [Ability: Store](./store-ability) - the ability that permits a type to be stored inside objects, and controls who can operate on the object; - [Sui Verifier: Internal Constraint](./internal-constraint) - the bytecode-level rule that reserves critical operations for the module defining the type; - [Storage Functions](./storage-functions) - the operations that place objects into storage: transfer, freeze, and share; - [UID and ID](./uid-and-id) - the identity of every object, and its lifecycle; - [Receiving as Object](./transfer-to-object) - the mechanism that lets objects own other objects. > Two types from the [Sui Framework](./../programmability/sui-framework) appear in almost every > example of this chapter: `UID` - the unique identifier stored in every object - and `TxContext` - > a special value describing the current transaction, available to any function as its last > argument. Both are covered in depth later ([UID and ID](./uid-and-id) in this chapter, > [Transaction Context](./../programmability/transaction-context) in the next one); to get > started, it is enough to know that `object::new(ctx)` uses the transaction context to produce a > fresh, unique `UID`. If you haven’t read the [Object Model][object-model] chapter yet, we recommend starting there before continuing. [object-model]: ./../object --- # Ability: Key In the [Move Basics][basic-syntax] chapter, we covered two of the four abilities - [Drop][drop-ability] and [Copy][copy-ability]. They affect the behavior of a value within a scope, and are not related to storage. Now it is time to cover the `key` ability - the ability that allows a struct to become a unit of storage. Historically, the `key` ability was created to mark a type as a _key in global storage_. A type with the `key` ability could be stored at the top level and could be _owned_ by an account or address. With the introduction of the [Object Model][object-model], the `key` ability became the defining ability for _objects_. > Throughout the book, we refer to any struct with the `key` ability as an _object_. ## Object Definition A struct with the `key` ability is an object, and can be used in [storage functions](./storage-functions). Two layers of rules apply to its definition: - The Move language requires every field of a `key` struct to have the [`store`][store-ability] ability - we explore `store` on the next page; - The Sui Verifier additionally requires the first field of the struct to be named `id` and to have the type `UID`. ```move /// An object: a struct with the `key` ability and an `id: UID` field. public struct User has key { id: UID, // required by the Sui Verifier, always the first field name: String, // all other fields must have `store` } /// Creates a new `User` object. The fresh `UID` is derived from the /// transaction context `ctx`. public fun new(name: String, ctx: &mut TxContext): User { User { id: object::new(ctx), name, } } ``` The `new` function creates the object. A fresh `UID` can only be produced by `object::new`, which takes a mutable reference to the [transaction context](./../programmability/transaction-context) - so every newly created object gets an identifier that has never existed on the network before. We look closer at the `UID` type and its guarantees in the [UID and ID](./uid-and-id) section. ## Relation to `copy` and `drop` `UID` is a type that has neither [`drop`][drop-ability] nor [`copy`][copy-ability]. Since every object is required to have a `UID` field, and a struct can only have an ability its fields support, this means that objects can never have `drop` or `copy`. Every object is non-discardable and non-copyable by construction - which is exactly what the [asset properties](./../object/digital-assets) demand. This property can be leveraged in [ability constraints][generics]: requiring `drop` or `copy` automatically excludes objects, and conversely, requiring `key` excludes types with `drop` or `copy`. ## Types with the `key` Ability Due to the `UID` requirement, none of the native types in Move can have the `key` ability, nor can any of the types in the [Standard Library][standard-library]. The `key` ability is present only in some [Sui Framework][sui-framework] types and in custom types. ## Summary - The `key` ability defines an object. - The Sui Verifier requires the first field of an object to be `id` of type `UID`. - The Move language requires all fields of a `key` struct to have [`store`][store-ability]. - Objects can never have [`drop`][drop-ability] or [`copy`][copy-ability]. ## Next Steps The `key` ability defines objects and forces all fields to have `store`. In the next section, we look at the `store` ability itself - and at the second, less obvious role it plays for objects. ## Further Reading - [Type Abilities](./../../reference/abilities) in the Move Reference. [drop-ability]: ./../move-basics/drop-ability [copy-ability]: ./../move-basics/copy-ability [store-ability]: ./store-ability [generics]: ./../move-basics/generics#constraints-on-type-parameters [sui-framework]: ./../programmability/sui-framework [standard-library]: ./../move-basics/standard-library [object-model]: ./../object [basic-syntax]: ./../move-basics --- # Ability: Store The [`key` ability][key-ability] requires all fields to have `store`, and that requirement is the best way to understand what `store` means: it is the ability to be _stored_ - to end up inside an object in the blockchain state. A struct with [`copy`][copy-ability] or [`drop`][drop-ability] but without `store` can live only during the transaction that creates it; it can never be persisted. ## Definition The `store` ability allows a type to be used as a field of a struct with the `key` ability - directly, or nested any number of levels deep. Like with other abilities, the rule applies recursively: a struct can only have `store` if all of its fields have `store`. ```move /// Extra metadata with `store`; all of its fields must have `store` as well! public struct Metadata has store { bio: String, } /// An object for a single user record. public struct User has key { id: UID, name: String, // `String` has `store` age: u8, // all integers have `store` metadata: Metadata, // another type with the `store` ability } ``` ## Relation to `copy` and `drop` `store` is independent of `copy` and `drop`: the three non-`key` abilities can be combined freely, and none of them implies another. A type may be copyable but not storable, storable but neither copyable nor droppable, and so on - each combination is valid and has its uses. ## Relation to `key` An _object_ can also have the `store` ability, and for objects it plays a double role: - An object with `store` can be _wrapped_: used as a field of another object. An object without `store` is constrained to always remain at the top level of storage. - `store` acts as a _public_ modifier on the object: it permits calling the public [storage functions](./storage-functions) - `public_transfer`, `public_share_object`, and `public_freeze_object` - from _any_ module. Without `store`, storage operations on the object are reserved for its defining module, which keeps full control over how the object moves. The second role is not a language feature but a convention of the [Sui Framework][sui-framework], enforced through the [internal constraint](./internal-constraint) - the topic of the next section. Whether to give an object `store` is one of the most consequential design decisions in a Sui application, and we return to it in [Storage Functions](./storage-functions#internal-rule-in-transfer-functions). ## Types with the `store` Ability All native types (except references) in Move have the `store` ability. This includes: - [bool](./../move-basics/primitive-types#booleans) - [unsigned integers](./../move-basics/primitive-types#integer-types) - [`vector`](./../move-basics/vector) when `T` has `store` - [address](./../move-basics/address) All of the types defined in the standard library have the `store` ability as well. This includes: - [`Option`](./../move-basics/option) when `T` has `store` - [String](./../move-basics/string) and [ASCII String](./../move-basics/string#ascii-strings) - [TypeName](./../move-basics/type-reflection) ## Summary - `store` allows a type to be persisted - used as a field of an object, at any nesting depth. - For objects, `store` additionally unlocks _wrapping_ and the public storage functions. - `store` is independent of `copy` and `drop`; container types have it conditionally on their contents. ## Further Reading - [Type Abilities](./../../reference/abilities) in the Move Reference. [key-ability]: ./key-ability [drop-ability]: ./../move-basics/drop-ability [copy-ability]: ./../move-basics/copy-ability [sui-framework]: ./../programmability/sui-framework --- # Sui Verifier: Internal Constraint In the [Internal Permit](./../move-basics/internal-permit) section, we introduced _internal type parameters_: type parameters that only accept types defined in the calling module. There, `std::internal::permit()` used the rule to produce a proof value. On Sui, the same rule protects a handful of critical framework functions _directly_ - no permit value involved - and the component enforcing it is the _Sui Verifier_. The Sui Verifier is a set of bytecode-level checks that run on top of regular Move verification, both at compilation and when a package is published onchain. Most of its rules formalize what this chapter has already described - such as the `id: UID` first-field requirement from the [key ability](./key-ability) section. The _internal constraint_ is the rule that matters most for what comes next: a function marked with it can only be called with a type parameter `T` that is _internal_ - defined in the calling module. Let's look at the classic example - the `emit` function from the `sui::event` module (covered in detail in the [Events](./../programmability/events) section), which requires its type parameter to be internal to the caller: ```move module sui::event; // Sui Verifier will emit an error at compilation if this function is // called from a module that does not define `T`. public native fun emit(event: T); ``` Here is a correct call to `emit`. The type `A` is defined in the same module that makes the call, so the constraint is satisfied: ```move /// Defines the type `A`. module book::exercise_internal; use sui::event; /// Type defined in this module, so it's internal here. public struct A has copy, drop {} /// Works, because `A` is defined in this module. public fun call_internal() { event::emit(A {}) } ``` But calling `emit` with a type defined elsewhere - for example, the `TypeName` type from the [Standard Library](./../move-basics/standard-library) - is rejected: ```move // This one fails! public fun call_foreign_fail() { use std::type_name; event::emit(type_name::with_defining_ids()); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid event. // Error: `sui::event::emit` must be called with a type // defined in the current module. } ``` The effect is the same authority rule we established for [struct fields](./../move-basics/struct#field-visibility) and saw generalized by `Permit`: the module that defines a type decides what happens with it. For `emit`, it means only the defining module can emit events of its type; for the [storage functions](./storage-functions) in the next section, it means the defining module fully governs how its objects enter storage - unless it opts out by adding the [`store`](./store-ability) ability. ## Summary - The Sui Verifier is a set of bytecode-level rules checked at compilation and on publish. - The internal constraint restricts a function's type parameter to types defined in the calling module. - It applies to a handful of critical framework functions: `event::emit`, and the restricted storage functions covered in the [next section](./storage-functions). ## Further Reading - [Internal Permit](./../move-basics/internal-permit) - the same rule, available to any library through `std::internal`. --- # Storage Functions The module that defines the main storage operations is `sui::transfer`. It is implicitly imported in all packages that depend on the [Sui Framework](./../programmability/sui-framework), so, like other implicitly imported modules (e.g. `std::option` or `std::vector`), it does not require a `use` statement. > For quick reference, [Appendix C: Transfer Functions](./../appendix/transfer-functions) contains > a list of all storage functions and object states. ## Overview The `transfer` module provides a function for each of the [ownership states](./../object/ownership) an object can be placed into: 1. [Transfer](#transfer) - send an object to an address, putting it into the _address owned_ state; 2. [Freeze](#freeze) - put an object into the _immutable_ state, making it a _public constant_ that can never change; 3. [Share](#share) - put an object into the _shared_ state, available to everyone. The `transfer` module is the go-to for most storage operations. Two special cases are covered separately: [Dynamic Fields](./../programmability/dynamic-fields) - attaching data to objects - in the next chapter, and [receiving objects sent to other objects](./transfer-to-object) at the end of this one. ## Ownership and References: a Quick Recap Storage functions build directly on the semantics from the [Ownership and Scope](./../move-basics/ownership-and-scope) and [References](./../move-basics/references) sections. All of them take the object _by value_: the object is moved into the function, the caller loses it - and, as we are about to see, it ends up in storage, in its new state. This is the resource model at work: an object is never copied into storage, it is _placed_ there, and the previous owner provably gives it up. A function that only needs to read or update an object, on the other hand, takes it by reference (`&T` or `&mut T`) and leaves the ownership state untouched. ## Internal Rule in Transfer Functions Each storage operation comes in two forms: _internal_ and _public_. The internal functions - `transfer`, `share_object`, `freeze_object` - enforce the [internal constraint](./internal-constraint) from the previous section: they can only be called in the module that defines the type of the object. The public versions - prefixed with `public_` - lift that restriction, but require the type to have [`store`](./store-ability) in addition to `key`: ```move /// Internal: can only be called in the module that defines `T`. public fun transfer(obj: T, recipient: address); /// Public: callable from any module, but requires `T` to have `store`. public fun public_transfer(obj: T, recipient: address); ``` Together, the two forms implement the rule we previewed in the [store ability](./store-ability#relation-to-key) section: storage of a `key`-only object is fully governed by its defining module, while `store` opens the object up to storage operations performed by any module - and by the owner directly, in a transaction. To see every combination at once, suppose module `book::transfer_a` defines two objects - `ObjectK` with `key` and `ObjectKS` with `key + store` - and module `book::transfer_b` tries to transfer them: ```move /// Imports the `ObjectK` and `ObjectKS` types from `transfer_a` and attempts /// to implement different `transfer` functions for them. module book::transfer_b; // The types are not internal to this module! use book::transfer_a::{ObjectK, ObjectKS}; // Fails! `ObjectK` is not internal to this module. public fun transfer_k(k: ObjectK, to: address) { transfer::transfer(k, to); } // Fails! `ObjectKS` is not internal to this module either - // `store` does not affect the internal functions. public fun transfer_ks(ks: ObjectKS, to: address) { transfer::transfer(ks, to); } // Fails! `public_transfer` requires `store`, and `ObjectK` does not have it. public fun public_transfer_k(k: ObjectK, to: address) { transfer::public_transfer(k, to); } // Works! `ObjectKS` has `store`, and the function is public. public fun public_transfer_ks(ks: ObjectKS, to: address) { transfer::public_transfer(ks, to); } ``` The same matrix applies to `share_object`/`public_share_object` and `freeze_object`/`public_freeze_object`. Knowing this rule is critical for understanding application design in Move: the choice between making an object publicly transferable (`key + store`) and keeping it internal (`key`-only) drastically affects the guarantees the application can give about its assets. ## Transfer The `transfer::transfer` function sends an object to an address, making that address its sole owner: ```move module sui::transfer; // Transfer `obj` to `recipient`. public fun transfer(obj: T, recipient: address); // Public version of the `transfer` function. public fun public_transfer(obj: T, recipient: address); ``` In the following example, a module creates an object representing the application's admin rights and sends it to the publisher of the module: ```move /// A struct with `key` is an object. The first field is `id: UID`! public struct AdminCap has key { id: UID } /// `init` is a special function called once, when the module is /// published. It is the best place to create singleton objects, /// such as an admin capability. fun init(ctx: &mut TxContext) { // Create the `AdminCap` object in this scope. let admin_cap = AdminCap { id: object::new(ctx) }; // Transfer the object to the transaction sender. transfer::transfer(admin_cap, ctx.sender()); } /// Transfers the `AdminCap` object to the `recipient`. Thus, the /// recipient becomes the owner of the object, and only they can /// access it. public fun transfer_admin_cap(cap: AdminCap, recipient: address) { transfer::transfer(cap, recipient); } ``` When the module is published, the `init` function is called, and the `AdminCap` object created in it is _transferred_ to the transaction sender - `ctx.sender()` returns the sender address of the current transaction. (The `init` function is covered in detail in the [Module Initializer](./../programmability/module-initializer) section.) From that point, say the sender was `0xa11ce`, the object is in the _address owned_ state: only `0xa11ce` can use it in a transaction - by reference or by value, including transferring it further with the `transfer_admin_cap` function above. > Address-owned objects are subject to _true ownership_ - only the owner address can access them. > This is a fundamental concept in the Sui storage model, introduced in the > [Ownership](./../object/ownership#account-owner-or-single-owner) section. ### Public Transfer Let's extend the example with a function that uses the `AdminCap` to authorize minting of a new object and transferring it to any address: ```move /// Some `Gift` object that the admin can `mint_and_transfer`. public struct Gift has key, store { id: UID } /// Creates a new `Gift` object and transfers it to the `recipient`. public fun mint_and_transfer( _: &AdminCap, recipient: address, ctx: &mut TxContext, ) { let gift = Gift { id: object::new(ctx) }; transfer::public_transfer(gift, recipient); } ``` The `mint_and_transfer` function "could" be called by anyone - it is public - but it requires an `AdminCap` reference as its first argument, and the `AdminCap` object is owned by `0xa11ce` exclusively. So in practice only `0xa11ce` can mint. This simple and explicit way of gating access to a function is the _[Capability pattern](./../programmability/capability)_, one of the cornerstones of Sui application design. Note the difference between the two objects in this example. `AdminCap` is `key`-only: the module keeps full control over it, and if the module exposed no `transfer_admin_cap` function, the admin rights would be _soulbound_ - impossible to give away. `Gift` has `key + store`: it is sent with `public_transfer`, and whoever owns a `Gift` can freely transfer it onward in their own transactions, without any help from this module. ### Quick Recap - `transfer` sends an object to an address, making it _address owned_; - Only the owner can use an address-owned object - by reference or by value; - Requiring a `key`-only object as an argument gates a function to the object's owner - the _Capability_ pattern; - `public_transfer` is the public form: callable anywhere, requires `key + store`. ## Freeze The `transfer::freeze_object` function puts an object into the _immutable_ state. Once an object is _frozen_, it can never change, and anyone can access it by immutable reference: ```move module sui::transfer; // Make the object immutable and allow anyone to read it. public fun freeze_object(obj: T); // Public version of the `freeze_object` function. public fun public_freeze_object(obj: T); ``` Let's extend the running example with a `Config` object that the admin creates and freezes: ```move /// Some `Config` object that the admin can `create_and_freeze`. public struct Config has key { id: UID, message: String, } /// Creates a new `Config` object and freezes it. public fun create_and_freeze( _: &AdminCap, message: String, ctx: &mut TxContext, ) { let config = Config { id: object::new(ctx), message, }; // Freeze the object so it becomes immutable. transfer::freeze_object(config); } /// Returns the message from the `Config` object. /// Can access the object by immutable reference! public fun message(c: &Config): String { c.message } ``` Once `create_and_freeze` is called, the `Config` becomes publicly available by its ID, and the `message` function can be called by anyone - on a frozen object, immutable references are free for the taking. Function definitions are not tied to the object's state, so it is perfectly legal to _define_ functions that take a frozen type by mutable reference or by value - they just cannot be _called_ with a frozen object: ```move /// The function can be defined, but it won't be callable on a frozen /// object - only immutable references to it are available. public fun message_mut(c: &mut Config): &mut String { &mut c.message } ``` The same applies to `delete_config`, defined below in the [Share](#share) section: it takes `Config` by value, and a frozen `Config` can never be passed to it. Freezing is _permanent_: a frozen object cannot be modified, transferred, deleted - or unfrozen. ### Owned → Frozen Since the `freeze_object` signature accepts any object by value, it can receive an object created in the same scope, but also an object the sender _owns_. Single Owner → Immutable conversion is possible! For example, an owner of a `Gift` can decide to preserve it forever: ```move /// Freezes the `Gift` object so it becomes immutable. /// `Gift` has `key` + `store`, so `public_freeze_object` can be used! public fun freeze_gift(gift: Gift) { transfer::public_freeze_object(gift); } ``` For obvious security reasons, this is also something to keep in mind in the other direction: an `AdminCap` must never be frozen - a frozen capability would be readable by everyone, and every function gated by `&AdminCap` would become callable by anyone. Which, once again, shows the value of the `key`-only pattern: `AdminCap` has no `store`, so external code has no way to freeze it, and the module simply does not expose a freezing function. ### Quick Recap - `freeze_object` puts an object into the _immutable_ state - permanently; - A frozen object is readable by anyone via immutable reference, and can never be modified, transferred, or deleted; - Owned objects can be frozen - including by their owner in a transaction, if the object has `store`; - `public_freeze_object` is the public form: callable anywhere, requires `key + store`. ## Share The `transfer::share_object` function puts an object into the _shared_ state, where anyone can access it by mutable (and hence also immutable) reference: ```move module sui::transfer; /// Put the object into the shared state - accessible to everyone. public fun share_object(obj: T); /// Public version of the `share_object` function. public fun public_share_object(obj: T); ``` ```move /// Creates a new `Config` object and shares it. public fun create_and_share(message: String, ctx: &mut TxContext) { let config = Config { id: object::new(ctx), message, }; // Share the object so it becomes shared. transfer::share_object(config); } ``` Unlike `freeze_object`, which accepts both new and owned objects, `share_object` has a runtime restriction: **only an object created in the same transaction can be shared**. An attempt to share an object that already exists in the owned state aborts with `ESharedNonNewObject`. There is no Owned → Shared conversion: the decision to make an object shared has to be made at its creation. And like freezing, sharing is one-way - once shared, an object stays shared for the rest of its life, with a single exception, which we look at next. ### Special Case: Shared Object Deletion While a shared object can't normally be taken by value, there is one special case where it can - if the function that takes it _deletes_ it. This is a special case in the Sui storage model, made to allow cleaning up shared state. Let's add a function that deletes the shared `Config`: ```move /// Deletes the `Config` object, takes it by value. /// Can be called on a shared object! public fun delete_config(c: Config) { let Config { id, message: _ } = c; id.delete() } ``` The `delete_config` function takes the `Config` by value and destroys it completely - unpacking the struct and deleting the `UID` - and the Sui Verifier allows this call. However, if the function returned the `Config`, or attempted to `transfer` or `freeze` it, the transaction would be rejected: ```move // Won't work! public fun transfer_shared(c: Config, to: address) { transfer::transfer(c, to); } ``` The rule: a shared object taken by value must be deleted in the same transaction. ### Quick Recap - `share_object` puts an object into the _shared_ state, accessible to everyone by mutable reference; - Only an object created in the same transaction can be shared - there is no Owned → Shared conversion; - Sharing is permanent, with one exception: a shared object may be taken by value in order to be _deleted_; - `public_share_object` is the public form: callable anywhere, requires `key + store`. ## Party Transfer The `transfer` module also provides `party_transfer` and `public_party_transfer`, which place an object into the [party state](./../object/ownership#party-objects) - single-owner access with consensus ordering. Party objects are an advanced, newer feature, and we leave them out of the running example; the function signatures are listed in [Appendix C](./../appendix/transfer-functions#party), and the details are covered in the [`sui::party`](https://docs.sui.io/references/framework/sui/party) module documentation. ## Summary | Function | Resulting state | Reversible? | Public version | | ---------------- | --------------- | ------------------------------------------------------- | ----------------------- | | `transfer` | Address owned | Yes - transfer away | `public_transfer` | | `freeze_object` | Immutable | No | `public_freeze_object` | | `share_object` | Shared | Only by deletion | `public_share_object` | | `party_transfer` | Party | [Depends on permissions](./../appendix/transfer-functions#party) | `public_party_transfer` | - Every storage function takes the object _by value_ - placing an object into storage consumes it; - Internal versions require the type to be defined in the calling module; `public_*` versions require `store` instead. ## Next Steps Now that you know the main features of the `transfer` module, you can start building applications that involve storage operations. In the next section we cover the [UID and ID](./uid-and-id) types - the identity of every object - and after that, [Receiving as Object](./transfer-to-object), the mechanism behind objects owning other objects. ## Further Reading - [`sui::transfer`](https://docs.sui.io/references/framework/sui/transfer) module documentation. - [Appendix C: Transfer Functions](./../appendix/transfer-functions). --- # UID and ID The use of the `UID` type is required by the Sui Verifier on all types that have the [`key`](./key-ability) ability. Here we go deeper into `UID` and its usage. ## Definition The `UID` type is defined in the `sui::object` module and is a wrapper around an `ID` which, in turn, wraps the `address` type. The UIDs on Sui are guaranteed to be unique, and can't be reused after the object was deleted. ```move module sui::object; /// UID is a unique identifier of an object. public struct UID has store { id: ID } /// ID is a wrapper around an address; freely copyable. public struct ID has copy, drop, store { bytes: address } ``` Note the difference in abilities: an `ID` is plain, copyable data - a pointer that can name any object (or even a non-existent one) without any special privileges. A `UID` can be neither copied nor dropped: it is the identity of an object, and both its creation and its destruction are explicit, controlled operations. ## Fresh UID Generation A new `UID` is created with the `object::new(ctx)` function: - `UID` is _derived_ from the transaction digest and a counter of IDs created so far in the transaction, which is incremented with each new UID. - The counter lives in the transaction context, which is why [TxContext](./../programmability/transaction-context) is required - as a mutable reference - for UID generation. - The `id` field of a newly created object must be a _fresh_ UID - one produced by `object::new` in the same transaction. The Sui Verifier rejects packing an object with a UID taken from another, unpacked object - so an identity can never be reused, even by the module that owns it. `UID` acts as the representation of an object, and enables features attached to the object's identity. One of the key ones - [Dynamic Fields](./../programmability/dynamic-fields) - is possible because the `UID` is explicit. Another - [Transfer to Object](./transfer-to-object), covered at the end of this chapter - allows an object to receive other objects sent to its ID. ## UID Lifecycle The `UID` is created with `object::new`, and deleted with the `object::delete` function. The `delete` function consumes the UID _by value_, so it can only be called after the object was [unpacked](./../move-basics/struct#unpacking-a-struct) - which, in turn, only the defining module can do: ```move public struct Character has key { id: UID } /// Creates a `Character` object and immediately destroys it: /// the UID can only be deleted after the object is unpacked. public fun create_and_destroy(ctx: &mut TxContext) { // Instantiate the `Character` object with a fresh UID. let char = Character { id: object::new(ctx) }; // Unpack the object to take out its UID. let Character { id } = char; // Delete the UID. id.delete(); } ``` ### Keeping the UID The `UID` does not have to be deleted immediately after the object is unpacked. It may carry [Dynamic Fields](./../programmability/dynamic-fields), or hold objects sent to it via [Transfer to Object](./transfer-to-object) - deleting the UID would make those unreachable. For such cases, the UID can be kept: stored as a plain `UID` field (not as `id`!) in another struct, until the associated data is dealt with and the UID can be safely deleted. > The ability to keep a UID after its object is gone enables a niche technique known as _proof of > deletion_: the returned UID is evidence that the object was destroyed, which an application can > exchange for a reward, or use to bypass restrictions that applied to the live object. ## UID Derivation Sui allows deriving UIDs from other UIDs using _derivation keys_. This functionality is implemented in the [`sui::derived_object`][derived-object] module, and produces predictable, deterministic IDs for easier offchain discovery. The UID for each parent + key pair can be claimed only once: ```move use sui::derived_object; /// Some central application object. public struct Base has key { id: UID } /// A derived object. public struct Derived has key { id: UID } /// Creates and shares a new `Derived` object, using an `address` /// as the derivation key. public fun derive(base: &mut Base, key: address) { let id = derived_object::claim(&mut base.id, key); transfer::share_object(Derived { id }) } ``` Derived addresses reduce the load on offchain indexers: it is enough to know the ID of the parent object, and the IDs of derived objects can be computed with a derivation function - present in most SDKs, and in Move itself: ```move module sui::derived_object; /// Checks if a UID was derived with `key` at `parent`. public fun exists(parent: &UID, key: K): bool; /// Derive the inner `address` of a UID, regardless of whether it was claimed. public fun derive_address(parent: ID, key: K): address; ``` The same derivation mechanism is used internally to generate IDs for [dynamic fields](./../programmability/dynamic-fields). ## ID When talking about `UID` we should also mention the `ID` type. It is a freely copyable wrapper around `address`, used to _point_ at an object. Usually an `ID` refers to some object, but there is no restriction - and no guarantee - that the ID points to an existing object. > An ID can be received as a transaction argument in a > [Transaction Block](./../concepts/what-is-a-transaction). Alternatively, an ID can be created > from an `address` value using the `to_id()` function. ```move public fun conversion_methods(ctx: &mut TxContext) { let uid: UID = object::new(ctx); // `to_inner` returns a copy of the underlying `ID`. let id: ID = uid.to_inner(); // Both `UID` and `ID` can be converted to a plain address. let addr_from_uid: address = uid.to_address(); let addr_from_id: address = id.to_address(); uid.delete(); } ``` ## Fresh Object Address [`TxContext`](./../programmability/transaction-context) provides the `fresh_object_address` function, which produces a unique address using the same derivation as `object::new` - without creating a `UID`. It is useful for applications that need unique identifiers for offchain entities - for example, an `order_id` in a marketplace. ## Summary - `UID` is the non-copyable, non-droppable identity of an object; `ID` is a freely copyable pointer. - Fresh UIDs come from `object::new(ctx)` and can never be reused for a new object. - A UID is deleted with `object::delete` after unpacking - or kept, if data is still attached to it. - Derived UIDs (`sui::derived_object`) make object IDs predictable and discoverable offchain. ## Further Reading - [`sui::object`][object] module documentation. - [`sui::derived_object`][derived-object] module documentation. - [Derived Objects](https://docs.sui.io/guides/developer/objects/derived-objects) in Sui Documentation. [object]: https://docs.sui.io/references/framework/sui/object [derived-object]: https://docs.sui.io/references/framework/sui/derived_object --- # Receiving as Object The [address owned](./storage-functions.md#transfer) object state supports two types of owners: an account and another object. If an object was transferred to another object, Sui provides a way to _receive_ this object through its owner's [`UID`][uid]. > This feature is also known as _"Transfer to Object"_ or TTO. ## Definition Receiving functionality is implemented in the [`sui::transfer`][transfer] module. It consists of a special type `Receiving` which is instantiated through a special transaction argument, and the `receive` function which takes a [`UID`][uid] of the parent. > The `T` in `transfer::receive` is subject to the [Internal Constraint][internal]. The public > version of `receive` is called `public_receive`, and like other [storage functions][storage-funs] > it requires `T` to have [`store`][store]. ```move module sui::transfer; // An ephemeral wrapper around `Receiving` argument. Provided as a special input // in a Transaction Block. // Note: this type should be explicitly imported to be used! public struct Receiving has drop { id: ID, version: u64, } /// Receive `T` from parent `UID` through special type `Receiving`. public fun receive(parent: &mut UID, to_receive: Receiving): T; ``` Because `receive` requires a mutable reference to the parent's `UID`, receiving is only possible through the module that defines the parent - or through the access it chooses to expose. An object whose module provides no receiving implementation cannot release the objects sent to it, so this feature should be used with caution and in a controlled setting. ## Example As an illustration of _transfer_ and _receive_, consider a `PostOffice` that registers post boxes and lets anyone send objects to them: ```move module book::receiving; use sui::derived_object; use sui::transfer::Receiving; // not imported by default! /// Base derivation object to create derived `PostBox`-es. public struct PostOffice has key { id: UID } /// Object with derived UID which receives objects sent to an address. public struct PostBox has key { id: UID, owner: address } /// Transfer functionality. Anyone can come to the PostOffice and send to a specific /// recipient's PostBox. Items can be received from the `PostBox` by the recipient. public fun send(office: &PostOffice, parcel: T, recipient: address) { let postbox = derived_object::derive_address(office.id.to_inner(), recipient); transfer::public_transfer(parcel, postbox) } /// Receive the parcel. Requires the sender to be the owner of the `PostBox`! public fun receive( box: &mut PostBox, to_receive: Receiving, ctx: &TxContext ): T { assert!(box.owner == ctx.sender()); // Receive `to_receive` from `PostBox`. let parcel = transfer::public_receive(&mut box.id, to_receive); parcel } /// If user hasn't claimed their `PostBox` yet, create it. /// Note: this is not a requirement for transferring assets! /// Parcels can be sent even to unregistered post boxes, see `send` implementation. public fun register_address(office: &mut PostOffice, ctx: &mut TxContext) { transfer::share_object(PostBox { id: derived_object::claim(&mut office.id, ctx.sender()), owner: ctx.sender() }) } // Create a PostOffice on module publish. fun init(ctx: &mut TxContext) { transfer::share_object(PostOffice { id: object::new(ctx) }); } ``` ## Use Cases Transferring to objects is a powerful feature that lets objects act as owners of other objects, and it enables designs that plain address ownership cannot express: - **Controlled receiving.** Because receiving goes through the parent's module, extra logic can be attached to it - the `PostOffice` above could, for example, charge a fee for every received item. - **Objects as containers.** A parent object collects assets sent to it and can itself be transferred, carrying its entire "inventory" along - without ever listing the contents in a transaction. - **Deferred delivery.** Assets can be sent to an object before its owner is ready to claim them - a post-box that accumulates items until the user activates their account. - **Account-like objects.** An object with an ID that receives and releases assets behaves much like an account, which makes TTO a building block for account-abstraction designs. Sending _to an object_ is also naturally parallel: transfers to an object ID are plain transfers - they do not reference the parent in the transaction, and therefore do not contend on it. ## Next Steps This section concludes the Using Objects chapter: you can now define objects, place them into any ownership state, manage their identity, and even make objects own other objects. The [Advanced Programmability](./../programmability) chapter builds on all of it - starting with the execution environment, and returning to object composition with [Dynamic Fields](./../programmability/dynamic-fields), the second mechanism behind parent-child object relationships. ## Further Reading - [Transfer to Object](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object) in Sui Documentation - [`sui::transfer`][transfer] module documentation [transfer]: https://docs.sui.io/references/framework/sui/transfer [key]: ./key-ability.md [store]: ./store-ability.md [uid]: ./uid-and-id.md [internal]: ./internal-constraint.md [storage-funs]: ./storage-functions.md --- # Advanced Programmability In previous chapters we've covered [the basics of Move](./../move-basics) and [Sui Storage Model](./../storage). Now it's time to dive deeper into the advanced topics of Sui programmability. This chapter introduces more complex concepts, practices, and features of Move and Sui that are essential for building more sophisticated applications. The sections are ordered so that each builds on what came before, but they also form a few mostly independent threads, and it is fine to follow the one you need right now: - **The execution environment** - what a program can learn about the transaction it runs in and the system around it, and how it communicates with the outside world: [Sui Framework](./sui-framework), [Transaction Context](./transaction-context), [Module Initializer](./module-initializer), [Epoch and Time](./epoch-and-time), [Events](./events), and [Binary Canonical Serialization](./bcs). - **Storage at scale** - from simple vector-based collections to dynamic fields, a primitive that attaches arbitrary data to objects and lifts static type and size limits: [Collections](./collections), [Wrapper Type](./wrapper-type-pattern), [Dynamic Fields](./dynamic-fields), [Dynamic Object Fields](./dynamic-object-fields), and [Dynamic Collections](./dynamic-collections). - **Patterns of authority** - Move's answer to access control: from owned objects acting as permissions to guarantees backed by the system, and features built on top of them: [Capability](./capability), [Witness](./witness-pattern), [One Time Witness](./one-time-witness), [Publisher](./publisher), [Display](./display), and [Hot Potato](./hot-potato-pattern). - **Assets and funds** - fungible value and the two ways to hold it, as objects and as balances attached directly to an address: [Balance and Coin](./balance-and-coin) and [Address Balances](./address-balances). - **Code evolution** - what happens after the code ships: publishing new versions of a package, protecting shared state from old versions, and migrating data: [Package Upgrades](./package-upgrades). > Many code samples in this chapter are written as [tests](./../move-basics/testing), and use > test-only helpers from the framework: `tx_context::dummy()` creates a placeholder transaction > context, and `std::unit_test::destroy` consumes any value at the end of a test. We cover testing > techniques in detail in the [Testing](./../testing) chapter. --- # Sui Framework Sui Framework is a default dependency set in the [Package Manifest](./../concepts/manifest). It depends on the [Standard Library](./../move-basics/standard-library) and provides the Sui-specific functionality: storage operations, native types, and the modules the rest of this chapter is built on. _For convenience, we grouped the modules in the Sui Framework into multiple categories. But they're still part of the same framework._ ## Core
| Module | Description | Chapter | | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------- | | [sui::address](https://docs.sui.io/references/framework/sui/address) | Adds conversion methods to the [address type](./../move-basics/address) | [Address](./../move-basics/address) | | [sui::transfer](https://docs.sui.io/references/framework/sui/transfer) | Implements the storage operations for Objects | [Storage Functions](./../storage/storage-functions.md) | | [sui::tx_context](https://docs.sui.io/references/framework/sui/tx_context) | Contains the `TxContext` struct and methods to read it | [Transaction Context](./transaction-context) | | [sui::object](https://docs.sui.io/references/framework/sui/object) | Defines the `UID` and `ID` type, required for creating objects | [UID and ID](./../storage/uid-and-id.md) | | [sui::derived_object](https://docs.sui.io/references/framework/sui/derived_object) | Allows `UID` generation through key derivation | [UID Derivation](./../storage/uid-and-id.md#uid-derivation) | | [sui::clock](https://docs.sui.io/references/framework/sui/clock) | Defines the `Clock` type and its methods | [Epoch and Time](./epoch-and-time) | | [sui::dynamic_field](https://docs.sui.io/references/framework/sui/dynamic_field) | Implements methods to add, use and remove dynamic fields | [Dynamic Fields](./dynamic-fields) | | [sui::dynamic_object_field](https://docs.sui.io/references/framework/sui/dynamic_object_field) | Implements methods to add, use and remove dynamic object fields | [Dynamic Object Fields](./dynamic-object-fields) | | [sui::event](https://docs.sui.io/references/framework/sui/event) | Allows emitting events for offchain listeners | [Events](./events) | | [sui::package](https://docs.sui.io/references/framework/sui/package) | Defines the `Publisher` type and package upgrade methods | [Publisher](./publisher) | | [sui::display](https://docs.sui.io/references/framework/sui/display) | Implements the `Display` object and ways to create and update it | [Display](./display) |
## Collections
| Module | Description | Chapter | | ------------------------------------------------------------------------------ | ----------------------------------------------------------------- | -------------------------------------------- | | [sui::vec_set](https://docs.sui.io/references/framework/sui/vec_set) | Implements a set type | [Collections](./collections) | | [sui::vec_map](https://docs.sui.io/references/framework/sui/vec_map) | Implements a map with vector keys | [Collections](./collections) | | [sui::table](https://docs.sui.io/references/framework/sui/table) | Implements the `Table` type and methods to interact with it | [Dynamic Collections](./dynamic-collections) | | [sui::linked_table](https://docs.sui.io/references/framework/sui/linked_table) | Implements the `LinkedTable` type and methods to interact with it | [Dynamic Collections](./dynamic-collections) | | [sui::bag](https://docs.sui.io/references/framework/sui/bag) | Implements the `Bag` type and methods to interact with it | [Dynamic Collections](./dynamic-collections) | | [sui::object_table](https://docs.sui.io/references/framework/sui/object_table) | Implements the `ObjectTable` type and methods to interact with it | [Dynamic Collections](./dynamic-collections) | | [sui::object_bag](https://docs.sui.io/references/framework/sui/object_bag) | Implements the `ObjectBag` type and methods to interact with it | [Dynamic Collections](./dynamic-collections) |
## Coins and Assets
| Module | Description | Chapter | | ---------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------- | | [sui::balance](https://docs.sui.io/references/framework/sui/balance) | The `Balance` type - the underlying store of value | [Balance and Coin](./balance-and-coin) | | [sui::coin](https://docs.sui.io/references/framework/sui/coin) | The `Coin` type - a transferable fungible asset | [Balance and Coin](./balance-and-coin) | | [sui::sui](https://docs.sui.io/references/framework/sui/sui) | The SUI coin type | [Balance and Coin](./balance-and-coin) | | [sui::pay](https://docs.sui.io/references/framework/sui/pay) | Helper functions for splitting and merging coins | - | | [sui::deny_list](https://docs.sui.io/references/framework/sui/deny_list) | Deny list for regulated coin types | - | | [sui::token](https://docs.sui.io/references/framework/sui/token) | The closed-loop token standard | - |
## Utilities
| Module | Description | Chapter | | ------------------------------------------------------------------ | ---------------------------------------------------------- | --------------------------------------- | | [sui::bcs](https://docs.sui.io/references/framework/sui/bcs) | Implements the BCS encoding and decoding functions | [Binary Canonical Serialization](./bcs) | | [sui::borrow](https://docs.sui.io/references/framework/sui/borrow) | Implements the borrowing mechanic for borrowing by _value_ | [Hot Potato](./hot-potato-pattern) | | [sui::hex](https://docs.sui.io/references/framework/sui/hex) | Implements the hex encoding and decoding functions | - | | [sui::random](https://docs.sui.io/references/framework/sui/random) | The `Random` object and secure onchain randomness | [Randomness](./randomness) | | [sui::types](https://docs.sui.io/references/framework/sui/types) | Provides a way to check if the type is a One-Time-Witness | [One Time Witness](./one-time-witness) |
The framework also contains modules not covered in this book: the commerce primitives ([sui::kiosk](https://docs.sui.io/references/framework/sui/kiosk), [sui::transfer_policy](https://docs.sui.io/references/framework/sui/transfer_policy)), a set of cryptographic functions ([sui::hash](https://docs.sui.io/references/framework/sui/hash), [sui::ed25519](https://docs.sui.io/references/framework/sui/ed25519), [sui::bls12381](https://docs.sui.io/references/framework/sui/bls12381), and others), and assorted utilities such as [sui::url](https://docs.sui.io/references/framework/sui/url) and [sui::versioned](https://docs.sui.io/references/framework/sui/versioned). Refer to the [framework documentation](https://docs.sui.io/references/framework) for the full list. ## Exported Addresses Sui Framework exports two named addresses: `sui = 0x2` and `std = 0x1` from the std dependency. ## Implicit Imports Just like with [Standard Library](./../move-basics/standard-library#implicit-imports), some of the modules and types are imported implicitly in the Sui Framework. This is the list of modules and types that are available without explicit `use` import: - sui::object - sui::object::ID - sui::object::UID - sui::tx_context - sui::tx_context::TxContext - sui::transfer ## Source Code The source code of the Sui Framework is available in the [Sui repository](https://github.com/MystenLabs/sui/tree/main/crates/sui-framework/packages/sui-framework/sources). --- # Transaction Context Every transaction is executed in a _transaction context_. The context is a set of predefined values available to the program during execution, such as the sender address, the current epoch, or the transaction digest. The transaction context is available to the program through the `TxContext` struct. The struct is defined in the [`sui::tx_context`][tx-context-framework] module and contains the following fields: [tx-context-framework]: https://docs.sui.io/references/framework/sui/tx_context ```move module sui::tx_context; /// Information about the transaction currently being executed. /// This cannot be constructed by a transaction--it is a privileged object created by /// the VM and passed in to the entrypoint of the transaction as `&mut TxContext`. public struct TxContext has drop { /// The address of the user that signed the current transaction sender: address, /// Hash of the current transaction tx_hash: vector, /// The current epoch number epoch: u64, /// Timestamp that the epoch started at epoch_timestamp_ms: u64, /// Counter recording the number of fresh id's created while executing /// this transaction. Always 0 at the start of a transaction ids_created: u64 } ``` > While the struct still declares its original fields, current versions of the framework no longer > read most of them directly - the getter functions forward to native functions implemented in the > Sui execution environment. The fields are kept for compatibility, and `TxContext` is best thought > of as an opaque handle to the execution environment. Transaction context cannot be constructed manually or directly modified. It is created by the system and passed to the function as a reference in a transaction. Any function called in a [Transaction](./../concepts/what-is-a-transaction) has access to the context and can pass it into the nested calls. > `TxContext` has to be the last argument in the function signature. ## Reading the Transaction Context The `sui::tx_context` module provides a getter for each of the values available in the context. None of the getters require a mutable reference, since reading the context does not modify it: - `sender()` - the address that signed the transaction; - `digest()` - a reference to the 32-byte digest (hash) of the current transaction, unique per transaction; - `epoch()` - the current [epoch](./epoch-and-time) number; - `epoch_timestamp_ms()` - the timestamp of the moment the epoch started, in milliseconds; - `sponsor()` - the address of the transaction sponsor, or `None` if the transaction was not sponsored; - `gas_price()` - the gas price submitted with the current transaction; - `reference_gas_price()` - the reference gas price of the current epoch. ```move public fun some_action(ctx: &TxContext) { let sender = ctx.sender(); let tx_digest = ctx.digest(); let epoch = ctx.epoch(); let epoch_start = ctx.epoch_timestamp_ms(); let sponsor = ctx.sponsor(); let gas_price = ctx.gas_price(); let ref_gas_price = ctx.reference_gas_price(); // ... } ``` > The transaction digest is a hash of the transaction inputs, and while it is unique per > transaction, it should never be used as a source of randomness - it is known before the > transaction is executed, and can be manipulated by the sender. The `sponsor()` getter is related to _sponsored transactions_ - transactions where a third party, the sponsor, pays the gas fees on behalf of the user. In a sponsored transaction, `sender()` still returns the address of the user, so sender-based logic behaves the same whether or not the transaction is sponsored. These getters are the complete public interface for reading the context. Other values, such as the transaction's gas budget, are intentionally not exposed to the program. ## Mutability Some operations require the context to be passed as a mutable reference - `&mut TxContext`. The most important of them is the creation of new objects: every object on Sui must have a globally unique `UID`. Fresh UIDs are derived from the transaction digest and a counter of IDs created so far in this transaction - the `ids_created` field. Each time a new UID is requested, the counter is incremented, which guarantees that every derived address is unique. Because the counter has to change, the operation requires a mutable reference to the context. We cover object creation in detail in the [UID and ID](./../storage/uid-and-id) section. ## Generating Unique Addresses The same derivation mechanism can be used directly in your program to generate unique addresses. The `sui::tx_context` module exposes the `fresh_object_address` function for that, which may be useful if an application needs a unique identifier - for example, to use as a key in a [dynamic field](./dynamic-fields) or an offchain index. ```move module sui::tx_context; /// Create an `address` that has not been used. As it is an object address, it will never /// occur as the address for a user. /// In other words, the generated address is a globally unique object ID. public fun fresh_object_address(ctx: &mut TxContext): address; ``` ## Transaction Context in Tests Since `TxContext` cannot be constructed in regular code, [tests](./../move-basics/testing) would not be able to call any function that expects it. For this scenario the framework provides test-only constructors: the simplest of them is `tx_context::dummy()`, which returns a context with placeholder values. You will see it in code samples throughout this book: ```move #[test] fun test_some_action() { let ctx = &mut tx_context::dummy(); // pass `ctx` into functions that expect `&mut TxContext` } ``` For tests that need specific values - a certain sender, epoch, or gas price - the module provides more test-only constructors, as well as helpers to simulate epoch changes. They are covered in the [Simulating Transaction Context](./../testing/transaction-context) section. For multi-transaction scenarios and access to objects in storage, use the `sui::test_scenario` module, described in the [Test Scenario](./../testing/test-scenario) section. ## Further Reading - [sui::tx_context][tx-context-framework] module documentation. --- # Module Initializer A common use case in many applications is to run certain code just once when the package is published. Imagine a simple shop module that needs to create the main `Shop` object upon its publication. In Sui, this is achieved by defining an `init` function within the module. This function will automatically be called when the module is published. > The `init` function of every module in the package is called during the publishing process. This > behavior is limited to the publish command and does not extend to package upgrades - a module > added in an upgrade will not have its `init` called. ```move module book::shop; /// The Capability which grants the Shop owner the right to manage /// the shop. public struct ShopOwnerCap has key, store { id: UID } /// The singular Shop itself, created in the `init` function. public struct Shop has key { id: UID, /* ... */ } // Called only once, upon module publication. It must be // private to prevent external invocation. fun init(ctx: &mut TxContext) { // Transfers the ShopOwnerCap to the sender (publisher). transfer::transfer(ShopOwnerCap { id: object::new(ctx) }, ctx.sender()); // Shares the Shop object. transfer::share_object(Shop { id: object::new(ctx) }); } ``` In the same package, another module can have its own `init` function, encapsulating distinct logic. ```move // In the same package as the `shop` module module book::bank; public struct Bank has key { id: UID, /* ... */ } fun init(ctx: &mut TxContext) { transfer::share_object(Bank { id: object::new(ctx) }); } ``` ## The `init` Rules The function is called on publish if it is present in the module and follows these rules: - The function must be named `init`, be private, and have no return values; - it cannot be declared as `entry` and cannot have type parameters; - it takes one or two arguments: an optional [One Time Witness](./one-time-witness) and the [TxContext](./transaction-context), with `TxContext` always being the last argument. ```move fun init(ctx: &mut TxContext) { /* ... */ } fun init(otw: OTW, ctx: &mut TxContext) { /* ... */ } ``` These rules are not a convention - they are enforced by the bytecode verifier. A function named `init` that violates any of them fails verification, and the package cannot be published. `TxContext` can also be taken as an immutable reference `&TxContext`, but in practice it should always be `&mut TxContext`: the `init` function cannot access the onchain state, so creating new objects is the whole point of it - and that requires a mutable reference to the context. ## Trust and Security While the `init` function can be used to create sensitive objects once, it is important to know that the same object (e.g. `ShopOwnerCap` from the first example) can still be created in another function - especially since new functions can be added to the module during an upgrade. The `init` function is a good place to set up the initial state of the module, but it is not a security measure on its own. There are ways to guarantee that the object was created only once, such as the [One Time Witness](./one-time-witness). And there are ways to limit or disable package upgrades, described in [Custom Upgrade Policies](https://docs.sui.io/concepts/sui-move-concepts/packages/custom-policies) in the Sui Documentation. ## Testing the Initializer The `init` function is called by the runtime and cannot be invoked in a transaction. However, it is a regular function in every other sense, so [tests](./../move-basics/testing) placed in the same module can call it directly: ```move #[test_only] use std::unit_test::assert_eq; #[test] fun test_init() { let ctx = &mut tx_context::dummy(); init(ctx); // Two objects were created: the `ShopOwnerCap` and the `Shop`. assert_eq!(ctx.ids_created(), 2); } ``` For an `init` function that takes a [One Time Witness](./one-time-witness), the witness value can be created in tests with the test-only `sui::test_utils::create_one_time_witness` function. And in scenario-based tests, described in the [Test Scenario](./../testing/test-scenario) section, the objects created by `init` can also be inspected after the call. ## Next Steps As follows from the definition, the `init` function is guaranteed to be called only once when the module is published. So it is a good place to put the code that initializes the module's objects and sets up the environment and configuration. For example, if there's a [Capability](./capability) which is required for certain actions, it should be created in the `init` function. In the next chapter we will talk about the `Capability` pattern in more detail. --- # Pattern: Capability In programming, a _capability_ is a token that gives the owner the right to perform a specific action. It is a pattern that is used to control access to resources and operations. A simple example of a capability is a key to a door. If you have the key, you can open the door. If you don't have the key, you can't open the door. A more practical example is an Admin Capability which allows the owner to perform administrative operations, which regular users cannot. ## Capability is an Object In the [Sui Object Model](./../object/), capabilities are represented as objects. An owner of an object can pass this object to a function to prove that they have the right to perform a specific action. Due to strict typing, the function taking a capability as an argument can only be called with the correct capability. > There's a convention to name capabilities with the `Cap` suffix, for example, `AdminCap` or > `KioskOwnerCap`. ```move module book::capability; use std::string::String; /// The capability granting the application admin the right to create new /// accounts in the system. public struct AdminCap has key, store { id: UID } /// The user account in the system. public struct Account has key, store { id: UID, name: String } /// Creates a new account in the system. Requires the `AdminCap` capability /// to be passed as the first argument. public fun new(_: &AdminCap, name: String, ctx: &mut TxContext): Account { Account { id: object::new(ctx), name, } } /// The `Account` itself acts as a capability too: only its owner can pass /// a mutable reference to it, and hence only the owner can update the name. public fun update(account: &mut Account, name: String) { account.name = name; } ``` ## Using `init` for Admin Capability A very common practice is to create a single `AdminCap` object on package publish. This way, the application can have a setup phase where the admin account prepares the state of the application. ```move module book::admin_cap; /// The capability granting the admin privileges in the system. /// Created only once in the `init` function. public struct AdminCap has key { id: UID } /// Create the AdminCap object on package publish and transfer it to the /// package owner. fun init(ctx: &mut TxContext) { transfer::transfer( AdminCap { id: object::new(ctx) }, ctx.sender() ) } ``` Notice that this `AdminCap` has only the `key` ability, unlike the one in the first example, which also had `store`. The [abilities](./../move-basics/abilities-introduction) of a capability define how it can move between accounts: with `key` and `store`, the capability can be freely transferred with public transfer functions and stored inside other objects; with only `key`, it can be transferred only by functions defined in its module, so the module can restrict - or completely forbid - passing the capability on. As described in the [Storage Functions](./../storage/storage-functions) section, this is the difference between internal and public transfer. ## Capabilities in the Sui Framework The capability pattern is not just a convention - the [Sui Framework](./sui-framework) itself is built around it. Knowing the standard capabilities helps recognize the pattern in real code; here are the ones you are most likely to encounter: - `sui::coin::TreasuryCap` - created together with a new currency, grants the right to mint and burn coins of type `T`. Owning the `TreasuryCap` is owning the supply of the currency; we explore it in the [Balance and Coin](./balance-and-coin) chapter; - `sui::package::UpgradeCap` - created when a package is published, authorizes future upgrades of the package. The owner of the `UpgradeCap` can also restrict future upgrades, or disable them completely by making the capability immutable; - `sui::kiosk::KioskOwnerCap` - grants the right to `place`, `take`, and `list` items in a [Kiosk](https://docs.sui.io/standards/kiosk) - the trading primitive of Sui. While the `Kiosk` object itself is shared and accessible to everyone, the "owner" operations on it require the capability; - `sui::transfer_policy::TransferPolicyCap` - grants the right to manage a `TransferPolicy`: add and remove trading rules, and withdraw the collected fees. Two of these capabilities take a type parameter - a technique worth noting. By adding a [generic](./../move-basics/generics) to the capability, the authority it grants is scoped to a single type: a `TreasuryCap` controls the supply of `GOLD` and gives no rights over the `SILVER` currency. The framework also features a more general form of authority - the `Publisher` object, which proves authority over all types of a package. It is covered separately in the [Publisher Authority](./publisher) chapter. ## Address Check vs Capability Utilizing objects as capabilities is a relatively new concept in blockchain programming. In other smart-contract languages, authorization is often performed by checking the address of the sender. This pattern is still viable on Sui, however, the overall recommendation is to use capabilities for better security, discoverability, and code organization. Let's look at how the `new` function that creates a user would look if it used the address check: ```move /// Error code for unauthorized access. const ENotAuthorized: u64 = 0; /// The application admin address. const APPLICATION_ADMIN: address = @0xa11ce; /// Creates a new user in the system. Requires the sender to be the application /// admin. public fun new(ctx: &mut TxContext): User { assert!(ctx.sender() == APPLICATION_ADMIN, ENotAuthorized); User { id: object::new(ctx) } } ``` And now, let's see how the same function looks with the capability: ```move /// Grants the owner the right to create new users in the system. public struct AdminCap has key { id: UID } /// Creates a new user in the system. Requires the `AdminCap` capability to be /// passed as the first argument. public fun new(_: &AdminCap, ctx: &mut TxContext): User { User { id: object::new(ctx) } } ``` Using capabilities has several advantages over the address check: - Migration of admin rights is easier with capabilities due to them being objects. In case of address, if the admin address changes, all the functions that check the address need to be updated - hence, require a package upgrade. - Function signatures are more descriptive with capabilities. It is clear that the `new` function requires the `AdminCap` to be passed as an argument. And this function can't be called without it. - Object Capabilities don't require extra checks in the function body, and hence, decrease the chance of a developer mistake. - An owned Capability also serves in discovery. The owner of the AdminCap can see the object in their account (via a Wallet or Explorer), and know that they have the admin rights. This is less transparent with the address check. However, the address approach has advantages of its own. One case is a _multisig_ address - an address controlled by multiple parties, where a transaction is only valid if enough of them sign it. If the admin rights of an application belong to a multisig address, checking the sender may be simpler than building a transaction that presents a capability object owned by that address. Another case is an application with a central object - a config or a registry - that is already passed into every function. Such an object can store the admin address as a regular field, and checking it requires no extra inputs. The address is plain data, so it can be changed at runtime, without a package upgrade. The same idea enables _revocation_: an owned capability, once transferred, cannot be taken back from its owner, but an entry in a central registry - an address or an ID of a previously issued capability - can be removed by the admin at any moment, instantly revoking access. --- # Epoch and Time Sui has two ways of accessing the current time: the _epoch_ and the `Clock` object. The former represents operational periods in the system and changes roughly every 24 hours. The latter gives the current time in milliseconds since the Unix Epoch. Both can be accessed freely in the program. ## Epoch Epochs are used to separate the system into operational periods. During an epoch the validator set is fixed; at the epoch boundary, it can change. Epochs play a crucial role in the consensus algorithm and are used as a unit of measurement in the staking mechanism. The current epoch can be read from the [transaction context](./transaction-context): ```move public fun current_epoch(ctx: &TxContext) { let epoch = ctx.epoch(); // ... } ``` It is also possible to get the Unix timestamp (in milliseconds) of the epoch start: ```move public fun current_epoch_start(ctx: &TxContext) { let epoch_start = ctx.epoch_timestamp_ms(); // ... } ``` Both values are embedded in the transaction itself, so reading them is free and does not require access to any object. Normally, epochs are used in staking and system operations, however, in custom scenarios they can be used to emulate 24h periods. They are critical if an application relies on the staking logic or needs to know the current validator set. ## Time For a more precise time measurement, Sui provides the `Clock` object. It is a system object, updated by a system transaction on every consensus commit - roughly every quarter of a second - which stores the current time in milliseconds since the Unix Epoch. The `Clock` object is defined in the `sui::clock` module and has a [reserved address](./../appendix/reserved-addresses) `0x6`. Clock is a shared object, but a transaction attempting to access it mutably will fail. This limitation allows parallel access to the `Clock` object, which is important for maintaining performance. ```move module sui::clock; /// Singleton shared object that exposes time to Move calls. This /// object is found at address 0x6, and can only be read (accessed /// via an immutable reference) by entry functions. /// /// Entry Functions that attempt to accept `Clock` by mutable /// reference or value will fail to verify, and honest validators /// will not sign or execute transactions that use `Clock` as an /// input parameter, unless it is passed by immutable reference. public struct Clock has key { id: UID, /// The clock's timestamp, which is set automatically by a /// system transaction every time consensus commits a /// schedule, or by `sui::clock::increment_for_testing` during /// testing. timestamp_ms: u64, } ``` For regular use, the module exposes a single function - `timestamp_ms`. It returns the current time in milliseconds since the Unix Epoch. ```move use sui::clock::Clock; /// Clock needs to be passed as an immutable reference. public fun current_time(clock: &Clock) { let time = clock.timestamp_ms(); // ... } ``` The `Clock` comes with a few useful guarantees: within a single transaction, `timestamp_ms` always returns the same value, and across transactions the value never decreases. However, because the clock is only updated on consensus commits, transactions executed close to each other may see an identical timestamp. ## Testing Since the real `Clock` is only updated by the system, the module provides test-only functions to create a clock, set its value, and destroy it: ```move #[test_only] use sui::clock; #[test_only] use std::unit_test::assert_eq; #[test] fun use_clock_in_test() { // Get `ctx` and create `Clock` for testing let ctx = &mut tx_context::dummy(); let mut clock = clock::create_for_testing(ctx); assert_eq!(clock.timestamp_ms(), 0); // Add a value to the timestamp stored in `Clock` clock.increment_for_testing(2_000_000_000); assert_eq!(clock.timestamp_ms(), 2_000_000_000); // Set the timestamp, but the time set must be no less than the value stored in `Clock` clock.set_for_testing(3_000_000_000); assert_eq!(clock.timestamp_ms(), 3_000_000_000); // The following setting will fail because the time set must be at least the timestamp stored in `Clock` // clock.set_for_testing(1_000_000_000); // assert_eq!(clock.timestamp_ms(), 1_000_000_000); // If need a shared `Clock` for testing, you can set it through this function // clock.share_for_testing(); // `Clock` does not have a `drop` capability, so it needs to be destroyed manually at the end of the test clock.destroy_for_testing(); } ``` ## Summary - The current epoch and its start timestamp are read from the [transaction context](./transaction-context) - free and available in every transaction; an epoch lasts roughly 24 hours. - The `Clock` object at the reserved address `0x6` gives the time in milliseconds, updated on every consensus commit; it can only be accessed immutably. - Within a transaction the `Clock` value never changes, and across transactions it never decreases. - In tests, use `create_for_testing`, `set_for_testing`, `increment_for_testing`, and `destroy_for_testing` to control the clock. ## Further Reading - [sui::clock](https://docs.sui.io/references/framework/sui/clock) module documentation. --- # Collections Storing groups of values is one of the most common needs in a program. The [`vector`](./../move-basics/vector) type, covered in the Move Basics chapter, is the base building block for it, and the [Sui Framework](./sui-framework) extends it with two collection types that add structure on top: `VecSet`, which keeps its elements unique, and `VecMap`, which associates keys with values. In this section we introduce all three in their most common role - as fields of an object - and show the operations and constraints of each. ## Vector While the [vector section](./../move-basics/vector) presents the `vector` type as a standalone value, in a real application it usually lives inside an object. A store that owns a list of books is a vector in a field: ```move module book::collections_vector; use std::string::String; /// The Book that can be sold by a `BookStore` public struct Book has key, store { id: UID, name: String } /// The BookStore that sells `Book`s public struct BookStore has key, store { id: UID, books: vector } ``` Everything from the vector section applies here unchanged; the collection types below follow the same pattern - plain struct values that can be placed in a field, passed around, and, unlike [dynamic fields](./dynamic-fields) introduced later in this chapter, fully described by the type of the object that holds them. ## VecSet `VecSet` is a collection that stores _unique_ items. Inserting a value that is already present aborts, so the set is a natural fit for collections that must not contain duplicates, such as a list of IDs or addresses. ```move module book::collections_vec_set; use sui::vec_set::{Self, VecSet}; public struct App has drop { /// `VecSet` used in the struct definition subscribers: VecSet
} #[test_only] use std::unit_test::assert_eq; #[test] fun vec_set_playground() { let mut set = vec_set::empty(); // create an empty set set.insert(1u8); // add items to the set set.insert(2); set.insert(3); assert_eq!(set.contains(&1), true); // check if an item is in the set assert_eq!(set.length(), 3); // get the number of items in the set assert_eq!(set.is_empty(), false); // check if the set is empty set.remove(&2); // remove an item from the set assert_eq!(set.contains(&2), false); // the contents can be taken out as a plain vector, e.g. for iteration let items = set.into_keys(); assert_eq!(items, vector[1, 3]); } ``` The `contains` function answers membership questions, and the contents can be read back either by reference, with `keys`, or taken out as a plain `vector` with `into_keys` - for example, to iterate over them with the [vector macros](./../move-basics/vector#vector-macros). > The element type of a `VecSet` must have the [`copy`](./../move-basics/copy-ability) and > [`drop`](./../move-basics/drop-ability) abilities. This is true for primitive types and simple > data structs, but rules out storing assets in a set. ## VecMap `VecMap` is a collection of key-value pairs, where each key is unique and maps to a single value. Reading a value back is the everyday operation of a map, and there are two ways to do it: the index syntax `map[&key]` borrows a value and aborts if the key is missing, while `try_get` returns an [`Option`](./../move-basics/option) and never aborts. ```move module book::collections_vec_map; use std::string::String; use sui::vec_map::{Self, VecMap}; public struct Metadata has drop { name: String, /// `VecMap` used in the struct definition attributes: VecMap } #[test_only] use std::unit_test::{assert_eq, assert_ref_eq}; #[test] fun vec_map_playground() { let mut map: VecMap = vec_map::empty(); // create an empty map map.insert(2, "two"); // add a key-value pair to the map map.insert(3, "three"); assert_eq!(map.contains(&2), true); // check if a key is in the map assert_eq!(map.length(), 2); // get the number of entries // index syntax borrows a value by key, aborts if the key is missing assert_ref_eq!(&map[&2], &"two"); // `try_get` copies the value, returns `none` if the key is missing assert_eq!(map.try_get(&2), option::some("two")); assert_eq!(map.try_get(&4), option::none()); // an existing value can be replaced through a mutable reference *(&mut map[&3]) = "III"; // `remove` returns the key-value pair let (key, value) = map.remove(&2); assert_eq!(key, 2); assert_eq!(value, "two"); } ``` Like `VecSet`, a `VecMap` aborts on an attempt to `insert` a key that is already present - it does _not_ silently overwrite the old value. Replacing a value requires going through a mutable reference, as the example above shows, or removing the old entry first. Keys of a `VecMap` must have the [`copy`](./../move-basics/copy-ability) ability, while the value can be any type. ## Limitations Vector-based collections are strictly typed: a `VecSet
` holds addresses and nothing else, which is exactly what you want most of the time, but makes them unsuitable for heterogeneous data. They are also plain values stored inside the object, so they count toward the object size limit of 256KB, described in the [Building Against Limits](./../guides/building-against-limits) guide. In practice, a different limit matters sooner: every operation - `insert`, `contains`, `get` - scans the underlying vector element by element, so the cost of each access grows with the size of the collection. Vector-based collections shine when the number of elements is small and bounded - tens or hundreds of entries. For large or unbounded collections, the Sui Framework provides `Table`, `Bag`, and other object-backed types, which we cover in the [Dynamic Collections](./dynamic-collections) section later in this chapter. Lastly, vector-based collections do not support equality comparison the way one might expect. `VecSet` and `VecMap` keep their contents in insertion order, and the `==` operator compares the underlying vectors element by element. As a result, two sets that contain the same elements, but received them in a different order, are _not_ equal. > This behavior is caught by the linter and will emit a warning: _Comparing collections of type > 'sui::vec_set::VecSet' may yield unexpected result_ ```move let mut set1 = vec_set::empty(); set1.insert(1u8); set1.insert(2); let mut set2 = vec_set::empty(); set2.insert(2); set2.insert(1); assert_eq!(set1, set2); // aborts! ``` In the example above, both sets contain the same elements - `1` and `2` - but they were inserted in a different order. Since the comparison is order-sensitive, `set1 == set2` evaluates to `false`, and the assertion aborts. Do not rely on `==` to compare vector-based collections, unless you can guarantee that the elements were inserted in the same order. ## Summary - Vector is a native type that allows storing a list of items; inside an object it appears as a regular field. - VecSet is built on top of vector and stores unique items; inserting a duplicate aborts. - VecMap stores key-value pairs with unique keys; inserting an existing key aborts, and values are read with the index syntax or `try_get`. - Vector-based collections are strictly typed, scan their contents linearly on every operation, and are best suited for small, bounded sets and lists; larger collections call for [dynamic collections](./dynamic-collections). ## Next Steps In the next section we will cover the [Wrapper Type Pattern](./wrapper-type-pattern) - a design pattern often used with collection types to extend or restrict their behavior. ## Further Reading - [sui::vec_set][vec-set-framework] module documentation. - [sui::vec_map][vec-map-framework] module documentation. [vec-set-framework]: https://docs.sui.io/references/framework/sui/vec_set [vec-map-framework]: https://docs.sui.io/references/framework/sui/vec_map --- # Pattern: Wrapper Type Sometimes, there's a need to create a new type that behaves similarly to an existing type but with certain modifications or restrictions. For example, you might want to create a [collection type](./collections) that behaves like a vector but doesn’t allow modifying the elements after they’ve been inserted. The wrapper type pattern is an effective way to achieve this. ## Definition The wrapper type pattern is a design pattern in which you create a new type that wraps an existing type. The wrapper type is distinct from the original but can be converted to and from it. Often, it is implemented as a [positional struct](./../move-basics/struct.md#positional-structs) with a single field. ```move module book::wrapper_type_pattern; /// Very simple stack implementation using the wrapper type pattern. Does not allow /// accessing the elements unless they are popped. public struct Stack(vector) has copy, store, drop; /// Create a new instance by wrapping the value. public fun new(value: vector): Stack { Stack(value) } /// Push an element to the stack. public fun push_back(v: &mut Stack, el: T) { v.0.push_back(el); } /// Pop an element from the stack. Unlike `vector`, this function won't /// fail if the stack is empty and will return `None` instead. public fun pop_back(v: &mut Stack): Option { if (v.0.length() == 0) option::none() else option::some(v.0.pop_back()) } /// Get the size of the stack. public fun size(v: &Stack): u64 { v.0.length() } ``` ## Common Practices In cases where the goal is to extend the behavior of an existing type, it is common to provide accessors for the wrapped type. This approach allows users to access the underlying type directly when needed. For example, in the following code, we provide the `inner()`, `inner_mut()`, and `into_inner()` methods for the Stack type. ```move /// Allows reading the contents of the `Stack`. public fun inner(v: &Stack): &vector { &v.0 } /// Allows mutable access to the contents of the `Stack`. public fun inner_mut(v: &mut Stack): &mut vector { &mut v.0 } /// Unpacks the `Stack` into the underlying `vector`. public fun into_inner(v: Stack): vector { let Stack(inner) = v; inner } ``` ## Advantages The wrapper type pattern offers several benefits: - Custom Functions: It allows you to define custom functions for an existing type. - Robust Function Signatures: It constrains function signatures to the new type, thereby making the code more robust. - Improved Readability: It often increases the readability of the code by providing a more descriptive type name. ## Disadvantages The wrapper type pattern is powerful in two scenarios - when you want to limit the behavior of an existing type while providing a custom interface to the same data structure, and when you want to extend the behavior of an existing type. However, it does have some limitations: - Verbosity: It can be verbose to implement, especially if you want to expose all the methods of the wrapped type. - Sparse Implementation: The implementation can be quite minimal, as it often just forwards calls to the wrapped type. ## Next Steps The wrapper type pattern is very useful, particularly when used in conjunction with collection types, as demonstrated in the previous section. In the next section, we will cover [Dynamic Fields](./dynamic-fields) - an important primitive that enables [Dynamic Collections](./dynamic-collections), a way to store large collections of data in a more flexible, albeit more expensive, way. --- # Dynamic Fields The Sui Object Model allows attaching extra data to objects at runtime as _dynamic fields_. The behavior is similar to how a `Map` works in other programming languages. However, unlike a `Map`, which in Move would be strictly typed (we have covered it in the [Collections](./collections) section), dynamic fields allow attaching values of any type. A similar approach from the world of frontend development would be a JavaScript Object type which allows storing any type of data dynamically. > There's no limit to the number of dynamic fields that can be attached to an object. Thus, dynamic > fields can be used to store large amounts of data that don't fit into the object size limit. Dynamic fields allow for a wide range of applications, from splitting data into smaller parts to avoid the [object size limit](./../guides/building-against-limits) to attaching objects as a part of application logic. ## Definition Dynamic fields are defined in the `sui::dynamic_field` module of the [Sui Framework](./sui-framework). They are attached to an object's `UID` via a _name_, and can be accessed using that name. There can be only one field with a given name attached to an object. ```move module sui::dynamic_field; /// Internal object used for storing the field and value public struct Field has key { /// Determined by the hash of the object ID, the field name /// value and its type, i.e. hash(parent.id || name || Name) id: UID, /// The value for the name of this field name: Name, /// The value bound to this field value: Value, } ``` As the definition shows, dynamic fields are stored in an internal `Field` object, which has the `UID` generated in a deterministic way based on the object ID, the field name, and the field type. The `Field` object contains the field name and the value bound to it. The constraints on the `Name` and `Value` type parameters define the abilities that the key and value must have. _See [full documentation for sui::dynamic_field][dynamic-field-framework] module._ ## Usage The methods available for dynamic fields are straightforward: a field can be added with `add`, removed with `remove`, and read with `borrow` and `borrow_mut`. Additionally, the `exists_` method can be used to check if a field exists (for stricter checks with type, there is an `exists_with_type` method), and `remove_if_exists` removes a field if it is present, returning an [`Option`](./../move-basics/option) with the value. ```move module book::dynamic_fields; // a very common alias for `dynamic_field` is `df` since the // module name is quite long use sui::dynamic_field as df; use std::string::String; /// The object that we will attach dynamic fields to. public struct Character has key { id: UID } // List of different accessories that can be attached to a character. // They must have the `store` ability. public struct Hat has key, store { id: UID, color: u32 } public struct Mustache has key, store { id: UID } #[test] fun test_character_and_accessories() { let ctx = &mut tx_context::dummy(); let mut character = Character { id: object::new(ctx) }; // Attach a hat to the character's UID df::add( &mut character.id, b"hat_key", Hat { id: object::new(ctx), color: 0xFF0000 } ); // Similarly, attach a mustache to the character's UID df::add( &mut character.id, b"mustache_key", Mustache { id: object::new(ctx) } ); // Check that the hat and mustache are attached to the character assert!(df::exists_(&character.id, b"hat_key")); assert!(df::exists_(&character.id, b"mustache_key")); // Modify the color of the hat let hat: &mut Hat = df::borrow_mut(&mut character.id, b"hat_key"); hat.color = 0x00FF00; // Remove the hat and mustache from the character let hat: Hat = df::remove(&mut character.id, b"hat_key"); let mustache: Mustache = df::remove(&mut character.id, b"mustache_key"); // Check that the hat and mustache are no longer attached to the character assert!(!df::exists_(&character.id, b"hat_key")); assert!(!df::exists_(&character.id, b"mustache_key")); std::unit_test::destroy(character); std::unit_test::destroy(mustache); std::unit_test::destroy(hat); } ``` In the example above, we define a `Character` object and two different types of accessories that could never be put together in a vector. However, dynamic fields allow us to store them together in a single object. Both objects are attached to the `Character` via a `vector` (a byte string literal), and can be accessed using their respective names. As you can see, when we attached the accessories to the Character, we passed them _by value_. In other words, both values were moved to a new scope, and their ownership was transferred to the `Character` object. If we changed the ownership of `Character` object, the accessories would have been moved with it. And the last important property of dynamic fields we should highlight is that they are _accessed through their parent_. This means that the `Hat` and `Mustache` objects are not directly accessible and follow the same rules as the parent object. ## Foreign Types as Dynamic Fields Dynamic fields allow objects to carry data of any type, including those defined in other modules. This is possible due to their generic nature and relatively weak constraints on the type parameters. Let's illustrate this by attaching a few different values to a `Character` object. ```move let mut character = Character { id: object::new(ctx) }; // Attach a `String` via a `vector` name let string_value: String = "Hello, World!"; df::add(&mut character.id, b"string_key", string_value); // Attach a `u64` via a `u32` name df::add(&mut character.id, 1000u32, 1_000_000_000u64); // Attach a `bool` via a `bool` name df::add(&mut character.id, true, false); ``` In this example we showed how different types can be used for both _name_ and the _value_ of a dynamic field. The `String` is attached via a `vector` name, the `u64` is attached via a `u32` name, and the `bool` is attached via a `bool` name. Anything is possible with dynamic fields! ## Custom Type as a Field Name In the examples above, we used primitive types as field names since they have the required set of abilities. But dynamic fields get even more interesting when we use custom types as field names. This allows for a more structured way of storing data, and also allows for protecting the field names from being accessed by other modules. ```move /// A custom type with fields in it. public struct AccessoryKey has copy, drop, store { name: String } /// An empty key, can be attached only once. public struct MetadataKey has copy, drop, store {} ``` Two field names that we defined above are `AccessoryKey` and `MetadataKey`. The `AccessoryKey` has a `String` field in it, hence it can be used multiple times with different `name` values. The `MetadataKey` is an empty key, and can be attached only once. ```move let mut character = Character { id: object::new(ctx) }; // Attaching via an `AccessoryKey { name: "hat" }` df::add( &mut character.id, AccessoryKey { name: "hat" }, Hat { id: object::new(ctx), color: 0xFF0000 } ); // Attaching via an `AccessoryKey { name: "mustache" }` df::add( &mut character.id, AccessoryKey { name: "mustache" }, Mustache { id: object::new(ctx) } ); // Attaching via a `MetadataKey` df::add(&mut character.id, MetadataKey {}, 42u64); ``` As you can see, custom types work as field names as long as they can be _constructed_ by the module - in other words, if they are _internal_ to the module and defined in it. This limitation on struct packing can open up new ways in the design of the application. This approach is used in the [Object Capability](./object-capability) pattern, where an application can authorize a foreign object to perform operations in it while not exposing the capabilities to other modules. ## Exposing UID
Mutable access to `UID` is a security risk. Exposing `UID` of your type as a mutable reference can lead to unwanted modifications or removal of the object's dynamic fields. Additionally, it affects [Transfer to Object](./../storage/transfer-to-object) and [Dynamic Object Fields](./dynamic-object-fields). Make sure to understand the implications before exposing the `UID` as a mutable reference.
Because dynamic fields are attached to `UID`s, their usage in other modules depends on whether the `UID` can be accessed. By default struct visibility protects the `id` field and won't let other modules access it directly. However, if there's a public accessor method that returns a reference to `UID`, dynamic fields can be read in other modules. ```move /// Exposes the UID of the character, so that other modules can read /// dynamic fields. public fun uid(c: &Character): &UID { &c.id } ``` In the example above, we show how to expose the `UID` of a `Character` object. This solution may work for some applications, however, it is important to remember that exposed `UID` allows reading _any_ dynamic field attached to the object. If you need to expose the `UID` only within the package, use a restrictive visibility, like `public(package)`, or even better - use more specific accessor methods that would allow only reading specific fields. ```move /// Only allow modules in the same package to access the UID. public(package) fun uid_package(c: &Character): &UID { &c.id } /// Allow borrowing dynamic fields from the character. public fun borrow( c: &Character, n: Name ): &Value { df::borrow(&c.id, n) } ``` ## Orphaned Dynamic Fields > To prevent orphaned dynamic fields, please use [Dynamic Collection Types](./dynamic-collections) > such as `Bag` as they track the dynamic fields and won't allow unpacking if there are attached > fields. The `object::delete()` function, which is used to delete a UID, does not track the dynamic fields, and cannot prevent dynamic fields from becoming orphaned. Once the parent UID is deleted, the dynamic fields are not automatically deleted, and they become orphaned. This means that the dynamic fields are still stored in the blockchain, but they will never become accessible again. ```move let hat = Hat { id: object::new(ctx), color: 0xFF0000 }; let mut character = Character { id: object::new(ctx) }; // Attach a `Hat` via a `vector` name df::add(&mut character.id, b"hat_key", hat); // ! DO NOT do this in your code // ! Danger - deleting the parent object let Character { id } = character; id.delete(); // ...`Hat` is now stuck in a limbo, it will never be accessible again ``` Orphaned objects are not subject to the storage rebate, and the storage fees will remain unclaimed. One way to avoid orphaned dynamic fields during unpacking of an object is to return the `UID` and store it somewhere temporarily until the dynamic fields are removed and handled properly. ## Dynamic Fields vs Fields Dynamic Fields are more expensive than regular fields, as they require additional storage and costs for accessing them. Their flexibility comes at a price, and it is important to understand the implications when making a decision between using dynamic fields and regular fields. ## Limits Dynamic Fields are not subject to the [object size limit](./../guides/building-against-limits), and can be used to store large amounts of data. However, they are still subject to the [dynamic fields created limit](./../guides/building-against-limits), which is set to 1000 fields per transaction. ## Applications Dynamic fields can play a crucial role in applications of any complexity. They open up a variety of different use cases, from storing heterogeneous data to attaching objects as part of the application logic. They allow for certain [upgradeability practices](./../guides/upgradeability-practices) based on the ability to define them _later_ and change the type of the field. ## Summary - Dynamic fields attach values to an object's `UID` under a _name_; both the name and the value can be of almost any type, including types defined in other modules. - Attached values are owned by the parent object and can only be accessed through it. - Custom types used as field names can only be constructed by the defining module, which protects the fields from external access. - Deleting the parent `UID` does not remove its dynamic fields - the fields left behind become inaccessible _orphans_. ## Next Steps In the next section we will cover [Dynamic Object Fields](./dynamic-object-fields) and explain how they differ from dynamic fields, and what are the implications of using them. ## Further Reading - [sui::dynamic_field][dynamic-field-framework] module documentation. [dynamic-field-framework]: https://docs.sui.io/references/framework/sui/dynamic_field --- # Dynamic Object Fields > This section expands on [Dynamic Fields](./dynamic-fields). Please read it first to understand > the basics of dynamic fields. Another variation of dynamic fields is _dynamic object fields_, which have certain differences from regular dynamic fields. In this section, we will cover the specifics of dynamic object fields and explain how they differ from regular dynamic fields. > The general recommendation is to avoid using dynamic object fields in favor of (just) dynamic fields, > especially if there's no need for direct discovery through the ID. The extra costs of dynamic > object fields may not be justified by the benefits they provide. ## Definition Dynamic Object Fields are defined in the `sui::dynamic_object_field` module in the [Sui Framework](./sui-framework). They are similar to dynamic fields in many ways, but unlike them, dynamic object fields have an extra constraint on the `Value` type. The `Value` must have a combination of `key` and `store`, not just `store` as in the case of dynamic fields. The module definition is smaller than that of dynamic fields - only the field _name_ gets a wrapper type, while the value is stored as-is: ```move module sui::dynamic_object_field; /// Internal object used for storing the field and the name associated with the /// value. The separate type is necessary to prevent key collision with direct /// usage of dynamic_field public struct Wrapper has copy, drop, store { name: Name, } ``` Unlike the `Field` type in the [Dynamic Fields](./dynamic-fields#definition) section, the `Wrapper` type only stores the name of the field. The value is the object itself, and is _not wrapped_. _See [full documentation for sui::dynamic_object_field][dynamic-object-field-framework] module._ The constraints on the `Value` type become visible in the methods available for dynamic object fields. Here's the signature for the `add` function: ```move /// Adds a dynamic object field to the object `object: &mut UID` at field /// specified by `name: Name`. Aborts with `EFieldAlreadyExists` if the object /// already has that field with that name. public fun add( // we use &mut UID in several spots for access control object: &mut UID, name: Name, value: Value, ) { /* implementation omitted */ } ``` The rest of the methods are identical to the ones in the [Dynamic Fields](./dynamic-fields#usage) section, and carry the same constraint on the `Value` type. Let's list them for reference: - `add` - adds a dynamic object field to the object - `remove` - removes a dynamic object field from the object - `borrow` - borrows a dynamic object field from the object - `borrow_mut` - borrows a mutable reference to a dynamic object field from the object - `exists_` - checks if a dynamic object field exists - `exists_with_type` - checks if a dynamic object field exists with a specific type Additionally, there is an `id` method which returns the `ID` of the `Value` object without specifying its type. ## Usage and Differences with Dynamic Fields The main difference between dynamic fields and dynamic object fields is that the latter allows storing _only objects_ as values. This means that you can't store primitive types like `u64` or `bool`. In exchange for this restriction, the attached object is _not wrapped_ into a separate object: it keeps its ID and stays visible to offchain tooling. > This is the property to weigh when choosing between the two: a value attached as a regular > dynamic field is wrapped into a `Field` object and disappears from ID-based queries, while a > value attached as a dynamic object field remains discoverable by its ID in wallets and explorers. ```move module book::dynamic_object_field; use std::string::String; // there are two common aliases for the long module name: `dof` and // `ofield`. Both are commonly used and met in different projects. use sui::dynamic_object_field as dof; use sui::dynamic_field as df; /// The `Character` that we will use for the example public struct Character has key { id: UID } /// Metadata that doesn't have the `key` ability public struct Metadata has store, drop { name: String } /// Accessory that has the `key` and `store` abilities. public struct Accessory has key, store { id: UID } #[test] fun equip_accessory() { let ctx = &mut tx_context::dummy(); let mut character = Character { id: object::new(ctx) }; // Create an accessory and attach it to the character let hat = Accessory { id: object::new(ctx) }; // Add the hat to the character. Just like with `dynamic_fields` dof::add(&mut character.id, b"hat_key", hat); // However for non-key structs we can only use `dynamic_field` df::add(&mut character.id, b"metadata_key", Metadata { name: "John" }); // Borrow the hat from the character let hat_id = dof::id(&character.id, b"hat_key").extract(); // Option let hat_ref: &Accessory = dof::borrow(&character.id, b"hat_key"); let hat_mut: &mut Accessory = dof::borrow_mut(&mut character.id, b"hat_key"); let hat: Accessory = dof::remove(&mut character.id, b"hat_key"); // Clean up, Metadata is an orphan now. std::unit_test::destroy(hat); std::unit_test::destroy(character); } ``` In the example above, the `Accessory` has both `key` and `store`, so it can be attached as a dynamic object field. The `Metadata`, however, only has `store`, so it can only be attached as a regular dynamic field. Both kinds of fields coexist on the same `UID` - even under similar names - because the internal `Wrapper` type prevents key collisions between the two modules. Lastly, the example demonstrates the `id` function, which returns the `ID` of the attached object without requiring its type - something only possible because the object keeps its identity. ## Pricing Differences Dynamic object fields are a little more expensive than dynamic fields. Because of their internal structure, a single dynamic object field is stored as two objects: an internal field storing the name, and the value object itself. As a result, the cost of adding and accessing dynamic object fields (loading 2 objects compared to 1 for dynamic fields) is higher. ## Summary - Dynamic object fields require the value to be an object (`key` + `store`) and, unlike regular dynamic fields, keep the attached object discoverable by its ID in wallets and explorers. - The methods mirror those of dynamic fields, with an extra `id` function that returns the `ID` of the attached object without specifying its type. - Dynamic object fields are more expensive than dynamic fields, so prefer the latter unless ID-based discovery is required. ## Next Steps Both dynamic fields and dynamic object fields are powerful features which allow for innovative solutions in applications. However, they are relatively low-level and require careful handling to avoid orphaned fields. In the next section, we will introduce a higher-level abstraction - [Dynamic Collections](./dynamic-collections) - which can help with managing dynamic fields and objects more effectively. ## Further Reading - [sui::dynamic_object_field][dynamic-object-field-framework] module documentation. [dynamic-object-field-framework]: https://docs.sui.io/references/framework/sui/dynamic_object_field --- # Dynamic Collections The [Sui Framework](./sui-framework) offers a variety of collection types that build on the [dynamic fields](./dynamic-fields) and [dynamic object fields](./dynamic-object-fields) concepts. These collections are designed to be a safer and more understandable way to store and manage dynamic fields and objects. For each collection type we will specify the primitive they use, and the specific features they offer. > Unlike dynamic (object) fields which operate on UID, collection types have their own type and > allow calling [associated functions](./../move-basics/struct-methods). ## Common Concepts All five collections follow the same shape: a struct with the `key` and `store` abilities, holding its own `UID` and a `size` counter. The entries are attached to that `UID` as dynamic fields. This is why creating a collection requires a mutable reference to the [transaction context](./transaction-context) - a fresh `UID` has to be derived from it - and why a collection is typically stored as a field of another object, as the examples below show. All of the collection types share the same set of core methods: - `new` - creates a new, empty collection - `add` - adds a field to the collection ([LinkedTable](#linkedtable) uses `push_front` and `push_back` instead) - `remove` - removes a field from the collection and returns the value - `borrow` - borrows a field from the collection - `borrow_mut` - borrows a mutable reference to a field from the collection - `contains` - checks if a field exists in the collection - `length` - returns the number of fields in the collection - `is_empty` - checks if the `length` is 0 - `destroy_empty` - destroys the collection, aborting if it still contains fields The last method is what makes collections safer than raw dynamic fields: because collections track their size, they cannot be destroyed while non-empty, which rules out [orphaned fields](./dynamic-fields#orphaned-dynamic-fields). The flip side of this protection is that a collection whose values cannot be dropped has to be emptied entry by entry before it can be destroyed - and since the number of dynamic fields accessed per transaction is [limited](./../guides/building-against-limits), dismantling a large collection may take more than one transaction. Another property, inherited from dynamic fields, is that the keys are not discoverable onchain: to access an entry, the code has to know its key. Offchain tooling can still list all entries, as they are stored as dynamic fields on the collection's `UID`. The only collection that can be iterated onchain is [LinkedTable](#linkedtable). All collection types support index syntax for `borrow` and `borrow_mut` methods. If you see square brackets in the examples, they are translated into `borrow` and `borrow_mut` calls. ```move let hat: &Hat = &bag[b"key"]; let hat_mut: &mut Hat = &mut bag[b"key"]; // is equivalent to let hat: &Hat = bag.borrow(b"key"); let hat_mut: &mut Hat = bag.borrow_mut(b"key"); ``` In the examples we won't focus on these functions, but rather on the differences between the collection types. ## Bag Bag, as the name suggests, acts as a "bag" of heterogeneous values. It is a simple, non-generic type built on [dynamic fields](./dynamic-fields), and it can store any data. Bag is the right choice when a single container has to hold values of different types - for example, a game character carrying items of various kinds, or a user profile storing unrelated settings side by side. ```move module sui::bag; public struct Bag has key, store { /// the ID of this bag id: UID, /// the number of key-value pairs in the bag size: u64, } ``` _See [full documentation for sui::bag][bag-framework] module._ Since Bag stores values of any type, it offers one extra method: - `contains_with_type` - checks if a field exists with a specific type Used as a struct field: ```move /// Imported from the `sui::bag` module. use sui::bag::{Self, Bag}; /// An example of a `Bag` as a struct field. public struct Carrier has key { id: UID, bag: Bag } ``` Using the Bag: ```move let mut bag = bag::new(ctx); // bag has the `length` function to get the number of elements assert_eq!(bag.length(), 0); // the type of the value is defined at insertion; here it is a `String` let value: String = "my_value"; bag.add(b"my_key", value); // length has changed to 1 assert_eq!(bag.length(), 1); // in order: `borrow`, `borrow_mut` and `remove` // the value type must be specified let field_ref: &String = &bag[b"my_key"]; let field_mut: &mut String = &mut bag[b"my_key"]; let field: String = bag.remove(b"my_key"); // length is back to 0 - we can unpack bag.destroy_empty(); ``` ## ObjectBag Defined in the `sui::object_bag` module. Identical to [Bag](#bag), but uses [dynamic object fields](./dynamic-object-fields) internally. Can only store objects as values, and in exchange keeps them discoverable by their IDs in offchain tooling. Use it for the same heterogeneous scenarios as Bag when the stored values are assets that should remain visible in wallets and explorers - such as an inventory of NFTs of different types. Like dynamic object fields, ObjectBag offers the `value_id` function, which returns the `ID` of a stored object without specifying its type. _See [full documentation for sui::object_bag][object-bag-framework] module._ ## Table Table is a typed dynamic collection that has a fixed type for keys and values. It is built on [dynamic fields](./dynamic-fields) and defined in the `sui::table` module. Table is the go-to collection for large uniform registries: user records, balances, or configuration entries keyed by an address or a name - like the `UserRegistry` in the example below. ```move module sui::table; public struct Table has key, store { /// the ID of this table id: UID, /// the number of key-value pairs in the table size: u64, } ``` _See [full documentation for sui::table][table-framework] module._ Since the type of the values is fixed, Table offers one extra method: - `drop` - destroys the table even if it is not empty; only available when the value type has the [drop](./../move-basics/drop-ability) ability Used as a struct field: ```move /// Imported from the `sui::table` module. use sui::table::{Self, Table}; /// Some record type with `store` public struct Record has store { /* ... */ } /// An example of a `Table` as a struct field. public struct UserRegistry has key { id: UID, table: Table } ``` Using the Table: ```move // Table requires explicit type parameters for the key and value // ...but does it only once in initialization. let mut table = table::new(ctx); // table has the `length` function to get the number of elements assert_eq!(table.length(), 0); table.add(@0xa11ce, "my_value"); table.add(@0xb0b, "another_value"); // length has changed to 2 assert_eq!(table.length(), 2); // in order: `borrow`, `borrow_mut` and `remove` let value_ref = &table[@0xa11ce]; let value_mut = &mut table[@0xa11ce]; // removing both values let _value = table.remove(@0xa11ce); let _another_value = table.remove(@0xb0b); // length is back to 0 - we can unpack table.destroy_empty(); ``` ## ObjectTable Defined in the `sui::object_table` module. Identical to [Table](#table), but uses [dynamic object fields](./dynamic-object-fields) internally. Can only store objects as values, and in exchange keeps them discoverable by their IDs in offchain tooling. Use it when a registry stores whole objects of the same type - for example, user profile objects keyed by the owner's address - and each of them should stay individually discoverable. Like dynamic object fields, ObjectTable offers the `value_id` function, which returns the `ID` of a stored object without specifying its type. _See [full documentation for sui::object_table][object-table-framework] module._ Storing objects requires the value type to have the `key` and `store` abilities: ```move /// Imported from the `sui::object_table` module. use sui::object_table::{Self, ObjectTable}; /// A profile is an object - it has the `key` and `store` abilities. public struct Profile has key, store { id: UID, name: String, } /// An example of an `ObjectTable` as a struct field. public struct ProfileRegistry has key { id: UID, profiles: ObjectTable } ``` Using the ObjectTable: ```move let mut profiles = object_table::new(ctx); // the interface is the same as the regular `Table` profiles.add(@0xa11ce, Profile { id: object::new(ctx), name: "Alice", }); // the stored object keeps its `ID` and can be looked up without its type let profile_id = profiles.value_id(@0xa11ce); // Option // objects cannot be dropped - remove the entry before destroying the table let profile = profiles.remove(@0xa11ce); profiles.destroy_empty(); ``` ## LinkedTable Defined in the `sui::linked_table` module. Built on [dynamic fields](./dynamic-fields), similar to [Table](#table), but the entries are linked together, allowing insertion at either end, ordered removal, and onchain iteration. This makes it the choice for anything that must be enumerated or processed in order onchain: queues and waitlists, leaderboards, or registries whose entries have to be listed - like the `AdminRegistry` in the example below. ```move module sui::linked_table; public struct LinkedTable has key, store { /// the ID of this table id: UID, /// the number of key-value pairs in the table size: u64, /// the front of the table, i.e. the key of the first entry head: Option, /// the back of the table, i.e. the key of the last entry tail: Option, } ``` _See [full documentation for sui::linked_table][linked-table-framework] module._ Since the entries in LinkedTable are linked together, adding an entry requires stating where it goes, so instead of `add` it has: - `push_front` - inserts a key-value pair at the front of the table - `push_back` - inserts a key-value pair at the back of the table - `pop_front` - removes the front of the table, returns the key and value - `pop_back` - removes the back of the table, returns the key and value Additionally, the `front`, `back`, `prev`, and `next` methods return the keys of neighboring entries, making it possible to iterate over the table onchain. Like [Table](#table), LinkedTable offers the `drop` method for value types with the [drop](./../move-basics/drop-ability) ability. Used as a struct field: ```move /// Imported from the `sui::linked_table` module. use sui::linked_table::{Self, LinkedTable}; /// Some record type with `store` public struct Permissions has store { /* ... */ } /// An example of a `LinkedTable` as a struct field. public struct AdminRegistry has key { id: UID, linked_table: LinkedTable } ``` Using the LinkedTable: ```move // LinkedTable requires explicit type parameters for the key and value // ...but does it only once in initialization. let mut linked_table = linked_table::new(ctx); // linked_table has the `length` function to get the number of elements assert_eq!(linked_table.length(), 0); linked_table.push_front(@0xa0a, "first_value"); linked_table.push_back(@0xb1b, "second_value"); linked_table.push_back(@0xc2c, "third_value"); // length has changed to 3 assert_eq!(linked_table.length(), 3); // in order: `borrow`, `borrow_mut` and `remove` let first_value_ref = &linked_table[@0xa0a]; let second_value_mut = &mut linked_table[@0xb1b]; // remove by key, from the beginning or from the end let _second_value = linked_table.remove(@0xb1b); let (_first_addr, _first_value) = linked_table.pop_front(); let (_third_addr, _third_value) = linked_table.pop_back(); // length is back to 0 - we can unpack linked_table.destroy_empty(); ``` ## Pricing Collections inherit the pricing of the primitives they are built on. Creating a collection adds an object with a `UID` to storage; each entry is priced as a [dynamic field](./dynamic-fields#dynamic-fields-vs-fields), or - in the Object-variants - as a [dynamic object field](./dynamic-object-fields#pricing-differences), with its higher, two-object cost per entry. ## Choosing a Collection Type A short decision guide: - The key and value types are fixed and known - use [Table](#table); if the values vary in type, use [Bag](#bag); - The values are objects that should stay visible to wallets and explorers - take the [ObjectTable](#objecttable) / [ObjectBag](#objectbag) variant; - The collection has to be iterated onchain or preserve insertion order - use [LinkedTable](#linkedtable), the only one of the five that links its entries; - The collection is small, bounded, and needs to be embedded or compared as a plain value - the vector-based [collections](./collections) from the earlier section may be a better fit than a dynamic one. > One more thing to keep in mind: the entries of a dynamic collection live outside of the struct > itself. Serializing a `Table` (for example, with [BCS](./bcs)) or comparing two tables only > takes the `id` and `size` fields into account - never the contents. ## Summary - [Bag](#bag) - a simple collection that can store any type of data; fits containers of heterogeneous values, such as inventories. - [ObjectBag](#objectbag) - same as Bag, but can only store objects; fits heterogeneous assets that should stay visible in wallets and explorers. - [Table](#table) - a typed dynamic collection that has a fixed type for keys and values; fits large uniform registries. - [ObjectTable](#objecttable) - same as Table, but can only store objects; fits registries of same-type objects that should stay individually discoverable. - [LinkedTable](#linkedtable) - similar to Table but the entries are linked together; fits queues and anything iterated onchain. ## Next Steps This section concludes the tour of dynamic fields and the collections built on top of them. In the next section we will move on to design patterns, starting with the [Witness](./witness-pattern) pattern. ## Further Reading - [sui::table][table-framework] module documentation. - [sui::object_table][object-table-framework] module documentation. - [sui::linked_table][linked-table-framework] module documentation. - [sui::bag][bag-framework] module documentation. - [sui::object_bag][object-bag-framework] module documentation. [table-framework]: https://docs.sui.io/references/framework/sui/table [object-table-framework]: https://docs.sui.io/references/framework/sui/object_table [linked-table-framework]: https://docs.sui.io/references/framework/sui/linked_table [bag-framework]: https://docs.sui.io/references/framework/sui/bag [object-bag-framework]: https://docs.sui.io/references/framework/sui/object_bag --- # Pattern: Witness Witness is a pattern of proving a fact by constructing evidence of it. In the context of programming, a witness is a way to prove a certain property of a system by providing a value that can only be constructed if the property holds. ## Witness in Move In the [Struct](./../move-basics/struct) section we have shown that a struct can only be created - or _packed_ - by the module defining it. Hence, in Move, a module proves ownership of the type by constructing it. This is one of the most important patterns in Move, and it is widely used for generic type instantiation and authorization. Practically speaking, for the witness to be used, there has to be a function that expects a witness as an argument. In the example below it is the `new` function that expects a witness of the `T` type to create an `Instance`. > The witness is usually discarded rather than stored, which is why such functions often require > the witness type to have the [drop](./../move-basics/drop-ability) ability. ```move module book::witness; /// A struct that can only be created with a witness of `T`. public struct Instance has drop {} /// Create a new `Instance` with the provided witness. The witness is /// discarded after use. public fun new(_witness: T): Instance { Instance {} } ``` The only way to construct an `Instance` is to call the `new` function with an instance of the type `T`. This is a basic example of the witness pattern in Move. A module providing a witness often has a matching implementation, like the module `book::witness_source` below: ```move module book::witness_source; use book::witness::{Self, Instance}; /// A struct used as a witness - canonically, an empty struct with `drop`. public struct W has drop {} /// Create a new instance of `Instance`. public fun new_instance(): Instance { witness::new(W {}) } ``` The instance of the struct `W` is passed into the `new_instance` function to create an `Instance`, thereby proving that the module `book::witness_source` owns the type `W`. ## Instantiating a Generic Type Witness allows generic types to be instantiated with a concrete type. This is useful for inheriting associated behaviors from the type with an option to extend them, if the module provides the ability to do so. ```move module sui::balance; /// A Supply of T. Used for minting and burning. /// Wrapped into a `TreasuryCap` in the `Coin` module. public struct Supply has store { value: u64, } /// Create a new supply for type T. public fun create_supply(_: T): Supply { Supply { value: 0 } } /// Get the `Supply` value. public fun supply_value(supply: &Supply): u64 { supply.value } ``` In the example above, which is borrowed from the [`balance` module][balance-framework] of the [Sui Framework](./sui-framework), the `Supply` is a generic struct that can be constructed only by supplying a witness of the type `T`. The witness is taken by value and _discarded_ - hence the `T` must have the [drop](./../move-basics/drop-ability) ability. [balance-framework]: https://docs.sui.io/references/framework/sui/balance The instantiated `Supply` can then be used to mint new `Balance`'s, where `T` is the type of the supply. ```move module sui::balance; const EOverflow: u64 = 1; /// Storable balance - an inner struct of a Coin type. /// Can be used to store coins which don't need the key ability. public struct Balance has store { value: u64, } /// Increase supply by `value` and create a new `Balance` with this value. public fun increase_supply(self: &mut Supply, value: u64): Balance { assert!(value <= (std::u64::max_value!() - self.value), EOverflow); self.value = self.value + value; Balance { value } } ``` This is how new currencies are typically created on Sui: the `TreasuryCap` - the [capability](./capability) described earlier in this chapter - is a wrapper around the `Supply`, instantiated with a witness. ## Authorization with Witness Instantiating a type is not the only use for a witness: any function can require one, making the call available only to the module that defines `T`. The module below implements a generic `RegulatedCoin`, in which the privileged operations - `mint`, `burn`, and `transfer` - require a witness, while the shared functionality - `join` - is available to everyone: ```move /// A custom RegulatedCoin type with implementable functions. public struct RegulatedCoin has key { id: UID, value: u64 } /// Protected function - requires a Witness. /// Mints a new `RegulatedCoin` with the value. public fun mint(_: T, value: u64, ctx: &mut TxContext): RegulatedCoin { RegulatedCoin { id: object::new(ctx), value } } /// Protected function - requires a Witness. /// Burns the `RegulatedCoin` and returns the value. public fun burn(_: T, coin: RegulatedCoin): u64 { let RegulatedCoin { id, value } = coin; id.delete(); value } /// Protected function - requires a Witness. public fun transfer(_: T, coin: RegulatedCoin, to: address) { transfer::transfer(coin, to) } /// Public API - does not require a Witness. public fun join(coin: &mut RegulatedCoin, other: RegulatedCoin) { let RegulatedCoin { id, value } = other; coin.value = coin.value + value; id.delete(); } ``` A module that defines a witness type and calls `mint` gets its own regulated currency: it alone decides how - and whether - to expose minting, burning, and transfers of its coins, while the base module implements the logic shared by all such currencies. This use of a witness is close to the [Capability](./capability) pattern, with an important difference: a capability is an object, so it authorizes whoever owns it - an account; a witness can only be constructed by the module defining it, so it authorizes code. Authorization with a witness is decided at the time the code is written, requires no storage, and cannot be transferred. ## One Time Witness While a struct can be created any number of times, there are cases where a struct should be guaranteed to be created only once. For this purpose, Sui provides the "One-Time Witness" - a special witness that can only be used once. We explain it in more detail in the [next section](./one-time-witness). > The standard library also provides a ready-made form of this proof: the > [Internal Permit](./../move-basics/internal-permit). An `internal::Permit` proves that the > call was authorized by the module defining `T` - without the library having to design a custom > witness type or require `drop` on `T` itself. ## Summary - Witness is a pattern of proving a certain property by constructing a proof. - In Move, a module proves ownership of a type by constructing it. - Witness is often used for generic type instantiation and authorization. ## Next Steps In the next section, we will learn about the [One Time Witness](./one-time-witness) pattern. --- # One Time Witness While the regular [Witness](./witness-pattern) is a great way to statically prove the ownership of a type, there are cases where we need to ensure that a witness is instantiated only once - and this is the purpose of the One Time Witness (OTW). ## Background To see the problem the OTW solves, let's try to build a simple generic coin implementation with the tools we already have. A `TreasuryCap` controls the supply of a coin of type `T`, and creating one requires a [witness](./witness-pattern) of `T`: ```move module book::simple_coin; /// Controls the supply of the Coin. public struct TreasuryCap has key, store { id: UID, total_supply: u64, } /// Create a new `TreasuryCap` with a witness. /// Vulnerable: nothing prevents the caller from creating /// multiple `TreasuryCap`s with the same witness! public fun new(_witness: T, ctx: &mut TxContext): TreasuryCap { TreasuryCap { id: object::new(ctx), total_supply: 0 } } ``` The regular witness proves that the calling module owns the type `T`, but it proves nothing about _how many times_ the witness has been - or will be - constructed. A dishonest developer can simply call `new` twice and keep a second treasury for themselves: ```move module book::simple_coin_cheater; /// The Coin witness... used twice. >_< public struct MOVE has drop {} fun init(ctx: &mut TxContext) { let treasury = book::simple_coin::new(MOVE {}, ctx); let secret_treasury = book::simple_coin::new(MOVE {}, ctx); transfer::public_transfer(treasury, ctx.sender()); transfer::public_transfer(secret_treasury, ctx.sender()); } ``` For anyone deciding whether to trust a coin built this way, there is a whole list of conditions to audit: that only one `TreasuryCap` exists for the given `T`, that the module has no backdoor to issue more, and that a future upgrade cannot add one. None of these conditions can be checked from within Move code - verifying them requires trust in the author, and careful (and repeated) review of the source. To remove the need for this trust, Sui introduces the One Time Witness - a witness that the system itself guarantees to be instantiated exactly once, checkable at runtime. ## Definition The OTW is a special type of witness that can be used only once. It cannot be manually created and it is guaranteed to be unique per module. The Sui execution environment treats a type as an OTW if it follows these rules: 1. Has only `drop` ability. 2. Has no fields. 3. Is not a generic type. 4. Named after the module with all uppercase letters. Here is an example of an OTW: ```move module book::one_time; /// The OTW for the `book::one_time` module. /// Only `drop`, no fields, no generics, all uppercase. public struct ONE_TIME has drop {} /// Receive the instance of `ONE_TIME` as the first argument. fun init(otw: ONE_TIME, ctx: &mut TxContext) { // do something with the OTW } ``` The OTW cannot be constructed manually, and any code attempting to do so will result in a compilation error. The OTW can be received as the first argument in the [module initializer](./module-initializer). And because the `init` function is called only once per module, the OTW is guaranteed to be instantiated only once. ## Enforcing the OTW To check if a type is an OTW, the `sui::types` module of the [Sui Framework](./sui-framework) offers a special function `is_one_time_witness`. This is the runtime counterpart of the rules above: a library function that expects an OTW should call it to make sure the received witness is the real, one-time one, and not a regular type with the `drop` ability. ```move use sui::types; const ENotOneTimeWitness: u64 = 1; /// Takes an OTW as an argument, aborts if the type is not OTW. public fun takes_witness(otw: T) { assert!(types::is_one_time_witness(&otw), ENotOneTimeWitness); } ``` This single `assert!` is what fixes the coin example from the [Background](#background) section: if `simple_coin::new` required an OTW instead of a regular witness, the second call in the cheater module would fail, because the OTW instance exists only once - in the first call. ## Summary The OTW pattern is a great way to ensure that a type is used only once. Most developers only need to know how to define and receive an OTW, while the checks and enforcement are mostly the concern of libraries and frameworks. For example, the `sui::coin` module requires an OTW in the `coin::create_currency` method, therefore enforcing that the `coin::TreasuryCap` is created only once - solving exactly the problem we described in the [Background](#background) section. OTW is a powerful tool that lays the foundation for the [Publisher](./publisher) object, which we will cover in the next section. --- # Publisher Authority Applications often need to prove _who published a type_. This is especially important in the context of digital assets, where the publisher may enable or disable certain features for their assets. The Publisher object, defined in the [Sui Framework](./sui-framework), is what allows the publisher to prove their _authority over a type_. ## Definition The Publisher object is defined in the `sui::package` module of the Sui Framework. It is a very simple, non-generic object that can be initialized once per module (and multiple times per package) and is used to prove the authority of the publisher over a type. To claim a Publisher object, the publisher must present a [One Time Witness](./one-time-witness) to the `package::claim` function. ```move module sui::package; public struct Publisher has key, store { id: UID, package: String, module_name: String, } ``` Here's a simple example of claiming a `Publisher` object in a module: ```move module book::publisher; use sui::package::{Self, Publisher}; /// Some type defined in the module. public struct Book {} /// The OTW for the module. public struct PUBLISHER has drop {} /// Uses the One Time Witness to claim the Publisher object. fun init(otw: PUBLISHER, ctx: &mut TxContext) { // Claim the Publisher object. let publisher: Publisher = sui::package::claim(otw, ctx); // Usually it is transferred to the sender. // It can also be stored in another object. transfer::public_transfer(publisher, ctx.sender()) } ``` > For the common claim-and-transfer flow, the `sui::package` module also provides a shorthand - > `package::claim_and_keep` - which claims the `Publisher` object and transfers it to the sender in > one call. ## Usage The Publisher object has two functions associated with it - `from_module` and `from_package` - which check whether a type was defined in the module or package this `Publisher` stands for: ```move // Checks if the type is from the same module, hence the `Publisher` has the // authority over it. assert!(publisher.from_module()); // Checks if the type is from the same package, hence the `Publisher` has the // authority over it. assert!(publisher.from_package()); ``` ## Publisher as Admin Role For small applications or simple use cases, the Publisher object can be used as an admin [capability](./capability). While in the broader context, the Publisher object has control over system configurations, it can also be used to manage the application's state. ```move /// Some action in the application gated by the Publisher object. public fun admin_action(cap: &Publisher, /* app objects... */ param: u64) { assert!(cap.from_module(), ENotAuthorized); // perform application-specific action } ``` However, the Publisher object lacks some of the native properties of [Capabilities](./capability), such as type safety and expressiveness. The signature of `admin_action` says nothing about the required authority - the function can be called by anyone holding _any_ `Publisher` object, so the authorization must be checked inside the function body. And since every published package produces a `Publisher`, forgetting the `from_module` check opens the action to every publisher on the network. For these reasons, it is important to be cautious when using the `Publisher` object as an admin role. ## Role on Sui Publisher is required for certain features on Sui. [Object Display](./display) can be created with the Publisher when it is set up outside of the module defining the type, and TransferPolicy - an important component of the Kiosk system - also requires the Publisher object to prove ownership of the type. ## Next Steps In the next section we will cover the first feature that can use the Publisher object - Object Display - a way to describe objects for clients, and standardize metadata. A must-have for user-friendly applications. --- # Object Display Objects on Sui are explicit in their structure and behavior and can be displayed in an understandable way. However, to support richer metadata for clients, there's a standard and efficient way of "describing" them to the client - the `Display` object, registered in the system _Display Registry_ defined in the [Sui Framework](./sui-framework). ## Background Historically, there were different attempts to agree on a standard structure of an object so it can be displayed in a user interface. One of the approaches was to define certain fields in the object struct which, when present, would be used in the UI. This approach was not flexible enough and required developers to define the same fields in every object, and sometimes the fields did not make sense for the object. ```move /// An attempt to standardize the object structure for display. public struct CounterWithDisplay has key { id: UID, /// If this field is present it will be displayed in the UI as `name`. name: String, /// If this field is present it will be displayed in the UI as `description`. description: String, // ... image: String, /// Actual fields of the object. counter: u64, // ... } ``` If any of the fields contained static data, it would be duplicated in every object. And, since Move does not have interfaces, it is not possible to know if an object has a specific field without "manually" checking the object's type, which makes the client fetching more complex. ## Object Display To address these issues, Sui introduces a standard way of describing an object for display. Instead of defining fields in the object struct, the display metadata is stored in a separate object - `Display` - which is associated with the type `T`. This way, the display metadata is not duplicated, and it is easy to extend and maintain. Another important feature of Sui Display is the ability to define templates and use object fields in those templates. Not only does it allow for a more flexible display, but it also frees the developer from the need to define the same fields with the same names and types in every object. > The Object Display is natively supported by the > [Sui Full Node](https://docs.sui.io/operators/full-node/sui-full-node), and the client can fetch > the display metadata for any object if the object type has a Display associated with it. ## Display Registry For every type `T` there is exactly one `Display`, and it lives at a predictable address. Both properties come from the _Display Registry_ - a system shared object located at the reserved address `0xd` (see [Reserved Addresses](./../appendix/reserved-addresses)). When a display is created, its object ID is [derived](https://docs.sui.io/references/framework/sui_sui/derived_object) from the registry's `UID` and the type `T`. As a result, anyone - including RPCs and other clients - can compute the ID of `Display` offline and fetch it directly, without scanning events or querying historical data. ```move module sui::display_registry; /// The root of display, to enable derivation of addresses. /// The address is system-generated at `0xd`. public struct DisplayRegistry has key { id: UID } /// Holds the display values for the type `T`. public struct Display has key { id: UID, /// All the (key,value) entries for a given display object. fields: VecMap, /// ID of the `DisplayCap` managing this display. `None` for /// migrated V1 displays until the capability is claimed. cap_id: Option, } /// The capability object that is used to manage the display. public struct DisplayCap has key, store { id: UID } ``` The `Display` object itself is _shared_, and the authority over it is represented by a separate owned object - the `DisplayCap` [capability](./capability). The holder of the capability can `set`, `unset`, or `clear` the display fields at any time, and the changes apply globally without the need to update every object. The capability can be transferred to another account, or built into an application with custom metadata-management functionality. ## Creating a Display A new `Display` is created with one of two functions, both taking a mutable reference to the `DisplayRegistry` and returning the `Display` together with its `DisplayCap`: - `display_registry::new` - takes an [Internal Permit](./../move-basics/internal-permit), and hence can only be called from the module that defines `T`; - `display_registry::new_with_publisher` - takes the [Publisher](./publisher) object, for cases when the display is created outside of the defining module. Because the registry is a shared object, it cannot be accessed in the [module initializer](./module-initializer) - the display is created by a separate, one-time call right after the package is published: ```move module book::arena; use std::string::String; use sui::display_registry::{Self, DisplayRegistry, DisplayCap}; /// Some object which will be displayed. public struct Hero has key { id: UID, class: String, level: u64, } /// Creates the `Display`. Call it exactly once, right after publishing: /// the registry holds a single `Display` per type, so a second call aborts. /// It is an `entry` function rather than a `public` one, so that a later /// package upgrade can remove it - upgrade rules freeze `public` functions, /// but not `entry` ones. entry fun create_display(registry: &mut DisplayRegistry, ctx: &mut TxContext) { let (mut display, cap) = display_registry::new( registry, internal::permit(), ctx, ); display.set(&cap, "name", "{class} (lvl. {level})"); display.set(&cap, "description", "One of the greatest heroes of all time. Join us!"); display.set(&cap, "link", "https://example.com/hero/{id}"); display.set(&cap, "image_url", "https://example.com/hero/{class}.jpg"); // Share the `Display` so clients can find it, and send the capability to // the publisher, who keeps it to update the fields later. display.share(); transfer::public_transfer(cap, ctx.sender()); } ``` The `set` calls define the template fields, and `share` finalizes the creation by sharing the `Display` object; the `DisplayCap` is then transferred to the publisher, who keeps it to update the fields later. Note that the function is defined as `entry` rather than `public`: a one-time setup function is best kept out of the package's public API, so that a later upgrade can remove it - [upgrade compatibility rules](https://docs.sui.io/develop/publish-upgrade-packages/upgrade) freeze `public` function signatures, but not `entry` ones. ## Standard Fields The fields that are supported most widely are: - `name` - A name for the object. The name is displayed when users view the object. - `description` - A description for the object. The description is displayed when users view the object. - `link` - A link to the object to use in an application. - `image_url` - A URL or a blob with the image for the object. - `thumbnail_url` - A URL to a smaller image to use in wallets, explorers, and other products as a preview. - `project_url` - A link to a website associated with the object or creator. - `creator` - A string that indicates the object creator. > Please refer to the [Sui Documentation](https://docs.sui.io/develop/objects/display) for the most > up-to-date list of supported fields. While there's a standard set of fields, the Display object does not enforce them. The developer can define any fields they need, and the client can use them as they see fit. Some applications may require additional fields and omit others, and the Display is flexible enough to support them. ## Template Syntax Every value in a Display is a _format string_ - a mix of literal text and expressions delimited by `{` and `}`. The simplest expression is a field path: `{path}` is replaced with the value of the field at that path, where the path is a dot-separated list of field names starting from the object being displayed. To output a literal brace, double it - `{{` becomes `{`. ```move /// Some common metadata for objects. public struct Metadata has store { name: String, description: String, published_at: u64 } /// The type with nested Metadata field. public struct LittlePony has key, store { id: UID, image_url: String, metadata: Metadata } ``` The Display for the type `LittlePony` above could be defined as follows: ```json { "name": "Just a pony", "image_url": "{image_url}", "description": "{metadata.description}" } ``` A field path is only the most basic expression. The full form of an expression has three parts - a _chain_ that navigates into the data, an optional list of _fallbacks_ separated by `|`, and an optional _transform_ prefixed with `:` that controls how the value is rendered: ```text { chain | fallback | ... : transform } ``` The following sections walk through the parts of this syntax that come up most often. For the complete grammar - literals, struct and enum values, derived-object access, and the exhaustive transform list - see the [Object Display Syntax](https://docs.sui.io/references/object-display-syntax) reference. ### Vector and Map Indexing A chain can index into a `vector` or a `VecMap` with square brackets. Numeric indices always carry a type suffix - `0u64`, not `0` - and another field's value can be used as the index: ```text {items[0u64]} first element of the `items` vector {items[idx]} use the `idx` field's value as the index {scores[6u32]} look up the key `6u32` in a VecMap, returns its value ``` ### Dynamic Field Access Templates can reach beyond the object's own fields and load [dynamic fields](./dynamic-fields) from storage. The `->` operator loads a dynamic field, `=>` loads a [dynamic object field](./dynamic-object-fields), and the key goes in brackets: ```text {parent->['color']} dynamic field with the string key 'color' {parent->['color'].x} read field `x` on the loaded value {parent=>['hat']} dynamic object field (the value is a full object) ``` Because each load reads from storage, they are budgeted: a template may perform at most 8 object loads by default, with `->` costing one and `=>` costing two. ### Transforms By default a value is rendered as a human-readable string. A transform after `:` changes that - useful for values that are not plain text, such as byte vectors or timestamps: | Transform | Effect | | ----------------- | ---------------------------------------------------------------- | | `str` _(default)_ | Human-readable string; UTF-8 for `String` and `vector`. | | `hex` | Lowercase, zero-padded hexadecimal. | | `base64` | Base64-encoded bytes; accepts `url` and `nopad` modifiers. | | `bcs` | BCS-serialized value, then Base64-encoded - for aggregate types. | | `json` | Structured JSON value; only when it is the whole format string. | | `timestamp` (`ts`)| A numeric value read as Unix milliseconds, formatted ISO 8601. | | `url` | Like `str`, but percent-encodes reserved URL characters. | ```text {amount:hex} render `amount` as hex {created_at:ts} "2023-04-12T17:00:00Z" {metadata:json} emit the whole struct as JSON ``` ### Fallbacks If a chain evaluates to null - a missing field, an out-of-bounds index, or a `None` [Option](./../move-basics/option) - the next chain after `|` is tried. A string literal in single quotes makes a convenient default: ```text {display_name | name | 'Anonymous'} ``` If every alternative is null, the whole format string evaluates to null and the field is omitted from the result. ## Migrating from V1 to V2 The registry-backed Display described on this page is the second version of the standard - _Display V2_. The original one - V1, implemented in the `sui::display` module - predates the registry: V1 `Display` objects were owned rather than shared, could only be created with the `Publisher` object, and were discovered through events. Any number of V1 displays could exist for the same type, and full nodes used the most recently updated one. V2 replaces event-based discovery with derivation from the registry, and reduces "any number of displays" to exactly one per type. Existing V1 displays were migrated to V2 automatically by a system migration: for every type with a V1 display, there is already a shared `Display` with the same fields and with `cap_id` set to `none`. To manage such a display, the creator claims its `DisplayCap` in one of two ways: - `claim` - consumes the legacy V1 `Display` object as the proof of authority over the type, destroying it in the process; - `claim_with_publisher` - uses the [Publisher](./publisher) object instead; the leftover V1 object can then be destroyed with `delete_legacy`. ```move use sui::display::Display as LegacyDisplay; /// Claim the `DisplayCap` for the system-migrated `Display`, giving up /// the legacy V1 `Display` object, which is destroyed in the process. public fun claim_display_cap( display: &mut display_registry::Display, legacy: LegacyDisplay, ctx: &mut TxContext, ): DisplayCap { display.claim(legacy, ctx) } ``` For a V1 display that was created after the system migration took place, the `display_registry::migrate_v1_to_v2` function performs the migration directly: it creates the V2 `Display`, copies the fields from the legacy object, destroys it, and returns the new display together with its capability. ## Further Reading - [Object Display](https://docs.sui.io/develop/objects/display) in the Sui Documentation - [Object Display Syntax](https://docs.sui.io/references/object-display-syntax) - the full template language reference - [Publisher](./publisher) - the representation of the creator - [Internal Permit](./../move-basics/internal-permit) - the authorization used to create a display --- # Events Onchain storage keeps the _current_ state of the application: objects, their fields, and their owners. What it does not keep is the history of actions that led to this state. A marketplace module stores listed items, but once an item is sold and the object changes hands, there is no onchain trace of the purchase - the price paid, the time of the sale, or the parties involved. Applications, however, often need exactly that: an activity feed, a trading history, or analytics. _Events_ are the mechanism for this. An event is a piece of data attached to the result of a successful transaction and stored offchain. Emitting an event does not modify any objects and costs no storage fees; instead, events are indexed by full nodes, and offchain services can query or subscribe to them. Events are the main way for a Move program to communicate with the outside world. ## Definition Events are emitted with the `emit` function defined in the [`sui::event`][event-framework] module of the [Sui Framework](./sui-framework): ```move module sui::event; /// Emit a custom Move event, sending the data offchain. /// /// Used for creating custom indexes and tracking onchain /// activity in a way that suits a specific application the most. /// /// The type `T` is the main way to index the event, and can contain /// phantom parameters, eg `emit(MyEvent)`. public native fun emit(event: T); ``` An event can be any custom type with the [copy](./../move-basics/copy-ability) and [drop](./../move-basics/drop-ability) abilities. Additionally, the Sui Verifier requires the type to be [_internal to the module_](./../storage/internal-constraint) that emits it: it is impossible to emit a type defined in another module, and, even though they satisfy the `copy + drop` requirement, [primitive types](./../move-basics/primitive-types) cannot be emitted either. This rule makes the event type an unforgeable label - an `ItemPurchased` event can only ever originate from the module that declares it. ## Emitting Events To emit an event, define a struct for it and pass an instance of the struct to `event::emit`. The event data is passed by value and sent offchain as part of the transaction result: ```move module book::events; use sui::coin::Coin; use sui::sui::SUI; use sui::event; /// The item that can be purchased. public struct Item has key { id: UID } /// Event emitted when an item is purchased. Contains the ID of the item and /// the price for which it was purchased. public struct ItemPurchased has copy, drop { item: ID, price: u64 } /// A marketplace function which performs the purchase of an item. public fun purchase(seller: address, coin: Coin, ctx: &mut TxContext): Item { let item = Item { id: object::new(ctx) }; // Create an instance of `ItemPurchased` and pass it to `event::emit`. event::emit(ItemPurchased { item: object::id(&item), price: coin.value() }); // Send the payment to the seller, return the item to the caller. transfer::public_transfer(coin, seller); item } ``` The type of the event serves as the primary filter for offchain queries - services subscribe to `ItemPurchased` events by naming the type. This suggests a simple design principle: emit a dedicated type per action, and name it after the action that happened, in past tense - `ItemPurchased`, `AuctionStarted`, `ConfigUpdated`. Inside the event, include the values an indexer would need to make sense of the action without fetching anything else: the IDs of the objects involved, amounts, and the relevant addresses. Note that events are attached to a _successful_ transaction: if the transaction aborts after the `emit` call, no events are recorded. ## Event Structure Events become part of the _transaction effects_, and the system attaches metadata to each of them: - the _sender_ - the address that signed the transaction; - the _transaction digest_ - linking the event to the transaction that emitted it; - the _timestamp_ - the time of the checkpoint that finalized the transaction, shared by all events of that transaction; - the _type signature_ of the event, including the package and module that emitted it. Because the sender and the transaction digest are always present in the metadata, there is no need to duplicate them in the event fields. A `sender: address` field in an event struct is redundant, unless the "logical" sender differs from the transaction signer (for example, in a sponsored transaction executed on behalf of a user). It is important to understand that events are a one-way channel. Emitted events are not stored onchain and cannot be read back by Move code - not in the same transaction, and not in any later one. If a value needs to be accessed by the program, it belongs in an object; if it needs to be seen by the outside world, it belongs in an event. ## Testing Events Because events are the interface between the application and its offchain services, it is important to test that the right events are emitted with the right values. The `sui::event` module provides two test-only functions for this: `num_events`, returning the number of events emitted so far in the test, and `events_by_type`, returning a vector of all emitted events of type `T`. ```move #[test] fun test_emit_item_purchased() { let ctx = &mut tx_context::dummy(); let item = Item { id: object::new(ctx) }; let item_id = object::id(&item); event::emit(ItemPurchased { item: item_id, price: 100 }); // Total number of events emitted in this test so far. assert_eq!(event::num_events(), 1); // Read back all `ItemPurchased` events and check their contents. let purchases = event::events_by_type(); assert_eq!(purchases.length(), 1); assert_eq!(purchases[0].item, item_id); assert_eq!(purchases[0].price, 100); std::unit_test::destroy(item); } ``` Since event structs are internal to the module, tests placed in the same module (or in a test module of the same package with appropriate accessors) can inspect their fields directly. ## Summary - Events attach application-defined data to the transaction result; they are indexed offchain and are the main way to notify the outside world about onchain activity. - Any custom type with `copy` and `drop` can be an event, but it must be internal to the emitting module - this makes the event type an unforgeable label. - Event metadata already contains the sender, the transaction digest, and a timestamp; event fields should carry action-specific data, such as object IDs and amounts. - Events cannot be read back by Move code - they are a one-way channel. - Use `num_events` and `events_by_type` to test emitted events. ## Further Reading - [sui::event][event-framework] module documentation. - [Using Events](https://docs.sui.io/guides/developer/sui-101/using-events) in the Sui Documentation - querying and subscribing to events offchain. [event-framework]: https://docs.sui.io/references/framework/sui/event --- # Balance and Coin Fungible tokens are the most common kind of digital asset: units of value that are interchangeable with each other, like money. On Sui, the main abstraction for fungible tokens is [`Coin`](https://docs.sui.io/references/framework/sui_sui/coin) - the object that wallets hold, transactions take as inputs, and applications accept as payment. Owning "10 SUI" means owning a `Coin` object with the value of 10 SUI. Two supporting types complete the standard - one layer below `Coin`, and one above: - [`Balance`](https://docs.sui.io/references/framework/sui_sui/balance) - the raw amount inside a `Coin`: a plain value without an object ID, which applications use to store and accumulate funds; - [`Currency`](https://docs.sui.io/references/framework/sui_sui/coin_registry) - a shared object describing the coin type itself: its metadata, supply, and regulatory status. This section walks through all three, and shows how to create a currency with the `sui::coin_registry` module - the standard way of doing it. ## Balance The `Balance` type is defined in the `sui::balance` module. It is a plain value with the `store` ability - not an object: it has no `UID` and no storage overhead of its own. This makes it the type of choice for _keeping_ funds: whenever an application needs to store or accumulate value inside its own types - a vault, a liquidity pool, an escrow - it embeds a `Balance`, not a `Coin`. ```move /// Storable balance - an inner struct of a Coin type. /// Can be used to store coins which don't need the key ability. public struct Balance has store { value: u64, } ``` The [phantom type parameter](./../move-basics/generics#phantom-type-parameters) `T` is what makes one balance different from another: `Balance` and `Balance` are distinct, non-interchangeable types, even though both store just a `u64`. `Balance` has no `copy`, no `drop`, and no public constructor for a non-zero value. A balance can only be created by increasing the total supply of `T`, and can only disappear by decreasing it. Everything in between - splitting, joining, storing - just moves the value around. This is the [ownership](./../move-basics/ownership-and-scope) guarantee applied to money: no duplication, no accidental loss. ```move // There is no public constructor for `Balance` - in this test we use // a test-only helper. Real balances come from minting or from a `Coin`. let mut balance = balance::create_for_testing(1000); assert_eq!(balance.value(), 1000); // Split part of the balance into a new `Balance`. let part = balance.split(300); assert_eq!(balance.value(), 700); assert_eq!(part.value(), 300); // Join it back; `join` returns the new total. let total = balance.join(part); assert_eq!(total, 1000); // A zero `Balance` can be created and destroyed freely, // as it does not represent any value. let zero = balance::zero(); assert_eq!(zero.value(), 0); zero.destroy_zero(); ``` ## Coin `Balance` cannot exist on its own in storage - it has to be wrapped in an object. The `sui::coin::Coin` type is the standard wrapper: ```move /// A coin of type `T` worth `value`. public struct Coin has key, store { id: UID, balance: Balance, } ``` With `key` and `store`, a `Coin` is a full-fledged object: it can be owned by an account, transferred, and passed into transactions as an input. The gas object used to pay for transactions is a `Coin`. This gives the standard its rule of thumb: `Coin` at the boundary, `Balance` inside. Funds enter an application as a `Coin`, are stored and accumulated as a `Balance`, and leave as a `Coin` again. The API mirrors `Balance` - splitting, joining, and converting between the two: ```move // Like `Balance`, `Coin` has no public constructor - here we use a // test-only helper to mint one out of thin air. let mut coin = coin::mint_for_testing(1000, ctx); assert_eq!(coin.value(), 1000); // `Coin` is an object, so splitting requires `ctx` to create a new UID. let part = coin.split(300, ctx); assert_eq!(coin.value(), 700); assert_eq!(part.value(), 300); // A `Coin` can be turned into a `Balance` and back. let balance = part.into_balance(); let part = balance.into_coin(ctx); // Join the split part back into the original coin. coin.join(part); assert_eq!(coin.value(), 1000); ``` > The samples above conjure their `Coin` and `Balance` out of thin air with the test-only > `coin::mint_for_testing` and `balance::create_for_testing` functions - the standard tools for > testing coin-handling code, covered in > [Using System Objects in Tests](./../testing/using-system-objects). In transactions, coins receive special treatment: the native `SplitCoins` and `MergeCoins` [commands](./../concepts/what-is-a-transaction#commands) operate on coins directly, so a wallet can prepare an exact payment - even split it off the gas coin - without calling any module functions. This is why modules rarely need to expose split or merge functionality of their own. The `sui::coin` module also provides `coin::take` and `coin::put` helpers, which combine the conversion and the split/join steps: `take` splits a `Coin` out of a `Balance`, and `put` merges a `Coin` into a `Balance`. They come in handy when an application stores funds as a `Balance` and sends them out as `Coin`s. > Coin objects are not the only way to hold fungible value: a newer mechanism keeps it directly at > an address, as a running total with no object to manage. It builds on the types described here, > and is covered in the [Address Balances](./address-balances) section. ## Currency and the Coin Registry A single `Coin` says nothing about the token `T` itself: its name, its symbol, how many decimals it uses, or how its supply is managed. This information is stored once per type in a `Currency` object, and all currencies are tracked by the `CoinRegistry` - a system object with the reserved address `0xc`: ```move /// System object found at address `0xc` that stores coin data for all /// registered coin types. public struct CoinRegistry has key { id: UID } ``` The `Currency` object holds everything there is to know about the coin type `T`: ```move /// Currency stores metadata such as name, symbol, decimals, icon_url and /// description, as well as supply state (optional) and regulatory status. public struct Currency has key { id: UID, /// Number of decimal places the coin uses for display purposes. decimals: u8, /// Human-readable name for the coin. name: String, /// Short symbol/ticker for the coin. symbol: String, /// Detailed description of the coin. description: String, /// URL for the coin's icon/logo. icon_url: String, /// Current supply state of the coin (fixed, burn-only, or unknown). supply: Option>, /// Regulatory status of the coin (regulated with deny cap or unknown). regulated: RegulatedState, /// ID of the treasury cap for this coin type, if registered. treasury_cap_id: Option, /// ID of the metadata capability for this coin type, if claimed. metadata_cap_id: MetadataCapState, /// Additional fields for extensibility. extra_fields: VecMap, } ``` Most of these fields get their own section on this page: the supply state, the regulatory status, and the two capabilities are all covered below. One field, however, deserves attention right away: `decimals`. Move has no fractional numbers - the value of a `Coin` is a plain integer, counting the currency's smallest units, and `decimals` tells clients where to put the decimal point _for display_. With `decimals = 8`, a `Coin` with the value `100_000_000` is displayed as `1` coin; the native SUI currency has 9 decimals, and its base unit even has a name of its own - MIST. Amounts in Move code - minting, splitting, comparing - are always expressed in base units. The `coin_registry` module is _the_ way to create a currency: it replaced the original `coin::create_currency` function, which stored metadata in a standalone `CoinMetadata` object (we cover the differences [at the end of this section](#legacy-coin-metadata)). It offers two ways to create a currency, both producing the same result: a shared `Currency` object with a [derived address](https://docs.sui.io/references/framework/sui_sui/derived_object), so that the metadata for any coin type can be found without knowing its object ID. ### Creating a Currency in `init` The most common flow uses a [One-Time Witness](./one-time-witness) to guarantee that a currency for the type can be created only once, in the [module initializer](./module-initializer): ```move /// A module that creates the GOLD currency on package publish. module book::gold; use sui::coin_registry; /// The One-Time Witness for the GOLD currency. public struct GOLD has drop {} /// Called once, on package publish. Creates the `Currency` and /// a `TreasuryCap` to manage the supply. fun init(otw: GOLD, ctx: &mut TxContext) { let (initializer, treasury_cap) = coin_registry::new_currency_with_otw( otw, 8, // decimals "GOLD", // symbol "Gold", // name "In-game gold currency", // description "https://example.com/gold.svg", // icon URL ctx, ); // Finalize the initializer, claiming the `MetadataCap`. let metadata_cap = initializer.finalize(ctx); // Transfer both capabilities to the publisher. transfer::public_transfer(treasury_cap, ctx.sender()); transfer::public_transfer(metadata_cap, ctx.sender()); } ``` The `new_currency_with_otw` call returns two values: - `CurrencyInitializer` - a temporary value used to configure the currency before it is published. It cannot be stored or dropped, so the transaction cannot succeed until it is consumed by the `finalize` call (a technique we explore in the [Hot Potato Pattern](./hot-potato-pattern) section); - `TreasuryCap` - the [capability](./capability) that controls minting and burning, explored in the [Supply and TreasuryCap](#supply-and-treasurycap) section below. The `finalize` call returns one more capability - the `MetadataCap`, which controls updates to the currency metadata. However, in the OTW flow, `finalize` does not complete the registration. Because `init` runs during publishing, before the `CoinRegistry` can be passed in as an argument, the `Currency` object takes a detour: `finalize` transfers it to the registry's address, where it waits for the second, closing step - `finalize_registration`: ```move /// The second step in the "otw" initialization of coin metadata, that takes in /// the `Currency` that was transferred from init, and transforms it in to a /// "derived address" shared object. /// /// Can be performed by anyone. public fun finalize_registration( registry: &mut CoinRegistry, currency: Receiving>, _ctx: &mut TxContext, ); ``` This function [receives](./../storage/transfer-to-object) the `Currency` sent to the registry and re-creates it as a shared object with a derived address. Until it is called, the registration is incomplete: the `Currency` is not shared, cannot be found at its derived address, and cannot be passed into any function that reads or updates it. The call is permissionless - anyone can make it, and indexers often do - but it should not be left to chance: > Treat `finalize_registration` as a mandatory part of the OTW flow, not as optional cleanup. The > publisher should call it in a follow-up transaction right after publishing - only then is the > currency fully registered and usable. ### Creating a Currency Dynamically The second flow does not require an OTW and can be performed at any time after the package is published - for example, in an application that creates currencies on demand. The `new_currency` function takes the `CoinRegistry` directly, and the `Currency` is shared immediately on `finalize`, with no extra registration step: ```move /// A module that creates the Doubloon currency dynamically - at any /// point after the package is published. module book::doubloon; use sui::coin::Coin; use sui::coin_registry::{Self, CoinRegistry, MetadataCap}; /// The type of the currency. For dynamic creation, the type must have /// `key` and only `key`. public struct Doubloon has key { id: UID } /// Creates the "Doubloon" currency. Unlike `init`, this function can be /// called at any time, but only from the module that defines `Doubloon`. public fun create_currency( registry: &mut CoinRegistry, ctx: &mut TxContext, ): (Coin, MetadataCap) { let (mut initializer, mut treasury_cap) = coin_registry::new_currency( registry, 6, // decimals "DBL", // symbol "Doubloon", // name "Pirate-themed currency", // description "https://example.com/doubloon.svg", // icon URL ctx, ); // Mint the entire supply upfront, then give up the `TreasuryCap`, // fixing the supply forever - no more minting or burning. let coins = treasury_cap.mint(1_000_000_000, ctx); initializer.make_supply_fixed(treasury_cap); // Finalize the initializer; `Currency` becomes a shared object. let metadata_cap = initializer.finalize(ctx); (coins, metadata_cap) } ``` ### One Type, Two Shapes Both flows denominate the currency with a marker type `T`, but they demand different shapes from it, matching how each flow proves that the currency is created only once: - `new_currency_with_otw` takes a `T` with `drop` - specifically, a [One-Time Witness](./one-time-witness): a `drop`-only struct with no fields, named after its module. The proof is the witness _value_ itself: it exists exactly once, is consumed by the call, and can never be produced again - so neither can the currency. - `new_currency` takes a _key-only_ `T` - `has key` and nothing else, with the single `id: UID` field. No instance of `T` is passed, only the type argument, so there is no witness value to prove anything. Instead, two checks stand in: `new_currency` is subject to the [internal constraint](./../storage/internal-constraint) - like `sui::event::emit`, it can only be called with a type defined in the calling module - and the registry aborts if a `Currency` has already been registered. A key-only type cannot have `drop`, so the same type can never be used with both flows. ## Supply and TreasuryCap The [Balance](#balance) section stated that value can only be created by increasing the total supply of `T`, and can only disappear by decreasing it. The type that does both is `Supply`, defined in `sui::balance` as the accounting counterpart of `Balance`: ```move module sui::balance; /// A Supply of T. Used for minting and burning. public struct Supply has store { value: u64, } /// Increase supply by `value`, creating a new `Balance`. public fun increase_supply(self: &mut Supply, value: u64): Balance; /// Destroy a `Balance`, decreasing the supply by its value. public fun decrease_supply(self: &mut Supply, balance: Balance): u64; ``` These two functions are the only gate through which value enters and leaves circulation, so every unit of `Balance` in existence is accounted for by the `Supply` - the number in the supply always equals the sum of all balances of `T`. And just as `Coin` is the object form of a `Balance`, the `TreasuryCap` - the capability returned by both creation flows - is the object form of a `Supply`: ```move module sui::coin; /// Capability allowing the bearer to mint and burn /// coins of type `T`. Transferable public struct TreasuryCap has key, store { id: UID, total_supply: Supply, } ``` Owning the `TreasuryCap` _is_ owning the supply authority. Its `mint` and `burn` functions are thin wrappers over the supply: `mint` increases it and wraps the new `Balance` into a `Coin`, `burn` unwraps a `Coin` and decreases the supply by its value. As long as the `TreasuryCap` exists, the current total can be read from it with `total_supply`. ```move // Test-only constructor - normally the `TreasuryCap` comes from // currency creation. let mut treasury_cap = coin::create_treasury_cap_for_testing(ctx); // Mint 100 units, increasing the total supply. let coin = treasury_cap.mint(100, ctx); assert_eq!(coin.value(), 100); assert_eq!(treasury_cap.total_supply(), 100); // Burn the coin, decreasing the total supply. let burned = treasury_cap.burn(coin); assert_eq!(burned, 100); assert_eq!(treasury_cap.total_supply(), 0); ``` Whoever owns the `TreasuryCap` controls the supply, so where the capability ends up is a design decision: kept by the publisher for a managed supply, stored inside an application object for programmatic minting, or given up entirely - as described next. > A `Supply` can also exist on its own: `balance::create_supply` turns a witness into a raw > `Supply` - it is, in fact, the example we used to introduce the > [Witness pattern](./witness-pattern) - and `treasury_into_supply` extracts the supply from a > `TreasuryCap`. These are low-level tools: a currency created through the registry should keep > its `TreasuryCap` intact, since the supply states described next operate on the capability. ## Supply States By default, the supply of a currency is flexible - the `Currency` object records it as `Unknown` and the `TreasuryCap` can mint and burn freely. The registry supports two irreversible transitions, both consuming the `TreasuryCap`: - `make_supply_fixed` - the supply can never change again. The `Doubloon` example above uses this: it mints the entire supply upfront and fixes it in the same call; - `make_supply_burn_only` - no more minting, but anyone can burn coins with the `coin_registry::burn` and `burn_balance` functions, which take the shared `Currency` object and permanently decrease the supply. Both can be applied either during initialization (on the `CurrencyInitializer`) or later, on the shared `Currency` object. Consuming the capability is not just ceremony: the transition unpacks the `TreasuryCap` and moves its `Supply` _into_ the `Currency` object - which is why, from that point on, the `Currency` itself tracks the total supply, readable onchain with `total_supply`. ## Managing Metadata The name, symbol, description, and icon URL of a currency can be updated after creation with the `set_name`, `set_symbol`, `set_description`, and `set_icon_url` functions - each requiring a reference to the `MetadataCap`. Like the `TreasuryCap`, the `MetadataCap` can be deleted with `delete_metadata_cap`, making the metadata immutable forever - or never claimed in the first place: `finalize_and_delete_metadata_cap` finalizes the currency with immutable metadata from the start. Either way, the deletion is recorded in the `Currency`, so the cap can never be claimed again. ## Reading a Currency A `Currency` is not only for its creator. As a shared object with a derived address, it can be found for any coin type and passed - by immutable reference - into any function, and the registry provides getters for every field: `decimals`, `name`, `symbol`, `description`, `icon_url`, the supply checks `is_supply_fixed` and `is_supply_burn_only`, and the `treasury_cap_id`, `metadata_cap_id`, and `deny_cap_id` functions to locate the currency's capabilities - or to verify that they were deleted (the deny cap belongs to _regulated_ currencies, covered [below](#regulated-currencies)). This turns coin metadata into something applications can rely on _on-chain_: a lending protocol can require the supply of a collateral coin to be fixed, and the function below uses `decimals` to accept deposits only in whole units of a currency: ```move module book::currency_reader; use sui::coin::Coin; use sui::coin_registry::Currency; /// The coin value has a fractional part. const ENotWholeUnit: u64 = 0; /// The number of whole units in the coin, calculated with the /// on-chain `decimals` value. Aborts if the value has a fractional /// part - a "half a coin" deposit is not allowed. public fun whole_units(currency: &Currency, coin: &Coin): u64 { let one_unit = 10u64.pow(currency.decimals()); assert!(coin.value() % one_unit == 0, ENotWholeUnit); coin.value() / one_unit } ``` ## Regulated Currencies A currency can opt into regulation during initialization by calling `make_regulated` on the `CurrencyInitializer`. This creates one more capability - `DenyCapV2` - whose owner maintains a _deny list_: addresses that cannot use `Coin` as transaction inputs. The list itself lives in the `DenyList` system object at the reserved address `0x403`, managed by the [sui::deny_list](https://docs.sui.io/references/framework/sui/deny_list) module. Optionally, a regulated currency can support a _global pause_, stopping all transfers of the coin type. This feature exists for compliance-heavy assets like stablecoins; most currencies are created without it. ## Legacy Coin Metadata Before the `CoinRegistry`, currencies were created with `coin::create_currency`, which produced a standalone `CoinMetadata` object instead of a `Currency`. This function is deprecated, but plenty of currencies created with it are still live, and some applications still expect `CoinMetadata` as an argument. The registry provides a bridge in both directions: - `migrate_legacy_metadata` registers an existing `CoinMetadata` in the registry, creating a `Currency` for it; - `borrow_legacy_metadata` produces a `CoinMetadata` view of a registry-native `Currency`, for compatibility with older interfaces (returned within the same transaction via a hot potato). New code should always use the `coin_registry` flows. ## Summary - `Coin` is the main abstraction for fungible tokens: an object that can be owned, transferred, and passed into transactions; - `Balance` is the unit of accounting inside a `Coin`: a non-object value that cannot be copied or dropped, only moved, split, and joined - and the type applications embed to keep funds; - `Currency` describes the coin type: metadata, supply state, and regulatory status. It is created through the `CoinRegistry` system object, either with an OTW in `init` or dynamically - and can be _read_ onchain by any module; - `Supply` is the accounting authority: the only gate through which `Balance` value is created and destroyed. `TreasuryCap` is its object form - it controls minting and burning, and can be given up to fix the supply; - `MetadataCap` controls metadata updates, and can be deleted to make them immutable; - coin values are integers of base units; the `decimals` field of a `Currency` is display-only. ## Further Reading - [Currency Standard](https://docs.sui.io/onchain-finance/fungible-tokens/currency) in Sui Documentation. - [sui::coin_registry](https://docs.sui.io/references/framework/sui_sui/coin_registry) module documentation. - [sui::coin](https://docs.sui.io/references/framework/sui_sui/coin) module documentation. - [sui::balance](https://docs.sui.io/references/framework/sui_sui/balance) module documentation. --- # Address Balances A [`Coin`](./balance-and-coin) is an object: to spend it, a transaction has to reference it by its ID, fetch it, and pass it in. That works well for discrete assets, but it makes an account's funds a set of individual objects that have to be tracked, merged, and split. _Address balances_ offer a different model: fungible value held directly at an address, as a running total, with no object to manage. Under the hood, the value lives in an onchain _accumulator_ keyed by the pair `(address, type)`. The balance of `T` at an address is a single number that goes up when funds are sent to it and down when they are withdrawn - much closer to how a bank account works than to a wallet full of coins. > Address balances are a recent addition to the Sui Framework. This section covers the core > `send_funds` / `redeem_funds` API, withdrawing from an object, and the transaction-level rules > that protect withdrawals from replay. ## Sending Funds to an Address Any `Coin` or [`Balance`](./balance-and-coin#balance) can be deposited into an address balance with `send_funds`. The value is consumed and credited to the recipient's balance of `T`: ```move /// Deposit a coin into the recipient's address balance. public fun pay(coin: Coin, recipient: address) { coin.send_funds(recipient); } ``` `send_funds` is defined on both `Coin` and `Balance`. For a `Coin`, it turns the coin into a `Balance` and adds it to the recipient's accumulator; there is no object left behind, and the recipient does not need to "accept" anything - the balance simply increases. > The current value of an address balance can be read from Move with > `balance::settled_funds_value`, given a reference to the system `AccumulatorRoot` object. As the > name suggests, it reports the funds _settled_ as of the beginning of the current consensus > commit - deposits made within the commit are not yet visible to it. ## Withdrawing Funds Going the other way - taking value _out_ of an address balance - is deliberately more restricted. You cannot read from an arbitrary address's balance and mint a coin from it; instead, a withdrawal is represented by a `Withdrawal>` value, defined in the `sui::funds_accumulator` module of the Sui Framework: ```move /// A permission to withdraw up to `limit` units of `T` from `owner`. public struct Withdrawal has drop { owner: address, limit: u256, } ``` A `Withdrawal` is an _authorization_, not the funds themselves. It records whose balance is being drawn from (`owner`) and the maximum amount that may be taken (`limit`). It has `drop`, so an unused one can simply be discarded. The transaction provides it - a `Withdrawal` for the transaction sender is supplied as an input by the transaction builder, in the same spirit as the gas coin or a [received object](./../storage/transfer-to-object). There is no constructor for it in user code. A transaction that spends from the sender's address balance therefore looks like this: the `Withdrawal` comes in as an input, checked against the sender's balance at signing, and a command turns it into a `Coin`: ```text // Spending 1_000 MIST from the sender's address balance // Input 0: Withdrawal> { owner: sender, limit: 1_000 } // Input 1: recipient address 0: sui::coin::redeem_funds(Input(0)); // -> Coin 1: TransferObjects([Result(0)], Input(1)); ``` Once a function has a `Withdrawal`, it redeems it into a real `Coin` with `redeem_funds`: ```move /// Redeem a withdrawal provided by the transaction into a spendable coin. public fun collect(w: Withdrawal>, ctx: &mut TxContext): Coin { coin::redeem_funds(w, ctx) } ``` Redemption is where the amount is actually moved out of the accumulator. It can only be performed from the module that defines the withdrawn type - this is enforced with the [internal permit](./../move-basics/internal-permit) mechanism, which is exactly why `sui::coin` and `sui::balance` (the modules that define `Coin` and `Balance`) are the ones exposing `redeem_funds`. ## Inspecting and Splitting a Withdrawal Before redeeming, the `Withdrawal` can be inspected and divided. This is useful when a single withdrawal needs to fund several operations: ```move public fun inspect_and_split(w: &mut Withdrawal>): (address, u256) { // Read who the funds belong to and how much may still be withdrawn. let owner = w.owner(); let remaining = w.limit(); // Carve off a sub-withdrawal with its own, smaller limit. The // parent's limit is reduced by the same amount. let sub: Withdrawal> = w.split(100); // Withdrawals from the same owner can be joined back together, // adding the limits up. w.join(sub); (owner, remaining) } ``` Splitting and joining a `Withdrawal` only moves the _limit_ around; no funds change hands until `redeem_funds` is called. Joining requires both withdrawals to have the same `owner`, and aborts otherwise. ## Withdrawing from an Object The owner of a `Withdrawal` does not have to be an account - it can be an object. An object with an address balance can produce a withdrawal from its own funds with `withdraw_funds_from_object`, passing a mutable reference to its `UID`: ```move /// Withdraw `value` units of `T` held at this object's address. public fun withdraw(id: &mut UID, value: u64): Withdrawal> { balance::withdraw_funds_from_object(id, value) } ``` This lets any object - a shared vault, an escrow, a treasury - hold and pay out fungible value without wrapping individual `Coin` objects. The withdrawal it produces is redeemed the same way as a sender's - through `redeem_funds`. ## Replay Protection and Parallel Execution Address balances also change how a transaction proves that it is unique and cannot be replayed. The usual anchor is an [owned object](./../object/ownership#account-owner-or-single-owner): every object carries a [version](./../object/object-model) that the system bumps on each change, so a signed transaction referencing it can execute only once - after the version moves, the transaction no longer matches. The gas coin normally provides this anchor for free. A transaction that has no owned-object input - for instance, one that pays gas straight from an address balance, or whose inputs are only shared objects - has nothing to anchor it, so it must carry the protection itself. Two fields of the transaction data cover this. SDKs set them when they build such a transaction, so this is a matter of how the transaction is _constructed_ rather than anything in Move code: - **Expiration (`ValidDuring`).** The transaction sets its expiration to `TransactionExpiration::ValidDuring` with a `min_epoch` and a `max_epoch` spanning at most one epoch (`max_epoch <= min_epoch + 1`). Bounding validity to a narrow epoch window bounds the window in which the transaction could be replayed, taking the place of the version check that protects owned objects. - **Nonce.** The transaction includes a `nonce` - an arbitrary value whose only job is to make two otherwise-identical transactions distinct. Unlike the nonces of account-based chains, it is not sequential and has no gap problem; it simply lets transactions that would otherwise share a digest coexist. These same properties are what keep such transactions parallelizable: with unique digests and the [per-object ordering](./../object/fast-path-and-consensus#consensus-path) that Sui already uses, non-conflicting withdrawals never have to wait on one another. ## Summary - An _address balance_ is fungible value of type `T` held directly at an address in an onchain accumulator, rather than as a `Coin` object; - `coin.send_funds(recipient)` (or `balance.send_funds`) deposits value into an address balance, consuming the coin; - withdrawing requires a `Withdrawal>` - an authorization with an `owner` and a `limit` - which the transaction provides for the sender, or an object provides for itself; - `coin::redeem_funds` turns a `Withdrawal` into a `Coin`, and can only be called from the module defining the type, via the [internal permit](./../move-basics/internal-permit) mechanism; - a transaction with no owned-object input (paying gas from an address balance, or using only shared objects) carries its own replay protection: a `ValidDuring` expiration bounded to one epoch, and a `nonce` that makes its digest unique. ## Further Reading - [sui::balance](https://docs.sui.io/references/framework/sui/balance) module documentation. - [Using Address Balances](https://docs.sui.io/onchain-finance/asset-custody/address-balances/using-address-balances) in the Sui Documentation. - [Balance and Coin](./balance-and-coin) for the object-based side of fungible tokens. --- # Pattern: Hot Potato A special case in the abilities system - a struct without any abilities - is called _hot potato_. It cannot be stored (not as [an object](./../storage/key-ability) nor as [a field in another struct](./../storage/store-ability)), it cannot be [copied](./../move-basics/copy-ability) or [discarded](./../move-basics/drop-ability). Hence, once constructed, it must be gracefully [unpacked by its module](./../move-basics/struct), or the transaction will abort due to unused value without drop. > If you're familiar with languages that support _callbacks_, you can think of a hot potato as an > obligation to call a callback function. If you don't call it, the transaction will abort. The name comes from the children's game where a ball is passed quickly between players, and none of the players want to be the last one holding it when the music stops, or they are out of the game. This is the best illustration of the pattern - the instance of a hot-potato struct is passed between calls, and none of the modules can keep it. ## Defining a Hot Potato A hot potato can be any struct with no abilities. For example, the following struct is a hot potato: ```move public struct Request {} ``` Because the `Request` has no abilities and cannot be stored or ignored, the module must provide a function to unpack it. For example: ```move /// Constructs a new `Request` public fun new_request(): Request { Request {} } /// Unpacks the `Request`. Due to the nature of the hot potato, this function /// must be called to avoid aborting the transaction. public fun confirm_request(request: Request) { let Request {} = request; } ``` ## Example Usage In the following example, the `Promise` hot potato is used to ensure that the borrowed value, when taken from the container, is returned back to it. The `Promise` struct contains the ID of the borrowed object, and the ID of the container, ensuring that the borrowed value was not swapped for another and is returned to the correct container. ```move /// Trying to return value to incorrect container. const ENotCorrectContainer: u64 = 0; /// Trying to return incorrect value. const ENotCorrectValue: u64 = 1; /// A generic container for any Object with `key + store`. The Option type /// is used to allow taking and putting the value back. public struct Container has key { id: UID, value: Option, } /// A Hot Potato struct that is used to ensure the borrowed value is returned. public struct Promise { /// The ID of the borrowed object. Ensures that there wasn't a value swap. id: ID, /// The ID of the container. Ensures that the borrowed value is returned to /// the correct container. container_id: ID, } /// A function that allows borrowing the value from the container. public fun borrow_val(container: &mut Container): (T, Promise) { let value = container.value.extract(); let id = object::id(&value); (value, Promise { id, container_id: object::id(container) }) } /// Put the taken item back into the container. public fun return_val( container: &mut Container, value: T, promise: Promise ) { let Promise { id, container_id } = promise; assert!(object::id(container) == container_id, ENotCorrectContainer); assert!(object::id(&value) == id, ENotCorrectValue); container.value.fill(value); } ``` ## Applications Below we list some of the common use cases for the hot potato pattern. ### Borrowing As shown in the [example above](#example-usage), the hot potato is very effective for borrowing with a guarantee that the borrowed value is returned to the correct container. While the example focuses on a value stored inside an `Option`, the same pattern can be applied to any other storage type, say a [dynamic field](./dynamic-fields). ### Flash Loans The canonical example of the hot potato pattern is the flash loan - a loan that is borrowed and repaid in the same transaction. The borrowed funds are used to perform some operations, and the repaid funds are returned to the lender. The hot potato pattern ensures that the borrowed funds are returned to the lender. An example usage of this pattern may look like this: ```move // Borrow the funds from the lender; the `potato` obligates us to repay. let (funds, potato) = lender.borrow(amount); // Perform some operations with the borrowed funds. let asset = dex.trade(funds); let proceeds = another_contract::do_something(asset); // Repay the loan and keep the profit. let payback = proceeds.split(amount, ctx); lender.repay(payback, potato); transfer::public_transfer(proceeds, ctx.sender()); ``` > An outstanding hot potato also affects what the rest of the transaction is allowed to do: values > entangled with it cannot be passed to non-`public` `entry` functions until the potato is > consumed. The exact rules - with a worked flash-loan example - are described in > [Entry Functions](./../move-advanced/entry-functions). ### Variable-path Execution The hot potato pattern can be used to introduce variation in the execution path. For example, if there is a module which allows purchasing a `Phone` for some "Bonus Points" or for USD, the hot potato can be used to decouple the purchase from the payment. The approach is very similar to how some shops work - you take the item from the shelf, and then you go to the cashier to pay for it. ```move /// Trying to purchase `Phone` with incorrect price of `BonusPoints` or `USD`. const ENotCorrectPrice: u64 = 0; /// A `Phone`. Can be purchased in a store. public struct Phone has key, store { id: UID } /// A ticket that must be paid to purchase the `Phone`. public struct Ticket { amount: u64 } /// Return the `Phone` and the `Ticket` that must be paid to purchase it. public fun purchase_phone(ctx: &mut TxContext): (Phone, Ticket) { ( Phone { id: object::new(ctx) }, Ticket { amount: 100 } ) } /// The customer may pay for the `Phone` with `BonusPoints`. public fun pay_in_bonus_points(ticket: Ticket, payment: Coin) { let Ticket { amount } = ticket; assert!(payment.value() == amount, ENotCorrectPrice); abort // omitting the rest of the function } /// The customer may pay for the `Phone` with `USD`. public fun pay_in_usd(ticket: Ticket, payment: Coin) { let Ticket { amount } = ticket; assert!(payment.value() == amount, ENotCorrectPrice); abort // omitting the rest of the function } ``` This decoupling technique allows separating the purchase logic from the payment logic, making the code more modular and easier to maintain. The `Ticket` could be split into its own module, providing a basic interface for the payment, and the shop implementation could be extended to support other goods without changing the payment logic. ### Compositional Patterns Hot potato can be used to link together different modules in a compositional way. Its module may define ways to interact with the hot potato, for example, stamp it with a type signature, or to extract some information from it. This way, the hot potato can be passed between different modules, and even different packages within the same transaction. ### Usage in the Sui Framework The pattern is used in various forms in the Sui Framework. Here are some examples: - [sui::borrow][borrow-framework] - uses hot potato to ensure that the borrowed value is returned to the correct container. - [sui::transfer_policy][transfer-policy-framework] - defines a `TransferRequest` - a hot potato which can only be consumed if all conditions are met. - [sui::token][token-framework] - in the Closed Loop Token system, an `ActionRequest` carries the information about the performed action and collects approvals similarly to `TransferRequest`. - [sui::package][package-framework] - the `UpgradeTicket` and `UpgradeReceipt` guarding the [package upgrade](./package-upgrades) flow are hot potatoes: an authorized upgrade must be performed and committed within the same transaction. [borrow-framework]: https://docs.sui.io/references/framework/sui/borrow [package-framework]: https://docs.sui.io/references/framework/sui/package [transfer-policy-framework]: https://docs.sui.io/references/framework/sui/transfer_policy [token-framework]: https://docs.sui.io/references/framework/sui/token ## Summary - A hot potato is a struct without abilities; its module must provide ways to create and destroy it. - Hot potatoes are used to ensure that some action is taken before the transaction ends, similar to a callback. - Most common use cases for hot potato are borrowing, flash loans, variable-path execution, and compositional patterns. --- # Package Upgrades As we mentioned in the [Packages](./../concepts/packages) concept, published packages are _immutable_ - the bytecode stored onchain can never be modified or deleted. Yet real applications need to evolve: bugs get fixed, features get added, and dependencies move forward. Sui reconciles these two requirements with _package upgrades_ - a way to publish a new version of a package while keeping every previous version intact. This section explains the mechanics: what an upgrade can and cannot change, the `UpgradeCap` object that authorizes upgrades, and - most importantly - what upgrades mean for the state your package has already created. For design advice on writing upgrade-friendly code, see the [Upgradeability Practices](./../guides/upgradeability-practices) guide. ## An Upgrade Is a New Package An upgrade does not touch the published bytecode. Instead, it publishes the new version of the code at a _new address_ and records it as the successor of the previous version. Both versions - in fact, all versions ever published - remain onchain side by side: ``` 0xAAA... <- version 1, published 0xBBB... <- version 2, upgrade of 0xAAA 0xCCC... <- version 3, upgrade of 0xBBB, the latest version ``` This has a consequence that is easy to miss: **old versions of a package remain callable**. An upgrade does not redirect anyone - a transaction can still call functions of version 1 directly, and packages that depend on version 1 keep calling version 1 until they upgrade their own dependency. Publishing a fix does not, by itself, stop the buggy version from being used. We will return to this point when we talk about [state](#upgrades-and-state). Types, however, are not duplicated across versions. A struct keeps the identity of the package version that first _defined_ it: a `Counter` type from version 1 is exactly the same type in version 2, and objects created before the upgrade are fully compatible with the new code. A type first added in version 2 belongs to version 2, and so on. ## What Can Change An upgraded package must stay _compatible_ with the previous version, so that existing callers and dependent packages don't break. Under the default - most permissive - upgrade policy, an upgrade can: - change the implementation of any function; - add new modules, functions, and types; - change, add, or remove `public(package)`, private, and non-public [`entry`](./../move-advanced/entry-functions) functions; - change dependencies. And it can not: - remove a module; - change or remove the signature of a `public` function; - change or remove an existing type definition - fields, abilities, and type parameters of every struct and enum are frozen forever, whether public or not. In short: public signatures and data layouts are permanent, implementations are not. This is why the [Upgradeability Practices](./../guides/upgradeability-practices) guide recommends keeping `public` surface minimal and structs thin - every `public` function and every struct field is a commitment for the lifetime of the package. ## The `UpgradeCap` When a package is published, the `Publish` command returns an `UpgradeCap` - an object defined in the `sui::package` module of the [Sui Framework](./sui-framework). It is a classic [capability](./capability): whoever owns it can upgrade the package, and no one else can. ```move module sui::package; /// Capability controlling the ability to upgrade a package. public struct UpgradeCap has key, store { id: UID, /// (Mutable) ID of the package that can be upgraded. package: ID, /// (Mutable) The number of upgrades that have been applied /// successively to the original package. Initially 0. version: u64, /// What kind of upgrades are allowed. policy: u8, } ``` The `package` field always points at the latest version - only the latest version of a package can be upgraded, so the chain of versions never forks. The upgrade itself is a three-step dance inside a single transaction: `authorize_upgrade` takes the `UpgradeCap` and returns an `UpgradeTicket`; the `Upgrade` transaction command consumes the ticket, verifies and publishes the new bytecode, and returns an `UpgradeReceipt`; finally, `commit_upgrade` applies the receipt back to the `UpgradeCap`. Both the ticket and the receipt are [hot potatoes](./hot-potato-pattern) - they cannot be stored or dropped, so an authorized upgrade cannot be left half-finished. In practice the whole flow is built for you by the `sui client upgrade` CLI command. The `policy` field stores the most permissive kind of upgrade the capability allows. It starts at _compatible_ - the default policy described [above](#what-can-change) - and can be restricted to _additive_ (only new functionality can be added, existing code is frozen) or _dependency-only_ (only dependencies can be changed). Restriction is a one-way street: `only_additive_upgrades` and `only_dep_upgrades` can tighten the policy, but nothing can loosen it back. And because `authorize_upgrade` is a regular public function taking the `UpgradeCap`, the capability can be wrapped in a custom object to enforce arbitrary upgrade rules - a timelock, a multisig, or a vote. ## Making a Package Immutable The final restriction is giving up upgrades altogether. Deleting the `UpgradeCap` makes the package truly immutable - no one will ever be able to publish a new version: ```move /// Discard the `UpgradeCap` to make a package immutable. public entry fun make_immutable(cap: UpgradeCap) { let UpgradeCap { id, package: _, version: _, policy: _ } = cap; id.delete(); } ``` This is irreversible, and that is exactly the point: it is the strongest guarantee a package can offer. Users and dependent packages know the code they reviewed is the code that will run forever. The trade-off is equally permanent - no bug can ever be fixed. Immutability is a common choice for small foundational libraries, and a dangerous one for evolving applications. ## Upgrades and State Objects are stored outside of packages, and an upgrade does not touch them: a shared object created by version 1 is just as accessible to version 1 as it is to version 2. Combined with the fact that old versions remain callable, this leads to the central problem of upgrades: **without explicit versioning, the old code keeps full access to the state**. If version 2 fixes a bug in a function that mutates a shared object, an attacker can simply keep calling the version 1 function - on the very same object. The solution is to version the state itself. The object carries a `version` field, the package carries a `VERSION` constant, and every function that touches the object first checks that the two match: ```move module book::versioned_state; /// The version of the package this module belongs to. Incremented on /// every upgrade that has to invalidate previous versions. const VERSION: u8 = 2; /// Trying to use an object with an older or newer package version. const EVersionMismatch: u64 = 0; /// Shared state of the application; the `version` field gates access, /// tying the object to a single version of the package. public struct Counter has key { id: UID, version: u8, value: u64, } /// Every function that uses the shared object starts with a version /// check: only the package version stored in the object may proceed. public fun increment(counter: &mut Counter) { assert!(counter.version == VERSION, EVersionMismatch); counter.value = counter.value + 1; } ``` Constants are baked into the bytecode, so each published version compares the object against its own number: version 1 bytecode checks for `1`, version 2 bytecode checks for `2`. As long as the object's field says `1`, the old code keeps working and the new code aborts - and the moment the field is bumped to `2`, the situation flips: every call into the old version aborts with `EVersionMismatch`, and only the latest code can proceed. Bumping the version is how the old package is _decommissioned_. ## Migrating State The version bump - the _migration_ - can be performed in two ways, and the choice depends on how much state there is and who can reach it. The straightforward way is an _eager_ migration: right after the upgrade, the holder of an admin [capability](./capability) calls a `migrate` function which bumps the version of the shared object in a single transaction: ```move /// Grants the holder the permission to migrate the shared state. public struct AdminCap has key, store { id: UID } /// The object is already migrated to the current version. const ENotUpgrade: u64 = 1; /// Bump the version of the shared object, so that only the current /// package version can use it. Called by the admin after an upgrade. public fun migrate(counter: &mut Counter, _: &AdminCap) { assert!(counter.version < VERSION, ENotUpgrade); counter.version = VERSION; } ``` Eager migration is a clean cut-over and is the right choice when the state is a handful of shared objects the publisher controls. It falls short when it can't reach everything: an application may have thousands of objects, or the objects may be _owned_ by users - and only the owner can send a transaction touching an owned object. For these cases there is _lazy_ migration: instead of migrating everything up front, each object is migrated the first time the new code touches it. This is also the answer to a limitation we saw [earlier](#what-can-change) - struct layouts can never change, so how does state evolve at all? By keeping the base object thin and storing the actual content in a [dynamic field](./dynamic-fields), which can be swapped for a new shape at any time: ```move module book::versioned_config; use sui::dynamic_field as df; /// The current version of the package. const VERSION: u8 = 2; /// The base object stays thin - its layout can never change in an /// upgrade. The actual configuration is attached as a dynamic field. public struct Config has key { id: UID, version: u8, } /// Configuration attached as a dynamic field in version 1. public struct ConfigV1 has store { fee: u64 } /// Version 2 of the configuration adds a new field. public struct ConfigV2 has store { fee: u64, discount: u64 } /// Read the fee, migrating the configuration on first access. public fun fee(config: &mut Config): u64 { config.migrate_if_needed(); df::borrow(&config.id, 0).fee } /// Replace `ConfigV1` with `ConfigV2` the first time the object is /// used after the upgrade. fun migrate_if_needed(config: &mut Config) { if (config.version == 1) { let ConfigV1 { fee } = df::remove(&mut config.id, 0u8); df::add(&mut config.id, 0u8, ConfigV2 { fee, discount: 0 }); config.version = VERSION; } } ``` Version 1 of this package attached a `ConfigV1` to the object; version 2 defines a richer `ConfigV2` and quietly replaces the old value on first access. No coordinated migration is needed - objects upgrade themselves as they are used, whether there are ten of them or ten million, owned or shared. ## Summary - An upgrade publishes a new version of a package at a new address; all previous versions stay onchain and _remain callable_. - Compatibility rules protect callers: implementations can change and new code can be added, but `public` function signatures and type definitions are permanent. - The `UpgradeCap` is the capability authorizing upgrades; its policy can be restricted one way - from compatible to additive to dependency-only - and deleting it via `make_immutable` makes the package immutable forever. - State is not part of the package: without explicit versioning, old versions keep full access to shared objects. A `version` field checked against a package `VERSION` constant decommissions old code. - Migrations can be _eager_ - an admin bumps the version right after the upgrade - or _lazy_ - each object migrates on first access, which also allows evolving the shape of the state through dynamic fields. ## Further Reading - [Upgradeability Practices](./../guides/upgradeability-practices) guide on designing upgrade-friendly packages. - [Package Upgrades](https://docs.sui.io/concepts/sui-move-concepts/packages/upgrade) in the Sui documentation. - [Custom Upgrade Policies](https://docs.sui.io/concepts/sui-move-concepts/packages/custom-policies) in the Sui documentation. --- # Onchain Randomness Randomness is a surprisingly hard problem for a blockchain. Execution has to be deterministic - every validator must run a transaction and arrive at exactly the same result - and every input is public. Values that may look random, such as the [current time](./epoch-and-time), the epoch, or a transaction digest, are predictable or, worse, can be influenced by the sender or by validators. When there is money on the line, "looks random" is not enough: any source of randomness that can be predicted or biased will eventually be exploited. To solve this, Sui generates randomness _collectively_: at the beginning of each epoch, validators run a distributed key generation protocol, and then, on every consensus commit, they jointly produce a new random value that no single party - not even a validator - could know in advance. This value is written into the `Random` system object and made available to Move programs. ## The `Random` Object The `Random` object is defined in the `sui::random` module and has a reserved address `0x8` (see [Reserved Addresses](./../appendix/reserved-addresses)). The address is the same on every network - localnet, devnet, testnet, and mainnet - so it can be safely hardcoded in applications and client code. Similar to the [Clock](./epoch-and-time#time) object, it is a shared object which cannot be accessed mutably - a transaction attempting to take it by a mutable reference will fail. This allows parallel access to randomness, and protects the global state from tampering. ```move module sui::random; /// Singleton shared object which stores the global randomness state. /// The actual state is stored in a versioned inner field. public struct Random has key { id: UID, inner: Versioned, } ``` The inner state of the object is updated by the system on every consensus commit, and its unpredictability does not degrade over the course of an epoch. ## Using Randomness Randomness is not read from the `Random` object directly. Instead, a transaction creates a `RandomGenerator` - a local source of random values, derived from the global state and unique to the transaction. The generator provides methods for the common needs: booleans, integers of every size, integers in a range (bounds are inclusive), raw bytes, and shuffling of vectors: ```move #[test] fun test_generator_methods() { let mut generator = random::new_generator_for_testing(); // Booleans, integers of any size, and integers in a range. let coin_flip: bool = generator.generate_bool(); let any_u64: u64 = generator.generate_u64(); let dice: u8 = generator.generate_u8_in_range(1, 6); // Random bytes and shuffling of vectors. let bytes = generator.generate_bytes(32); let mut cards = vector[1u8, 2, 3, 4, 5]; generator.shuffle(&mut cards); } ``` A typical use looks like this: a function takes `&Random`, creates a generator with `new_generator`, and uses it to produce as many values as it needs. The following example mints a `Medal` of a random quality - 10% chance of Gold, 30% of Silver, and 60% of Bronze: ```move const GOLD: u8 = 0; const SILVER: u8 = 1; const BRONZE: u8 = 2; /// A medal of a random quality, awarded to the caller. public struct Medal has key { id: UID, quality: u8, } /// The entry function - a thin "facade" which takes the `Random` object, /// creates a generator, and forwards it to the implementation. entry fun mint_medal(random: &Random, ctx: &mut TxContext) { let mut generator = random.new_generator(ctx); let medal = mint_medal_impl(&mut generator, ctx); transfer::transfer(medal, ctx.sender()); } /// The actual implementation: 10% for Gold, 30% for Silver, 60% for Bronze. /// Thanks to `public(package)` visibility and the `RandomGenerator` /// parameter, this function can be called directly in tests. public(package) fun mint_medal_impl(generator: &mut RandomGenerator, ctx: &mut TxContext): Medal { let value = generator.generate_u8_in_range(1, 100); let quality = if (value <= 10) GOLD else if (value <= 40) SILVER else BRONZE; Medal { id: object::new(ctx), quality } } ``` The example is intentionally split into two functions, and this split is the recommended way to structure code that uses randomness. Let's look at why. ## Encapsulating Randomness Correctly The `mint_medal` function is declared as a private [entry](./../move-basics/visibility) function - it can be called from a transaction, but not from other modules. This is intentional, and it is the single most important rule of using randomness: > Functions that take `&Random` (or `RandomGenerator`) as a parameter should never be `public`. This > includes `public entry` - a `public entry` function is still callable from other modules. For > randomness, always use a private `entry` function. To see why, let's break the rule. Here is a variation of the same function which is `public` and returns the result of the roll: ```move /// Mints a `Medal`, transfers it to the caller, and returns the quality. public fun risky_mint(random: &Random, ctx: &mut TxContext): u8 { let mut generator = random.new_generator(ctx); let medal = mint_medal_impl(&mut generator, ctx); let quality = medal.quality; transfer::transfer(medal, ctx.sender()); quality } ``` Nothing prevents another module from wrapping this function, inspecting the outcome, and aborting if it is not favorable. An abort rolls back all effects of the transaction, so at the price of gas, the attacker gets to "re-roll" - retrying until they win: ```move /// The module of an attacker. module attacker::exploit; entry fun re_roll(random: &Random, ctx: &mut TxContext) { let quality = book::randomness::risky_mint(random, ctx); // Not Gold? Abort, revert all effects, and try again // in the next transaction. assert!(quality == 0); } ``` Importantly, this is not a hard limit - the `risky_mint` function compiles. The Move linter flags the risky signature with the `public_random` warning, which should be treated as an error unless you know exactly what you are doing: ``` warning[Lint W99006]: Risky use of 'sui::random' │ │ public fun risky_mint(random: &Random, ctx: &mut TxContext): u8 { │ ^^^^^^^ 'public' function 'risky_mint' accepts 'Random' as a parameter │ = Functions that accept 'sui::random::Random' as a parameter might be abused by attackers by inspecting the results of randomness = Non-public functions are preferred ``` What Sui does enforce - at the protocol level - is transaction composition: in a programmable transaction block, a command that uses `Random` can only be followed by `TransferObjects` or `MergeCoins` commands. Neither of them can inspect a value or abort based on it, which makes randomness _non-composable by design_: the result of a random roll can never be acted upon by any other code in the same transaction. The outcome is delivered exclusively through the effects of the function - such as the `Medal` object transferred to the caller. The `entry` function is still inconvenient to test: it requires the full `Random` object - a shared object which takes effort to set up in a test. This is why the actual logic lives in a separate function with `public(package)` visibility, which takes a `RandomGenerator` instead of `Random`: - the `entry` function is a thin facade: it creates the generator and passes it on; - the `public(package)` function contains the logic, returns a value, and can be called directly in tests - with a test-only generator, no `Random` object required. Note that the inner function must not be `public` either - passing a `RandomGenerator` to an untrusted caller is just as dangerous as passing `Random`, since the caller can inspect the result and conditionally abort. The linter warns about `public` functions with `RandomGenerator` parameters as well. ## Calling from a Transaction To call an entry function which expects `&Random`, pass the `Random` object at `0x8` as the argument - as mentioned above, the address is identical on every network. For example, with the Sui CLI: ```bash sui client ptb \ --move-call $PACKAGE_ID::randomness::mint_medal @0x8 ``` Due to the restrictions described in the previous section, the call must effectively be the last command in the transaction block - only `TransferObjects` and `MergeCoins` commands may follow it. ## Testing The pattern above pays off in tests. The `sui::random` module provides test-only functions to create generators without the `Random` object: `new_generator_for_testing` and `new_generator_from_seed_for_testing`. A seeded generator always produces the same sequence of values, which makes tests reproducible - and since different seeds produce different sequences, you can search for seeds that lead the test into a specific branch, covering every outcome deterministically. A non-seeded generator is useful for property-style checks that must hold for any outcome: ```move #[test] fun test_mint_medal() { let ctx = &mut tx_context::dummy(); // Generators created from the same seed return the same sequence of // values, making the test fully reproducible... let mut gen1 = random::new_generator_from_seed_for_testing("victory"); let mut gen2 = random::new_generator_from_seed_for_testing("victory"); assert_eq!(gen1.generate_u64(), gen2.generate_u64()); // ...and different seeds produce different values. Search for seeds // which lead the test into the branch you want to check: `"victory"` // rolls a 6 (Gold), and `"trophy"` rolls a 90 (Bronze). let mut gold_gen = random::new_generator_from_seed_for_testing("victory"); let gold_medal = mint_medal_impl(&mut gold_gen, ctx); assert_eq!(gold_medal.quality, GOLD); destroy(gold_medal); let mut bronze_gen = random::new_generator_from_seed_for_testing("trophy"); let bronze_medal = mint_medal_impl(&mut bronze_gen, ctx); assert_eq!(bronze_medal.quality, BRONZE); destroy(bronze_medal); // A non-seeded generator is useful for property-style tests: whatever // the outcome, the quality must be one of the three defined values. let mut generator = random::new_generator_for_testing(); 100u8.do!(|_| { let medal = mint_medal_impl(&mut generator, ctx); assert!(medal.quality <= BRONZE); destroy(medal); }); } ``` To test the entry function itself - the full flow, as a transaction would execute it - use [Test Scenario](./../testing/test-scenario) and create the shared `Random` object with `random::create_for_testing`. Note that the `Random` object can only be created and updated by the system address `0x0`: ```move #[test] fun test_mint_medal_via_entry() { let user = @0xA11CE; // The `Random` object can only be created and updated by the system, // so the scenario has to start as the system address `0x0`. let mut scenario = test_scenario::begin(@0x0); random::create_for_testing(scenario.ctx()); scenario.next_tx(@0x0); let mut random: Random = scenario.take_shared(); random.update_randomness_state_for_testing( 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", scenario.ctx(), ); test_scenario::return_shared(random); // Now the user calls the entry function, as a transaction would. scenario.next_tx(user); let random: Random = scenario.take_shared(); mint_medal(&random, scenario.ctx()); test_scenario::return_shared(random); // The `Medal` is now owned by the caller. scenario.next_tx(user); let medal: Medal = scenario.take_from_sender(); assert!(medal.quality <= BRONZE); scenario.return_to_sender(medal); scenario.end(); } ``` See [Creating and Using System Objects in Tests](./../testing/using-system-objects) for more details on testing with system objects. > Onchain randomness should not be confused with the [`#[random_test]`](./../testing/random-test) > attribute, which is a compiler feature for generating random test inputs. ## Limitations Onchain randomness is unpredictable _before_ the transaction executes, but it is not a secret: once the transaction is committed, the result is public, like everything else onchain. This makes it a great fit for fair selection - raffles, loot tables, matchmaking, shuffling - but not for hidden information. A card game where players hold concealed hands cannot be built on the `Random` object alone and requires additional cryptography. Other limitations follow from the security rules described above: - randomness is non-composable by design: the consuming function must be `entry` and effectively the last meaningful command in the transaction, so its result cannot be inspected or acted upon within that transaction - the outcome is delivered through effects, such as objects created or transferred by the function; - the outcome cannot be known in advance - a dry run of the transaction will not match the actual execution; - randomness is only available in transactions, there is no way to "peek" at the next value. ## Attacks and Mitigations Even with correct encapsulation, one class of attacks remains, and it is the responsibility of the application developer: _conditional failure_ attacks. The platform guarantees the attacker cannot choose the outcome, but if the transaction can be made to fail more often on a loss than on a win (or vice versa), the attacker still gains an edge - a failed transaction rolls everything back, granting a free retry. The main variation is the _gas-based_ attack. If the winning and losing branches consume different amounts of gas, an attacker can set the gas budget between the two costs: the cheaper branch completes, and the more expensive one fails with an out-of-gas error, undoing the unfavorable result. Similar tricks can rely on other limited resources, such as the number of new objects or dynamic field accesses in a transaction. To mitigate exposure: - keep the gas cost of all outcomes as close as possible - avoid expensive logic that only runs on one of the branches (in the `Medal` example, every outcome performs the same work); - if outcomes do require different processing, split the flow into two transactions: the first one draws the randomness and stores the raw result in an object, using the same cost for every outcome; the second one - a regular function which no longer touches `Random` - applies the consequences; - never expose `Random` or `RandomGenerator` through `public` functions, and always create a fresh generator inside the function that uses it. ## Further Reading - [Onchain Randomness](https://docs.sui.io/guides/developer/advanced/randomness-onchain) guide in Sui documentation. - [sui::random](https://docs.sui.io/references/framework/sui/random) module documentation. --- # Binary Canonical Serialization Binary Canonical Serialization (BCS) is a binary encoding format for structured data. It was originally designed in Diem, and became the standard serialization format for Move. BCS is simple, efficient, deterministic, and easy to implement in any programming language. While serialization may sound like an advanced topic, BCS is everywhere on Sui: arguments of a transaction are BCS-encoded, objects and events are stored as - and read offchain as - BCS bytes, and messages signed and verified in smart contracts are usually BCS-serialized structs. Most of the time the encoding is handled for you, but sooner or later an application needs to do it by hand: decode a signed payload, parse raw bytes passed as a `vector` argument, or produce bytes that match what an offchain client built. > The full format specification is available in the > [BCS repository](https://github.com/zefchain/bcs). ## Format BCS is a binary format that supports unsigned integers up to 256 bits, options, booleans, unit (empty value), fixed and variable-length sequences, and maps. The format is designed to be deterministic, meaning that the same data will always be serialized to the same bytes. > "BCS is not a self-describing format. As such, in order to deserialize a message, one must know > the message type and layout ahead of time" from the [README](https://github.com/zefchain/bcs) The core rules are: - integers are stored in little-endian byte order; - sequences (like [vectors](./../move-basics/vector)) are prefixed with their length, encoded as ULEB128 - a compact, variable-length integer encoding; - [enums](./../move-basics/enum-and-match) are stored as the index of the variant, followed by the fields of that variant; - maps are stored as ordered sequences of key-value pairs; - structs are treated as a sequence of fields: the fields are serialized one after another, in the order they are defined in the struct, with no names, types, or separators in between. To make this concrete, here is how a `User` value is laid out byte by byte: ```move /// A struct we will encode and decode in the examples below. public struct User has drop { age: u8, is_active: bool, name: String, } ``` | Field | Value | Encoded bytes | | ------------------------ | ----- | --------------------------------- | | `age: u8` | `42` | `2A` | | `is_active: bool` | `true`| `01` | | `name: String` | `"Bob"` | `03 42 6F 62` (length + bytes) | | `User` (all of the above)| | `2A 01 03 42 6F 62` | ## Using BCS Two modules implement BCS in Move: the [Standard Library](./../move-basics/standard-library) provides `std::bcs` with a single native encoding function `to_bytes`, and the [Sui Framework](./sui-framework) builds on top of it with the [`sui::bcs`][sui-bcs] module, which re-exports `to_bytes` and adds decoding functions implemented in Move. In Sui code, importing `sui::bcs` alone is enough for both encoding and decoding. ## Encoding To encode data, use the `bcs::to_bytes` function, which converts a data reference into a byte vector. This function supports encoding any type, including structs and enums. ```move module std::bcs; /// Return the binary representation of `v` in BCS (Binary Canonical /// Serialization) format. public native fun to_bytes(v: &MoveValue): vector; ``` The following example shows the encoding of primitive values: ```move use sui::bcs; // 0x01 - a single byte with value 1 (or 0 for false) let bool_bytes = bcs::to_bytes(&true); assert_eq!(bool_bytes, x"01"); // 0x2a - just a single byte let u8_bytes = bcs::to_bytes(&42u8); assert_eq!(u8_bytes, x"2A"); // 0x2a00000000000000 - 8 bytes, little-endian let u64_bytes = bcs::to_bytes(&42u64); assert_eq!(u64_bytes, x"2A00000000000000"); // address is a fixed sequence of 32 bytes // 0x0000000000000000000000000000000000000000000000000000000000000002 let addr = bcs::to_bytes(&@sui); assert_eq!(addr, x"0000000000000000000000000000000000000000000000000000000000000002"); ``` ### Encoding a Struct A struct is encoded as nothing more than its fields, one after another. The example below encodes the `User` value from the [Format](#format) section, checks the exact bytes from the table, and then demonstrates the "sequence of fields" rule directly - concatenating the individually encoded fields yields the same result: ```move let user = User { age: 42, is_active: true, name: "Bob", }; // A struct is encoded as its fields, one after another, in the // order they are declared: no names, no types, no separators. // // age | is_active | name // 2A | 01 | 03 42 6F 62 (length + "Bob") let user_bytes = bcs::to_bytes(&user); assert_eq!(user_bytes, x"2A0103426F62"); // Concatenating individually encoded fields gives the same bytes! let name: String = "Bob"; let mut field_bytes = vector[]; field_bytes.append(bcs::to_bytes(&42u8)); field_bytes.append(bcs::to_bytes(&true)); field_bytes.append(bcs::to_bytes(&name)); assert_eq!(user_bytes, field_bytes); ``` ## Decoding Because BCS is not a self-describing format, decoding requires prior knowledge of the data type. This is not just a formality - the same bytes are perfectly valid under different readings, and the decoder has no way to detect a mismatch. The 6 bytes of the encoded `User` above can just as well be read as a `u16` followed by a `vector`: ```move // The exact same 6 bytes that encoded the `User` above... let mut bcs = bcs::new(x"2A0103426F62"); // ...can be read as completely different types. The bytes carry // no type information - the reader decides what they mean. let num = bcs.peel_u16(); // 0x012A = 298 let vec = bcs.peel_vec_u8(); // [0x42, 0x6F, 0x62] assert_eq!(num, 298); assert_eq!(vec, vector[66, 111, 98]); ``` The [`sui::bcs`][sui-bcs] module provides functions to assist with decoding: `peel_bool`, `peel_u8` through `peel_u256`, and `peel_address` for primitive values, a `peel_vec_*` family and a `peel_option_*` family for common containers, and macros for everything else. If the decoder runs out of bytes - or the bytes do not form a valid value, such as a boolean byte other than `0` or `1` - the call aborts. ### Wrapper API The decoder is a wrapper around the bytes: the `bcs::new` function takes the bytes by value, and then the caller _peels off_ values one by one, front to back, by calling the `peel_*` functions. Whatever has not been decoded stays inside the wrapper, and can be taken back out with the `into_remainder_bytes` function. ```move use sui::bcs; // The decoder wraps the bytes; it must be declared as mutable, // because every `peel_*` call consumes a part of the input. let mut bcs = bcs::new(x"012A2823000000000000"); let bool_value = bcs.peel_bool(); assert_eq!(bool_value, true); let u8_value = bcs.peel_u8(); assert_eq!(u8_value, 42); // Whatever was not decoded can be taken back out of the wrapper. let remainder = bcs.into_remainder_bytes(); assert_eq!(remainder.length(), 8); ``` There is a common practice to use multiple variables in a single `let` statement during decoding. It makes code a little bit more readable and helps to avoid unnecessary copying of the data. ```move let mut bcs = bcs::new(x"012A2823000000000000"); // mind the order!!! // handy way to peel multiple values let (bool_value, u8_value, u64_value) = ( bcs.peel_bool(), bcs.peel_u8(), bcs.peel_u64(), ); assert_eq!(u64_value, 9000); ``` ### Decoding Vectors While most of the primitive types have a dedicated decoding function, vectors need special handling, which depends on the type of the elements. The underlying structure is always the same: first decode the length of the vector, then decode each element in a loop. ```move // vector[1u64, 2u64]: length prefix `02`, then the two elements let mut bcs = bcs::new(x"0201000000000000000200000000000000"); // first, peel the length of the vector... let mut len = bcs.peel_vec_length(); let mut vec = vector[]; // ...then peel each element in a loop while (len > 0) { vec.push_back(bcs.peel_u64()); // or any other type len = len - 1; }; assert_eq!(vec, vector[1, 2]); ``` For everyday use, the library offers the `peel_vec!` macro, which performs the loop internally and calls the given function once per element, as well as ready-made `peel_vec_*` functions for vectors of primitive types: ```move let mut bcs = bcs::new(x"0201000000000000000200000000000000"); // The `peel_vec!` macro does the same in a single call. let vec = bcs.peel_vec!(|bcs| bcs.peel_u64()); assert_eq!(vec, vector[1, 2]); // For vectors of primitive types, there are ready-made functions. let mut bcs = bcs::new(x"0201000000000000000200000000000000"); let vec = bcs.peel_vec_u64(); assert_eq!(vec, vector[1, 2]); ``` ### Decoding Option [Option](./../move-basics/option) is encoded as a single byte - `0` for _none_ and `1` for _some_ - followed by the value, if present. The `peel_option!` macro reads the byte and evaluates the given function only if the value is there; primitive types also have ready-made `peel_option_*` functions. ```move // `option::none()` is a single `00` byte... let mut bcs = bcs::new(x"00"); let none = bcs.peel_option!(|bcs| bcs.peel_u8()); assert!(none.is_none()); // ...and `option::some(42u8)` is `01` followed by the value. let mut bcs = bcs::new(x"012A"); let some = bcs.peel_option!(|bcs| bcs.peel_u8()); assert_eq!(some, option::some(42)); // For primitive types, there are ready-made `peel_option_*` functions. let mut bcs = bcs::new(x"012A"); let some = bcs.peel_option_u8(); assert_eq!(some, option::some(42)); ``` ### Decoding Structs There is no way to automatically decode bytes into a Move struct - the [struct](../move-basics/struct) can only be packed by its module, and the bytes carry no information about what they represent. To parse bytes into a struct, peel each field and pack the type. The example below makes the full round trip: it encodes a `User` value, decodes it back from the bytes, and checks that the result is identical to the original. ```move let user = User { age: 42, is_active: true, name: "Bob", }; // Encode the value... let mut bcs = bcs::new(bcs::to_bytes(&user)); // ...and decode it back, peeling the fields in exactly the order // they are declared in the struct definition. let decoded = User { age: bcs.peel_u8(), is_active: bcs.peel_bool(), name: bcs.peel_vec_u8().to_string(), }; assert_eq!(user, decoded); ``` > The bytes contain no field names and no type tags, so the only thing that makes decoding correct > is peeling the exact same types in the exact same order as they were encoded. Getting the order > wrong does not necessarily abort - it may silently produce wrong values, as the > [example above](#decoding) shows. ### Decoding Enums An [enum](./../move-basics/enum-and-match) value is encoded as the index of its variant, followed by the fields of that variant. Decoding mirrors this: the `peel_enum_tag` function reads the variant index, and a `match` expression on it decodes the corresponding fields: ```move let status = Status::Shipped { tracking: 12345 }; // An enum value is encoded as the variant index, followed by the // fields of that variant. let mut bcs = bcs::new(bcs::to_bytes(&status)); let decoded = match (bcs.peel_enum_tag()) { 0 => Status::Pending, 1 => Status::Shipped { tracking: bcs.peel_u64() }, _ => abort, }; assert_eq!(status, decoded); ``` ## Summary - BCS is the standard binary serialization format of Move: deterministic - the same value always produces the same bytes. - The format is not self-describing: the bytes carry no names or types, and the reader must know the layout ahead of time. - Structs and enums encode as their fields in declaration order; decoding must peel the same types in the same order. - Encoding is done with `bcs::to_bytes`; decoding with the `bcs::new` wrapper and the `peel_*` family of functions and macros, which abort on malformed or truncated input. ## Further Reading - [BCS specification](https://github.com/zefchain/bcs) - the full format description. - [std::bcs](https://docs.sui.io/references/framework/std/bcs) and [sui::bcs][sui-bcs] module documentation. [sui-bcs]: https://docs.sui.io/references/framework/sui/bcs --- # Testing Move is designed to be [secure by default](./../foreword.md) - its type system and built-in safeguards prevent entire classes of bugs that plague other smart contract languages, such as reentrancy, arithmetic overflow, and unauthorized access to assets. But language safety is not the same as program correctness. A type system can ensure your code won't violate Move's rules, but it cannot verify that your transfer logic sends funds to the right recipient, that your auction closes at the right time, or that your access control matches your intended policy. These are properties of your design, not the language - and they can only be verified through testing. The stakes of getting it wrong are uniquely high in onchain programming: - **Financial risk**: Bugs in asset-handling code can lead to permanent loss of funds. A single overlooked edge case in transfer logic or access control can be exploited, resulting in irreversible damage. - **Immutability**: Once deployed, smart contract code cannot always be easily modified, and even with the ability to upgrade, the previous version of the code will always be available. Thorough testing before deployment is your primary defense against vulnerabilities. - **Adversarial environment**: Published Move packages are effectively open-source - anyone can read and decompile onchain bytecode. This means malicious actors can study your code in detail, searching for exploitable flaws. Your code must handle not just expected inputs, but intentional attempts to break it. - **Composability risks**: Move modules interact with other onchain code. Testing must verify that your code behaves correctly not only in isolation but also when composed with other packages. Given these stakes, comprehensive testing is not optional - it is essential for any Move application that handles assets or implements any business logic. --- # Testing Basics The Move compiler has a built-in testing framework - tests are written in Move and live alongside your source code. You annotate functions with `#[test]`, and the compiler handles discovery and execution. The VM execution environment is the same as in production, so your code runs with identical semantics. However, network and storage features are simulated in tests and don't behave exactly as they do during actual onchain execution - something to keep in mind when testing interactions with objects, transactions, and other platform-specific functionality. ## What is a Test? A test is a function annotated with the `#[test]` attribute. Tests cannot take arguments and should not return a value. Test functions are automatically detected and executed when running the test command. If a test function [aborts](./../move-basics/assert-and-abort.md) unexpectedly, the test fails. ```move module book::my_module; #[test] fun test_addition() { assert!(2 + 2 == 4); } #[test] fun test_that_aborts() { abort // This test will FAIL - unexpected abort } #[test, expected_failure] fun test_expected_abort() { abort // This test will PASS - abort was expected } ``` ## Running Tests To run tests, use the `sui move test` command. The compiler builds the package in _test mode_ and runs all tests found in the package. ```bash sui move test ``` Example output: ``` Running Move unit tests [ PASS ] book::my_module::test_addition [ FAIL ] book::my_module::test_that_aborts [ PASS ] book::my_module::test_expected_abort Test result: FAILED. Total tests: 3; passed: 2; failed: 1 ``` ## Filtering Tests Run specific tests by providing a filter string. Only tests whose fully qualified name contains the filter will run: ```bash # Run tests containing "addition" in the name sui move test addition # Run all tests in a specific module sui move test my_module # Run a specific test sui move test book::my_module::test_addition ``` ## Expected Failures Use `#[expected_failure]` to test that code aborts under certain conditions. The test passes only if it aborts; if it completes normally, the test fails. ### Basic Expected Failure ```move #[test, expected_failure] fun test_division_by_zero() { let _ = 1 / 0; // Aborts - test passes } ``` ### Expected Abort Code Specify the expected abort code to ensure the function fails for the right reason: ```move module book::errors; const EInvalidInput: u64 = 1; const ENotFound: u64 = 2; public fun validate(x: u64) { assert!(x > 0, EInvalidInput); } #[test, expected_failure(abort_code = EInvalidInput)] fun test_validate_zero_fails() { validate(0); // Aborts with EInvalidInput - test passes } #[test, expected_failure(abort_code = ENotFound)] fun test_wrong_error_code() { validate(0); // Aborts with EInvalidInput, not ENotFound - test FAILS } ``` ### Abort Codes from Other Modules The `abort_code` argument can also reference a constant defined in another module - including the [Standard Library](./../move-basics/standard-library) and the [Sui Framework](./../programmability/sui-framework) - by spelling out its full path. Visibility does not matter here: the attribute can name a private constant of a dependency. This is the way to test a function that is expected to fail _inside_ a dependency: ```move /// The test aborts inside `sui::dynamic_field`, and the expected abort /// code is imported from that module by its full path. #[test, expected_failure(abort_code = sui::dynamic_field::EFieldDoesNotExist)] fun test_borrow_missing_field() { let ctx = &mut tx_context::dummy(); let id = object::new(ctx); // There is no field with this name, so `borrow` aborts. let _: &u64 = sui::dynamic_field::borrow(&id, b"missing"); id.delete(); } ``` ### Expected Location Specify where the abort should occur using `location`: ```move #[test, expected_failure(abort_code = EInvalidInput, location = book::errors)] fun test_abort_location() { validate(0); } // Use `location = Self` for aborts in the current module #[test, expected_failure(abort_code = ENotFound, location = Self)] fun test_abort_in_self() { abort ENotFound } ``` ## Test-Only Code Code marked with `#[test_only]` is compiled only in test mode. Use it for test utilities, helper functions, or imports that shouldn't exist in production code. It is common for `#[test_only]` functions to have `public` or `public(package)` visibility so they can be called from tests in other modules - since test-only code is stripped from production builds, this does not affect the public API of your package. > Note: a good rule of thumb is to add a `_for_testing` suffix to test-only functions and a `TEST_` > prefix to test-only constants. This helps distinguish them from production code and makes it > easier to find them in the codebase. Given that test-only functions often do things that > production code cannot, this is a good way to ensure that you are not accidentally using a > test-only function in production code. ### Test-Only Imports ```move #[test_only] use std::unit_test::assert_eq; #[test] fun test_with_assert_eq() { assert_eq!(2 + 2, 4); } ``` ### Test-Only Functions ```move #[test_only] fun setup_test_data(): vector { vector[1, 2, 3, 4, 5] } #[test] fun test_sum() { let data = setup_test_data(); let mut sum = 0; data.do!(|x| sum = sum + x); assert!(sum == 15); } ``` ### Test-Only Constants ```move #[test_only] const TEST_ADDRESS: address = @0xCAFE; ``` ### Test-Only Modules An entire module can be test-only: ```move #[test_only] module book::test_helpers; public fun create_test_scenario(): u64 { 42 } ``` ## Useful CLI Options | Option | Description | | ---------------------- | --------------------------------------------------------------------------------------- | | `` | Run only tests matching the filter (positional argument) | | `--coverage` | Collect coverage information (see [Coverage](./coverage.md)) | | `--trace` | Generate traces for coverage LCOV output | | `--statistics` | Show execution statistics including gas usage (see [Gas Profiling](./gas-profiling.md)) | | `--threads ` | Number of threads for parallel test execution | | `--rand-num-iters ` | Number of iterations for [random tests](./random-test.md) | | `--seed ` | Seed for reproducible random test runs | ## Test Output When a test fails, the output shows: - The test name and FAIL status - The abort code (if any) - The location where the failure occurred - A stack trace for debugging ```table ┌── test_that_failed ────── │ error[E11001]: test failure │ ┌─ ./sources/module.move:15:9 │ │ │ 15 │ assert!(balance == 100); │ │ ^^^^^^^^^^^^^^^^^^^^^^^ Test was not expected to error, but it │ │ aborted with code 1 originating in the module 0x0::module │ └────────────────── ``` ## Next Steps In the next sections you will learn how to write good tests, how to use test utilities, how to test transactions and how to master the testing framework. --- # What Makes a Good Test Writing tests is one thing; writing _good_ tests is another. A test suite that merely exists provides false confidence if it doesn't actually catch bugs or help you understand your code. This section covers the principles and practices that distinguish effective tests from superficial ones. ## Characteristics of Good Tests ### 1. Tests Should Be Concise Each test should be concise and to the point. Avoid writing tests that are too long and complex. Keep tests short and focused on a single behavior or scenario. ### 2. Tests Should Be Readable Tests serve as documentation for your code's expected behavior. Anyone reading a test should quickly understand what scenario is being tested and what the expected outcome is. > **Note:** one of the guaranteed ways to make long function calls more readable is to use the > [Builder Pattern](./builder-pattern.md) which is covered later in this chapter. ```move module book::readable_tests; public struct Balance has copy, drop { value: u64 } public fun new(value: u64): Balance { Balance { value } } public fun add(balance: &mut Balance, amount: u64) { balance.value = balance.value + amount; } public fun value(balance: &Balance): u64 { balance.value } #[test_only] use std::unit_test::assert_eq; #[test] fun test_add_increases_balance_by_specified_amount() { // Arrange: set up initial state let mut balance = new(100); // Act: perform the operation being tested balance.add(50); // Assert: verify the expected outcome assert_eq!(balance.value(), 150); } ``` ### 3. Tests Should Test One Thing Each test should verify a single behavior or scenario. When a test fails, you should immediately know what went wrong. Tests that verify multiple unrelated behaviors make debugging harder. ```move module book::single_responsibility; public struct Counter has copy, drop { value: u64 } public fun increment(c: &mut Counter) { c.value = c.value + 1; } public fun decrement(c: &mut Counter) { c.value = c.value - 1; } #[test_only] use std::unit_test::assert_eq; // Good: separate tests for each behavior #[test] fun test_increment_adds_one() { let mut counter = Counter { value: 0 }; counter.increment(); assert_eq!(counter.value, 1); } #[test] fun test_decrement_subtracts_one() { let mut counter = Counter { value: 1 }; counter.decrement(); assert_eq!(counter.value, 0); } ``` ## What to Test ### Test the Contract, Not the Implementation Focus on testing the observable behavior of your functions - what they return and what side effects they produce - rather than how they achieve it internally. This allows you to refactor implementations without breaking tests. ### Test Edge Cases Edge cases are where bugs often hide. For numeric operations, consider: - Zero values - Maximum values (`std::u64::max_value!()`, `std::u128::max_value!()`) - Boundary conditions (off-by-one errors) - Empty collections ```move module book::edge_cases; public fun safe_divide(a: u64, b: u64): u64 { if (b == 0) return 0; a / b } #[test_only] use std::unit_test::assert_eq; #[test] fun test_divide_normal_case() { assert_eq!(safe_divide(10, 2), 5); } #[test] fun test_divide_by_zero_returns_zero() { assert_eq!(safe_divide(10, 0), 0); } #[test] fun test_divide_zero_by_nonzero() { assert_eq!(safe_divide(0, 5), 0); } ``` ### Test Error Conditions Verify that your code fails appropriately when given invalid inputs. Use `#[expected_failure]` to test that functions abort with the correct error codes. Use explicit error constants in the expectations and do not use magic numbers. ```move module book::error_conditions; const EInsufficientBalance: u64 = 1; public struct Wallet has copy, drop { balance: u64 } public fun withdraw(wallet: &mut Wallet, amount: u64) { assert!(wallet.balance >= amount, EInsufficientBalance); wallet.balance = wallet.balance - amount; } #[test_only] use std::unit_test::assert_eq; #[test] fun test_withdraw_succeeds_with_sufficient_balance() { let mut wallet = Wallet { balance: 100 }; wallet.withdraw(50); assert_eq!(wallet.balance, 50); } #[test, expected_failure(abort_code = EInsufficientBalance)] fun test_withdraw_fails_with_insufficient_balance() { let mut wallet = Wallet { balance: 50 }; wallet.withdraw(100); } ``` ### Aim for Good Coverage, but Don't Chase Numbers High test coverage is a positive indicator - it means more of your code is exercised during testing, increasing the chance of catching bugs. Reaching good coverage demonstrates that you've thought through various code paths and scenarios. However, coverage should not be the primary goal of writing tests. A test suite with 100% coverage can still miss critical bugs if the tests don't verify meaningful behavior. Tests that exist solely to increase coverage metrics - without asserting anything useful - provide false confidence. Write tests to verify behavior and catch bugs. Good coverage should be a natural outcome of thorough testing, not an end in itself. For more information on measuring and interpreting coverage, see [Coverage Reports](./coverage.md). ## Test Organization ### Use Descriptive Names Test names should describe the scenario being tested and the expected outcome. A good naming convention is `test___` or simply a description of the behavior. Whatever naming convention you use, it should be consistent and easy to understand. ### Group Related Tests Organize tests logically, either by the function they test or by the feature they verify. In Move, you can place tests in the same module as the code they test, or in separate test modules. It is very common to create a testing module `*_tests.move` in the `tests/` directory for each module in the `sources/` directory. ## The Testing Pyramid A well-balanced test suite typically follows the testing pyramid: 1. **Unit tests** (base): Many small, fast tests that verify individual functions in isolation 2. **Integration tests** (middle): Fewer tests that verify how components work together 3. **End-to-end tests** (top): Few tests that verify complete user scenarios Currently, in Move all tests are implemented as unit tests, but by using [Test Scenario](./test-scenario.md) you can test multiple transactions and user actions in a single test. ## Common Testing Mistakes ### Testing Only the Happy Path Don't just test that code works when everything goes right. Test what happens when things go wrong - invalid inputs, edge cases, and error conditions. ### Over-Mocking While isolation is important, over-mocking can lead to tests that pass even when the real integration would fail. Balance unit tests with integration tests that use real components. ### Ignoring Test Maintenance Tests are code too. Keep them clean, remove obsolete tests, and update them when requirements change. A neglected test suite becomes a liability rather than an asset. --- # Unit Test Utilities In addition to the built-in `assert!` macro, the [Standard Library](./../move-basics/standard-library.md) provides common utilities for testing. The most important ones are defined in the [`std::unit_test`][stdlib-unit-test] module. While not a requirement, it is recommended to use this module in tests. ## `assert!` The `assert!` macro is a built-in language feature and the most basic tool for verifying conditions in tests. It takes a boolean expression and aborts if the expression evaluates to `false`. For a detailed explanation of assertions and error handling, see [Aborting Execution](./../move-basics/assert-and-abort.md). ```move #[test] fun test_addition() { let sum = 2 + 2; assert!(sum == 4); } ``` In published code, `assert!` should normally have an abort code as the second argument to help identify failures. However, in tests, the abort code is not necessary and doesn't provide any value. ```move // In published code - abort code recommended assert!(balance >= amount, EInsufficientBalance); // In test code - abort code unnecessary assert!(balance >= amount); ``` ## `assert_eq!` and `assert_ref_eq!` While `assert!` works, it has a limitation: when it fails, it only shows that the condition was false, not giving any insight into the actual values that caused the failure. Consider this test: ```move #[test] fun test_balance_update() { let balance = calculate_balance(); assert!(balance == 1000); // does not print compared values on failure } ``` If this test fails, you only know that the assertion failed - not what `balance` actually was. You would need to add debug statements or investigate further to understand the failure. The `assert_eq!` macro from `std::unit_test` solves this by printing both values when the assertion fails: ```move #[test_only] use std::unit_test::assert_eq; #[test] fun test_balance_update() { let balance = calculate_balance(); assert_eq!(balance, 1000); // on failure, prints: "Assertion failed:", 750, "!=", 1000 } ``` Now the error message shows the actual value (`750`) and the expected value (`1000`), making it immediately clear what went wrong. This debug output works because `assert_eq!` calls the [`std::debug::print`](./../move-basics/standard-library.md) function, which prints the values if the assertion fails. To compare by reference, use `assert_ref_eq!` instead of `assert_eq!`: ```move #[test_only] use std::unit_test::assert_ref_eq; #[test] fun test_reference_equality() { let user = get_user(); let expected = create_expected_user(); assert_ref_eq!(&user, &expected); } ``` ## Black Hole Function: `destroy` The `destroy` function consumes any value, regardless of its abilities. This is essential for testing types that don't have the `drop` ability - without it, cleanup would require additional logic implemented for each type. ```move module std::unit_test; /// Black hole function to destroy any value in `test` mode. public native fun destroy(v: T); ``` Consider a type without `drop`: ```move module book::ticket; /// A ticket is an Object - doesn't have `drop`. public struct Ticket has key, store { id: UID, event_id: u64, seat: u64, } public fun new(event_id: u64, seat: u64, ctx: &mut TxContext): Ticket { Ticket { id: object::new(ctx), event_id, seat } } ``` In published code, `Ticket` type may not have a deletion function or require a condition to be met before deletion. In this case, `destroy` is the best way to deal with the value: ```move #[test_only] use std::unit_test; #[test] fun test_ticket_creation() { let ctx = &mut tx_context::dummy(); let ticket = ticket::new(1, 42, ctx); // Test passes - but how do we get rid of `ticket`? unit_test::destroy(ticket); // Consumes the ticket } ``` The `destroy` function acts as a "black hole" - it accepts any type and makes it disappear. This lets you focus tests on specific functionality without being forced to handle cleanup logic that isn't relevant to what you're testing. > The `destroy` function is only available in test code. It cannot be used in production modules. In the next sections, we will cover Sui-specific testing utilities and features. [stdlib-unit-test]: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/move-stdlib/sources/unit_test.move --- # Simulating Transaction Context Most Move functions that create objects or interface with the user have a `TxContext` argument. When a transaction is executed, its value is provided by the runtime, but in tests you need to create and pass it yourself. The `sui::tx_context` module provides several utility functions for this purpose. > **Note:** The utilities in this chapter are suitable for simple unit tests only. They do not > provide access to shared or transferred objects from storage. For tests that require taking > objects from storage or simulating multi-transaction scenarios, use > [Test Scenario](./test-scenario.md). ## Creating a Dummy Context The simplest way to get a `TxContext` is `tx_context::dummy()`. It creates a context with default values - zero address sender, epoch 0, and a fixed transaction hash: ```move use std::unit_test::assert_eq; #[test] fun test_create_object() { let ctx = &mut tx_context::dummy(); let obj = my_module::new(ctx); assert_eq!(ctx.sender(), @0); // sender is 0x0 by default // ... } ``` This is sufficient for most tests where you don't care about specific context values, and when you need to test the creation of objects, rather than the interaction with the storage. ## Custom Context with `new` When you need specific values for sender, epoch, or timestamp, use `tx_context::new`: ```move use std::unit_test::assert_eq; #[test] fun test_with_specific_sender() { let sender = @0xA; let tx_hash = x"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"; let epoch = 5; let epoch_timestamp_ms = 1234567890000; let ids_created = 0; let ctx = &mut tx_context::new( sender, tx_hash, epoch, epoch_timestamp_ms, ids_created, ); assert_eq!(ctx.sender(), @0xA); assert_eq!(ctx.epoch(), 5); } ``` The `tx_hash` must be exactly 32 bytes. For convenience, use `new_from_hint` to generate a unique hash from a simple integer: ```move #[test] fun test_with_hint() { let ctx = &mut tx_context::new_from_hint( @0xA, // sender 42, // hint (used to generate unique tx_hash) 5, // epoch 1000, // epoch_timestamp_ms 0, // ids_created ); // ... } ``` ## Tracking Created Objects When testing object creation, you may want to verify how many objects were created or get the address of the last created object: ```move use std::unit_test::{assert_eq, destroy}; #[test] fun test_object_creation_count() { let ctx = &mut tx_context::dummy(); assert_eq!(ctx.ids_created(), 0); let obj1 = my_module::new(ctx); assert_eq!(ctx.ids_created(), 1); let obj2 = my_module::new(ctx); assert_eq!(ctx.ids_created(), 2); // Get the address of the most recently created object (derived from its ID) let last_id = ctx.last_created_object_id(); assert_eq!(last_id, object::id(&obj2).to_address()); // Objects don't have `drop`, so they need to be cleaned up destroy(obj1); destroy(obj2); } ``` ## Simulating Time and Epochs For tests that depend on time or epoch changes, use the increment functions: ```move use std::unit_test::assert_eq; #[test] fun test_time_dependent_logic() { let ctx = &mut tx_context::dummy(); // Initial state assert_eq!(ctx.epoch(), 0); assert_eq!(ctx.epoch_timestamp_ms(), 0); // Simulate epoch change ctx.increment_epoch_number(); assert_eq!(ctx.epoch(), 1); // Simulate time passing (add 1 day in milliseconds) ctx.increment_epoch_timestamp(24 * 60 * 60 * 1000); assert_eq!(ctx.epoch_timestamp_ms(), 86_400_000); } ``` ## Full Control with `create` For complete control over all context fields including gas-related values, use `tx_context::create`: ```move use std::unit_test::assert_eq; #[test] fun test_with_full_context() { let ctx = &tx_context::create( @0xA, // sender tx_context::dummy_tx_hash_with_hint(1), // tx_hash 10, // epoch 1700000000000, // epoch_timestamp_ms 0, // ids_created 1000, // reference_gas_price 1500, // gas_price 10_000_000, // gas_budget option::none(), // sponsor (None = no sponsor) ); assert_eq!(ctx.gas_budget(), 10_000_000); } ``` ## Summary | Function | Use Case | | ----------------------------- | --------------------------------------------- | | `dummy()` | Quick context for simple tests | | `new()` | Custom sender, epoch, or timestamp | | `new_from_hint()` | Like `new` but generates tx_hash from integer | | `create()` | Full control including gas parameters | | `ids_created()` | Check number of objects created | | `last_created_object_id()` | Get address of the most recent object | | `increment_epoch_number()` | Simulate epoch progression | | `increment_epoch_timestamp()` | Simulate time passing | ## Further Reading - [Transaction Context](./../programmability/transaction-context.md) - detailed overview of `TxContext` and its role in transactions --- # Test Scenario The `test_scenario` module from the [Sui Framework](./../programmability/sui-framework.md) provides a way to simulate multi-transaction scenarios in tests. It maintains a view of the global object pool and allows you to test how objects are created, transferred, and accessed across multiple transactions. ```move #[test_only] use sui::test_scenario; ``` ## Starting and Ending a Scenario A test scenario begins with `test_scenario::begin` which takes the sender address as an argument. The scenario must be ended with `test_scenario::end` to clean up resources. Failing to end a scenario will result in a compilation error. > **Note:** there should be only one scenario per test. Creating multiple scenarios in the same test > may produce unexpected results and should be avoided. ```move use sui::test_scenario; #[test] fun test_basic_scenario() { let alice = @0xA; // Start a scenario with alice as the sender let mut scenario = test_scenario::begin(alice); // ... perform operations ... // End the scenario - returns TransactionEffects scenario.end(); } ``` ## Transaction Simulation Use `next_tx` to advance to a new transaction with a specified sender. Objects transferred in the previous transaction become available in the next one. Each `next_tx` call returns [`TransactionEffects`](#reading-transaction-effects) containing information about what happened in the previous transaction. ```move use sui::test_scenario; #[test] fun test_multi_transaction() { let alice = @0xA; let bob = @0xB; let mut scenario = test_scenario::begin(alice); // First transaction: alice creates an object // Objects created here are not yet in anyone's inventory // Advance to second transaction with bob as sender // Objects from the first transaction are now available let _effects = scenario.next_tx(bob); // ... bob can now access objects transferred to him ... scenario.end(); } ``` > Important: Objects transferred during a transaction are only available after calling `next_tx`. > You cannot access an object in the same transaction where it was transferred. ## Accessing Owned Objects [Owned objects](./../object/ownership.md#account-owner-or-single-owner) transferred to an address can be accessed using `take_from_sender` or `take_from_address`. The object then can be passed to a function, returned with `return_to_sender` or `return_to_address`, or transferred elsewhere using `public_transfer` (if the object has `store` ability). ```move module book::test_scenario_example; public struct Item has key, store { id: UID, value: u64, } public fun create(value: u64, ctx: &mut TxContext): Item { Item { id: object::new(ctx), value } } public fun value(item: &Item): u64 { item.value } #[test] fun test_take_and_return() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // Transaction 1: Create and transfer an item to alice { let item = create(100, scenario.ctx()); transfer::public_transfer(item, alice); }; // Transaction 2: Alice takes the item scenario.next_tx(alice); { // Take the most recent Item from sender's inventory let item = scenario.take_from_sender(); assert_eq!(item.value(), 100); // Return the item to sender's inventory scenario.return_to_sender(item); }; scenario.end(); } ``` ### Taking by ID When multiple objects of the same type exist, use `take_from_sender_by_id` or `take_from_address_by_id` to take a specific one: ```move #[test] fun test_take_by_id() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // Create two items let item1 = create(100, scenario.ctx()); let item2 = create(200, scenario.ctx()); let id1 = object::id(&item1); transfer::public_transfer(item1, alice); transfer::public_transfer(item2, alice); scenario.next_tx(alice); { // Take the specific item by ID let item = scenario.take_from_sender_by_id(id1); assert_eq!(item.value(), 100); scenario.return_to_sender(item); }; scenario.end(); } ``` ### Checking Object Availability Before taking an object, you can check if one exists: ```move #[test] fun test_has_object() { use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // No items exist yet assert!(!scenario.has_most_recent_for_sender()); let item = create(100, scenario.ctx()); transfer::public_transfer(item, alice); scenario.next_tx(alice); // Now an item exists assert!(scenario.has_most_recent_for_sender()); scenario.end(); } ``` ## Accessing Shared Objects [Shared objects](./../object/ownership.md#shared-state) are accessed using `take_shared` and must be returned with `return_shared`: ```move module book::shared_counter; public struct Counter has key { id: UID, value: u64, } public fun create(ctx: &mut TxContext) { transfer::share_object(Counter { id: object::new(ctx), value: 0, }) } public fun increment(counter: &mut Counter) { counter.value = counter.value + 1; } public fun value(counter: &Counter): u64 { counter.value } #[test] fun test_shared_object() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let bob = @0xB; let mut scenario = test_scenario::begin(alice); // Alice creates a shared counter create(scenario.ctx()); // Bob increments it scenario.next_tx(bob); { let mut counter = scenario.take_shared(); counter.increment(); assert_eq!(counter.value(), 1); test_scenario::return_shared(counter); }; // Alice increments it again scenario.next_tx(alice); { let mut counter = scenario.take_shared(); counter.increment(); assert_eq!(counter.value(), 2); test_scenario::return_shared(counter); }; scenario.end(); } ``` ### The `with_shared` Macro For cleaner code, use the `with_shared!` macro which handles take and return automatically: ```move #[test] fun test_with_shared_macro() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); create(scenario.ctx()); scenario.next_tx(alice); scenario.with_shared!(|counter, _scenario| { counter.increment(); assert_eq!(counter.value(), 1); }); scenario.end(); } ``` ## Accessing Immutable Objects [Immutable (frozen) objects](./../object/ownership.md#immutable-frozen-state) are accessed with `take_immutable` and returned with `return_immutable`: ```move module book::immutable_config; public struct Config has key { id: UID, max_value: u64, } public fun create(max_value: u64, ctx: &mut TxContext) { transfer::freeze_object(Config { id: object::new(ctx), max_value, }) } public fun max_value(config: &Config): u64 { config.max_value } #[test] fun test_immutable_object() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // Create an immutable config create(1000, scenario.ctx()); scenario.next_tx(alice); { // Take the immutable object let config = scenario.take_immutable(); assert_eq!(config.max_value(), 1000); // Return it to the global inventory test_scenario::return_immutable(config); }; scenario.end(); } ``` ## Accessing Transaction Context The `ctx` method provides access to the [`TxContext`](./../programmability/transaction-context.md) for the current transaction. Use it when calling functions that require a context: ```move #[test] fun test_context_access() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // Access the transaction context let ctx = scenario.ctx(); // Use it for operations that need context let item = create(100, ctx); transfer::public_transfer(item, alice); // The sender matches what we passed to begin() assert_eq!(ctx.sender(), alice); scenario.end(); } ``` ## Reading Transaction Effects Both `next_tx` and `end` return `TransactionEffects` which contains information about what happened during the transaction: ```move #[test] fun test_transaction_effects() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let bob = @0xB; let mut scenario = test_scenario::begin(alice); // Create objects in first transaction let item1 = create(100, scenario.ctx()); let item2 = create(200, scenario.ctx()); transfer::public_transfer(item1, alice); transfer::public_transfer(item2, bob); // Get effects from the first transaction let effects = scenario.next_tx(alice); // Check what was created assert_eq!(effects.created().length(), 2); // Check transfers to accounts assert_eq!(effects.transferred_to_account().size(), 2); // Check number of events emitted assert_eq!(effects.num_user_events(), 0); scenario.end(); } ``` ### Available Effect Fields | Method | Returns | Description | | -------------------------- | --------------------- | ------------------------------------ | | `created()` | `vector` | Objects created in this transaction | | `written()` | `vector` | Objects modified in this transaction | | `deleted()` | `vector` | Objects deleted in this transaction | | `transferred_to_account()` | `VecMap` | Objects transferred to addresses | | `transferred_to_object()` | `VecMap` | Objects transferred to other objects | | `shared()` | `vector` | Objects shared in this transaction | | `frozen()` | `vector` | Objects frozen in this transaction | | `num_user_events()` | `u64` | Number of events emitted | ## System Objects Use `create_system_objects` to make system objects like `Clock`, `Random`, and `DenyList` available in tests. For more detailed coverage of testing with system objects, see [Using System Objects](./using-system-objects.md). ```move use sui::clock::Clock; #[test] fun test_with_clock() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // Create system objects (Clock, Random, DenyList) // This call advances the transaction, so the objects are immediately available scenario.create_system_objects(); { // Now Clock is available as a shared object let clock = scenario.take_shared(); assert_eq!(clock.timestamp_ms(), 0); test_scenario::return_shared(clock); }; scenario.end(); } ``` ## Epoch and Time Manipulation Test [time-dependent logic](./../programmability/epoch-and-time.md) using `next_epoch` and `later_epoch`: ```move #[test] fun test_epoch_advancement() { use std::unit_test::assert_eq; use sui::test_scenario; let alice = @0xA; let mut scenario = test_scenario::begin(alice); // Check initial epoch assert_eq!(scenario.ctx().epoch(), 0); // Advance to next epoch scenario.next_epoch(alice); assert_eq!(scenario.ctx().epoch(), 1); // Advance epoch and time together (1000ms = 1 second) scenario.later_epoch(1000, alice); assert_eq!(scenario.ctx().epoch(), 2); assert_eq!(scenario.ctx().epoch_timestamp_ms(), 1000); scenario.end(); } ``` ## Complete Example Here's a complete example testing a simple token transfer flow: ```move module book::simple_token; public struct Token has key, store { id: UID, amount: u64, } public fun mint(amount: u64, ctx: &mut TxContext): Token { Token { id: object::new(ctx), amount } } public fun amount(token: &Token): u64 { token.amount } #[test] fun test_token_transfer_flow() { use std::unit_test::assert_eq; use sui::test_scenario; let admin = @0xAD; let alice = @0xA; let bob = @0xB; // Start scenario as admin let mut scenario = test_scenario::begin(admin); // Admin mints tokens for alice { let token = mint(1000, scenario.ctx()); transfer::public_transfer(token, alice); }; // Alice receives and transfers to bob scenario.next_tx(alice); { assert!(scenario.has_most_recent_for_sender()); let token = scenario.take_from_sender(); assert_eq!(token.amount(), 1000); transfer::public_transfer(token, bob); }; // Bob receives the token scenario.next_tx(bob); { let token = scenario.take_from_sender(); assert_eq!(token.amount(), 1000); scenario.return_to_sender(token); }; // Verify final state via effects - `return_to_sender` is recorded as a // transfer back to bob in the effects of the final transaction let effects = scenario.end(); assert_eq!(effects.transferred_to_account().size(), 1); } ``` ## Summary | Function | Purpose | | --------------------------- | -------------------------------------- | | `begin(sender)` | Start a new scenario | | `end(scenario)` | End the scenario and get final effects | | `next_tx(scenario, sender)` | Advance to next transaction | | `ctx(scenario)` | Get mutable reference to `TxContext` | | `take_from_sender` | Take owned object from sender | | `return_to_sender(obj)` | Return object to sender | | `take_shared` | Take shared object | | `return_shared(obj)` | Return shared object | | `take_immutable` | Take immutable object | | `return_immutable(obj)` | Return immutable object | | `create_system_objects` | Create Clock, Random, DenyList | | `next_epoch` | Advance to next epoch | | `later_epoch(ms, sender)` | Advance epoch and time | ## Further Reading - [Using System Objects](./using-system-objects.md) - Creating and manipulating Clock, Random, DenyList, Coin, and Balance in tests - [Test Utilities](./test-utilities.md) - `assert_eq!`, `destroy`, and other testing helpers - [Transaction Context](./../programmability/transaction-context.md) - Understanding `TxContext` and its fields - [Object Ownership](./../object/ownership.md) - How owned, shared, and immutable objects work - [Epoch and Time](./../programmability/epoch-and-time.md) - Working with time in Sui --- # Creating and Using System Objects in Tests Some tests require system objects like `Clock`, `Random`, or `DenyList`. These objects have [fixed addresses](./../appendix/reserved-addresses.md) on the network and are created during genesis. In tests, they don't exist by default, so the Sui Framework provides `#[test_only]` functions to create and manipulate them. ## Clock The [`Clock`](./../programmability/epoch-and-time.md#time) provides the current network timestamp. Use `clock::create_for_testing` to create one, and manipulate time with test-only functions: ```move use std::unit_test::assert_eq; use sui::clock; #[test] fun test_clock() { let ctx = &mut tx_context::dummy(); let mut clock = clock::create_for_testing(ctx); // Starts at 0 assert_eq!(clock.timestamp_ms(), 0); // Add time (in milliseconds) clock.increment_for_testing(1000); assert_eq!(clock.timestamp_ms(), 1000); // Set absolute time (must be >= current) clock.set_for_testing(5000); assert_eq!(clock.timestamp_ms(), 5000); // Clean up - Clock doesn't have `drop` clock.destroy_for_testing(); } ``` To share a `Clock` for use in a test scenario, call `share_for_testing`: ```move #[test] fun test_shared_clock() { let ctx = &mut tx_context::dummy(); let clock = clock::create_for_testing(ctx); clock.share_for_testing(); } ``` ## Random The `Random` object provides onchain randomness. In tests, the full `Random` shared object can only be created inside a [test scenario](./test-scenario.md) via `random::create_for_testing`. However, the preferred approach is to structure your code so that the core logic takes a `RandomGenerator` parameter - this lets you create a generator directly in unit tests with `random::new_generator_for_testing()`, bypassing the `Random` object entirely. This is easier to work with because `Random` requires an `entry` function (which cannot return non-droppable values), making it harder to assert on results. ```move use sui::random::{Self, Random, RandomGenerator}; // To use Random, a function must have `entry` modifier, hence it cannot return // a value, and not so easy to test. entry fun my_entry_function(r: &Random, ctx: &mut TxContext) { let mut gen = random::new_generator(r, ctx); let result = inner_function(&mut gen); result.destroy_or!(abort); } // Example of an inner function that is easier to test than the entry point. public(package) fun inner_function(gen: &mut RandomGenerator): Option { if (gen.generate_bool()) { option::some(gen.generate_u64()) } else { option::none() } } #[test] fun test_simple_random() { // Non-deterministic seed, useful for fuzzing. The result differs between // runs, so don't assert a specific outcome. let mut gen = random::new_generator_for_testing(); let _result = inner_function(&mut gen); // Deterministic (reproducible with same seed) let seed: vector = "Arbitrary seed bytes"; let mut gen = random::new_generator_from_seed_for_testing(seed); assert!(inner_function(&mut gen).is_none()); // A different seed gives a different - but still reproducible - result let mut gen = random::new_generator_from_seed_for_testing("move book"); assert!(inner_function(&mut gen).is_some()); } ``` For entry points that take the full `Random` shared object (the only possible way is to take it as a reference `&Random`), use a [test scenario](./test-scenario.md): ```move use sui::random::{Self, Random}; use sui::test_scenario; #[test] fun test_random_shared() { let mut scenario = test_scenario::begin(@0x0); // Create and share Random random::create_for_testing(scenario.ctx()); scenario.next_tx(@0x0); let mut random = scenario.take_shared(); // Initialize with 32 bytes of randomness (required before use) random.update_randomness_state_for_testing( 0, x"2020202020202020202020202020202020202020202020202020202020202020", scenario.ctx(), ); my_entry_function(&random, scenario.ctx()); test_scenario::return_shared(random); scenario.end(); } ``` ## DenyList The `DenyList` is used by regulated coins to block specific addresses. Create a local instance with `new_for_testing`, or a shared one with `create_for_testing`: ```move use sui::deny_list; use sui::test_scenario; use std::unit_test::destroy; #[test] fun test_deny_list() { let mut scenario = test_scenario::begin(@0x0); // Create a local instance for simple tests let deny_list = deny_list::new_for_testing(scenario.ctx()); // ... use deny_list destroy(deny_list); // Or create a shared DenyList deny_list::create_for_testing(scenario.ctx()); scenario.next_tx(@0x0); // ... take_shared and use scenario.end(); } ``` ## Coin and Balance For testing with coins, use `coin::mint_for_testing` and `balance::create_for_testing`: ```move use std::unit_test::assert_eq; use sui::coin; use sui::balance; use sui::sui::SUI; #[test] fun test_coins() { let ctx = &mut tx_context::dummy(); // Create a coin of any type let coin = coin::mint_for_testing(1000, ctx); assert_eq!(coin.value(), 1000); // Destroy and get the value back let value = coin.burn_for_testing(); assert_eq!(value, 1000); // Create a balance directly let balance = balance::create_for_testing(500); let value = balance.destroy_for_testing(); assert_eq!(value, 500); } ``` ## Create All System Objects at Once When using [Test Scenario](./test-scenario.md), you can create all system objects at once with `create_system_objects`. This creates and shares `Clock`, `Random`, and `DenyList`: ```move use sui::clock::Clock; use sui::random::Random; use sui::deny_list::DenyList; use sui::test_scenario; #[test] fun test_with_all_system_objects() { let mut scenario = test_scenario::begin(@0xA); // Creates Clock, Random, and DenyList as shared objects // (advances the transaction, so they are immediately available) scenario.create_system_objects(); // Take objects by type let clock = scenario.take_shared(); let random = scenario.take_shared(); let deny_list = scenario.take_shared(); // ... use the objects // Return them when done test_scenario::return_shared(clock); test_scenario::return_shared(random); test_scenario::return_shared(deny_list); scenario.end(); } ``` > System objects created in tests won't have the same fixed addresses they have on a live network. > Use `take_shared()` to access them by type rather than by ID. To take a specific shared object by ID, use `take_shared_by_id`: ```move use sui::test_scenario::{Self, most_recent_id_shared}; #[test] fun test_take_by_id() { let mut scenario = test_scenario::begin(@0xA); scenario.create_system_objects(); // Get the ID of the most recent shared Clock let clock_id = most_recent_id_shared().destroy_some(); // Take by ID let clock = scenario.take_shared_by_id(clock_id); // ... test_scenario::return_shared(clock); scenario.end(); } ``` ## Summary | Object | Creation | Test-only Features | | ------------------ | --------------------------------------- | ------------------------------------------ | | `Clock` | `clock::create_for_testing(ctx)` | `increment_for_testing`, `set_for_testing` | | `Random` | `random::create_for_testing(ctx)` | `update_randomness_state_for_testing` | | `RandomGenerator` | `random::new_generator_for_testing()` | `new_generator_from_seed_for_testing` | | `DenyList` | `deny_list::create_for_testing(ctx)` | `new_for_testing` | | `Coin` | `coin::mint_for_testing(value, ctx)` | `burn_for_testing` | | `Balance` | `balance::create_for_testing(value)` | `destroy_for_testing` | | All system objects | `scenario.create_system_objects()` | Creates Clock, Random, DenyList | --- # Pattern: Builder The builder pattern is used to construct complex objects with many parameters in a flexible and readable way. Instead of requiring all parameters upfront, a builder accumulates configuration through method calls and produces the final object when `build()` is called. This pattern is especially useful in testing, where you often need to create objects with slight variations while keeping most fields at sensible defaults. > In published code, builder pattern may introduce additional gas costs due to intermediate structs > and multiple function calls. This pattern is best suited for tests where gas considerations are > not a concern, and readability and maintainability are required. ## Defining a Builder A builder struct mirrors the target object's fields but wraps them in `Option` types. This allows each field to remain unset until explicitly configured. A typical builder provides: - A `new()` function that creates an empty builder - Setter methods that configure individual fields and return the builder for chaining - A `build()` function that constructs the final object using defaults for unset fields ```move module book::user; use std::string::String; /// A user account with multiple properties. public struct User has drop { name: String, age: u8, email: String, balance: u64, is_active: bool, } /// Creates a new user - requires all fields. public fun new( name: String, age: u8, email: String, balance: u64, is_active: bool, ): User { User { name, age, email, balance, is_active } } public fun balance(self: &User): u64 { self.balance } public fun is_active(self: &User): bool { self.is_active } public fun age(self: &User): u8 { self.age } ``` The corresponding builder: ```move #[test_only] module book::user_builder; use book::user::{Self, User}; use std::string::String; /// Builder for creating `User` instances in tests. public struct UserBuilder has drop { name: Option, age: Option, email: Option, balance: Option, is_active: Option, } /// Creates an empty builder with all fields unset. public fun new(): UserBuilder { UserBuilder { name: option::none(), age: option::none(), email: option::none(), balance: option::none(), is_active: option::none(), } } // === Setter methods - each returns the builder for chaining === public fun name(mut self: UserBuilder, name: String): UserBuilder { self.name = option::some(name); self } public fun age(mut self: UserBuilder, age: u8): UserBuilder { self.age = option::some(age); self } public fun email(mut self: UserBuilder, email: String): UserBuilder { self.email = option::some(email); self } public fun balance(mut self: UserBuilder, balance: u64): UserBuilder { self.balance = option::some(balance); self } public fun is_active(mut self: UserBuilder, is_active: bool): UserBuilder { self.is_active = option::some(is_active); self } /// Builds the `User`, using defaults for any unset fields. public fun build(self: UserBuilder): User { let UserBuilder { name, age, email, balance, is_active } = self; user::new( name.destroy_or!("Default User"), age.destroy_or!(18), email.destroy_or!("user@example.com"), balance.destroy_or!(0), is_active.destroy_or!(true), ) } ``` Here, the `new()` function initializes all fields to `option::none()`, representing an "unconfigured" state. Each setter method wraps the provided value in `option::some()` and stores it in the corresponding field. The key to the pattern is the `build()` function, which uses the `destroy_or!` macro to unwrap each `Option`: if a field was configured, its value is used; otherwise, the macro returns the default value provided as the second argument. This approach lets tests specify only the fields they care about while ensuring the final object is always fully initialized. ## Example Usage Without a builder, every test must specify all fields, even when only one field is relevant to the test: ```move #[test] fun test_balance_check_without_builder() { // We only care about `balance`, but must specify everything let user = user::new( "Alice", 25, "alice@example.com", 1000, // <-- the only field we care about true, ); assert!(user.balance() == 1000); } #[test] fun test_inactive_user_without_builder() { // We only care about `is_active`, but must specify everything let user = user::new( "Bob", 30, "bob@example.com", 500, false, // <-- the only field we care about ); assert!(user.is_active() == false); } ``` With a builder, tests become focused and self-documenting: ```move #[test] fun test_balance_check() { // Only specify what matters for this test let user = new() .balance(1000) .build(); assert!(user.balance() == 1000); } #[test] fun test_inactive_user() { // Only specify what matters for this test let user = new() .is_active(false) .build(); assert!(user.is_active() == false); } #[test] fun test_underage_user() { // Testing age-related logic let user = new() .age(16) .build(); assert!(user.age() < 18); } ``` Each test clearly shows which field matters. Adding new fields to `User` only requires updating the builder's `build()` function with a default - existing tests remain unchanged. ## Method Chaining The key to fluent builder syntax is method chaining. Each setter method takes `mut self` by value, modifies it, and returns the modified builder. Here's a very common example: ```move public fun is_active(mut self: UserBuilder, is_active: bool): UserBuilder { self.is_active = option::some(is_active); self } ``` Because the method takes ownership of `self` and returns `UserBuilder`, you can chain multiple calls together: ```move let user = user_builder::new() .name("Alice") .balance(1000) .is_active(true) .build(); ``` Each method in the chain consumes the previous builder and returns a new one. The final `build()` call consumes the builder and produces the target object. ## Usage in system packages The Sui Framework and Sui System packages use builders extensively for testing. The most notable examples are: ### ValidatorBuilder in Sui System The [`ValidatorBuilder`][validator-builder] in the `sui-system` package demonstrates a comprehensive builder for a complex type with many fields - cryptographic keys, network addresses, and economic parameters: ```move use sui_system::validator_builder; #[test] fun test_validator_operations() { let ctx = &mut tx_context::dummy(); let validator = validator_builder::preset(1) .name("My Validator") .gas_price(1000) .commission_rate(500) // 5% .initial_stake(100_000_000) .build(ctx); // test validator operations... } ``` The `preset(index)` function returns a builder pre-filled with valid test defaults - keys, addresses and economic parameters - for one of several predefined validators, so tests only override the fields they care about. ### TxContextBuilder in Sui Framework The [`TxContextBuilder`][tx-context-builder] allows customizing transaction context for specific test scenarios. The builder is passed to `begin_with_context()` to start a scenario, or to `next_with_context()` to advance an existing one: ```move use sui::test_scenario as ts; #[test] fun test_epoch_dependent_logic() { let mut test = ts::begin_with_context( ts::ctx_builder_from_sender(@0x1) .set_epoch(100) .set_epoch_timestamp(1_000_000), ); // test logic that depends on epoch... test.end(); } ``` ## Summary - A builder accumulates configuration through setter methods and produces the final object via `build()`. - Use `Option` fields to make configuration optional, with sensible defaults in `build()`. - Method chaining (`fun method(mut self, ...): Self`) creates a fluent API. - Builders reduce test boilerplate and isolate tests from changes to the target struct. - Reserve this pattern for test utilities where readability matters more than gas costs. [validator-builder]: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/tests/builders/validator_builder.move [tx-context-builder]: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/test/test_scenario.move --- # Random Inputs The Move compiler supports running tests with randomized inputs through the `#[random_test]` attribute. This enables property-based testing, where a test runs multiple times with randomly generated values to discover edge cases you might not think to test manually. > The `#[random_test]` attribute is a compiler feature for test inputs, separate from the > `sui::random` module used for onchain randomness. ## Basic Usage Mark a function with `#[random_test]` and declare parameters with primitive types. The test runner will generate random values for each parameter when the test is run. ```move module book::math; public fun safe_add(a: u64, b: u64): u64 { if (a > 0xFFFFFFFFFFFFFFFF - b) { 0xFFFFFFFFFFFFFFFF // saturate at max } else { a + b } } #[random_test] fun test_safe_add_never_overflows(a: u64, b: u64) { let result = safe_add(a, b); // Result should always be >= both inputs (no overflow wrap-around) assert!(result >= a && result >= b); } ``` ## Supported Types Random inputs work with all primitive types: | Type | Generated Range | | ----------------------------------------- | ----------------------------------------- | | `u8`, `u16`, `u32`, `u64`, `u128`, `u256` | Full range of the type | | `bool` | `true` or `false` | | `address` | Random 32-byte address | | `vector` | Random length vector with random elements | Note: `T` in `vector` must be a primitive type or another vector (e.g., `vector>`). ## Practical Tips **Constrain large integers**: If your function expects small values, use a smaller type and cast: ```move #[random_test] fun test_with_bounded_input(small: u8) { let bounded = (small as u64) % 100; // 0-99 range // ... test with bounded value } ``` **Avoid unbounded vectors**: `vector` can generate very large vectors, causing slow tests or gas errors. Prefer fixed-size inputs or construct vectors manually: ```move // Avoid: can generate huge vectors #[random_test] fun test_bad(v: vector) { /* ... */ } // Better: control the size #[random_test] fun test_good(a: u8, b: u8, c: u8) { let v = vector[a, b, c]; // ... test with known-size vector } ``` **Complement, don't replace**: Random tests discover unexpected edge cases but may miss specific scenarios. Use them alongside targeted unit tests: ```move use std::unit_test::assert_eq; // Targeted test for specific case #[test] fun test_add_zero() { assert_eq!(safe_add(std::u64::max_value!(), 0), std::u64::max_value!()); } // Random test for general properties #[random_test] fun test_add_commutative(a: u64, b: u64) { assert_eq!(safe_add(a, b), safe_add(b, a)); } ``` **Use `assert_eq!` for better debugging**: When a random test fails, you need to know which values caused the failure. Using [`assert_eq!`](./test-utilities.md#assert_eq-and-assert_ref_eq) prints both compared values on failure, making it easier to reproduce and debug issues: ```move use std::unit_test::assert_eq; #[random_test] fun test_double(value: u64) { let doubled = value * 2; // This can overflow, but we omit the check for brevity. // On failure, prints: "Assertion failed: != " assert_eq!(doubled / 2, value); } ``` ## Controlling Test Runs ### Number of iterations By default, random tests run multiple times with different inputs. Use `--rand-num-iters` to control how many iterations each random test runs: ```bash # Run each random test 100 times sui move test --rand-num-iters 100 ``` ### Reproducible seeds When a random test fails, the output includes the seed and instructions to reproduce: ``` ┌── test_that_failed ────── (seed = 2033439370411573084) │ ... │ This test uses randomly generated inputs. Rerun with `test test_that_failed --seed 2033439370411573084` to recreate this test failure. │ └────────────────── ``` Use the provided seed to reproduce the exact failure: ```bash sui move test test_that_failed --seed 2033439370411573084 ``` ## Limitations - **No range constraints**: You cannot limit random values to a specific range directly; use modulo or type casting as shown above - **Vector size**: No control over generated vector lengths ## Summary - Use `#[random_test]` (not `#[test]`) to enable randomized inputs for a test function - Parameters must be primitive types or vectors of primitives - Constrain inputs using smaller types and casting to avoid extreme values - Use `assert_eq!` for better failure diagnostics - Control iterations with `--rand-num-iters` and reproduce failures with `--seed` - Use random tests to complement, not replace, targeted unit tests --- # Extending Modules When testing code that depends on external packages, you often need to create test data for types defined in those packages. However, many libraries don't provide test utilities, leaving you unable to construct the objects your tests require. Module extensions solve this problem by allowing you to add test-only functions to foreign modules. > This feature is currently available only in `2024.alpha` edition. > To use it, you need to specify the edition in the `Move.toml` : > ```toml > [package] > edition = "2024.alpha" > ``` ## The Problem Consider an application that uses [Pyth Network](https://pyth.network/) for price feeds. Your code depends on `PriceInfoObject` from the Pyth package to get asset prices: ```move module app::trading; use pyth::price_info::PriceInfoObject; use pyth::price::{Self, Price}; /// Execute a trade using the current price from Pyth oracle public fun execute_trade(/* ... */ price_info: &PriceInfoObject, amount: u64): u64 { let price = get_price(price_info); // ... trading logic using the price amount * price / 1_000_000 } fun get_price(price_info: &PriceInfoObject): u64 { // Extract price from the oracle object // ... 0 // placeholder } ``` To test `execute_trade`, you need a `PriceInfoObject`. But Pyth's Sui implementation doesn't provide a `create_price_info_for_testing` function - the only way to get a `PriceInfoObject` is through actual oracle updates, which isn't practical in unit tests. Without extensions, your options are limited: - Skip testing price-dependent logic (dangerous) - Fork and modify the Pyth package (maintenance burden) ## What is an Extension? An extension allows you to add functions to an existing module - even one from a foreign package. Extended functions have access to the module's private types and can create, read, or modify them. This is expressed using the `extend` keyword: ```move #[test_only] extend module pyth::price_info; // Now you can define functions that have access to // pyth::price_info's private types and functions ``` Extensions are: - **Additive only**: Extensions can only add new declarations; they cannot modify or remove existing items in the target module - **Local to your package**: They don't affect downstream dependencies or the original package. Only extensions defined in the root package are applied - extensions in dependencies are ignored - **Mode-restricted**: Extensions require a mode attribute, most commonly `#[test_only]` for testing - **Powerful**: They have full access to the extended module's internals, as if the code were written directly in that module ## Solving the Pyth Problem Here's how to create a test helper for `PriceInfoObject` using an extension. First, create an extension file: ```move // tests/extensions/pyth_price_info_ext.move #[test_only] extend module pyth::price_info; public fun new_price_info_object_for_testing( price_info: PriceInfo, ctx: &mut TxContext, ): PriceInfoObject { PriceInfoObject { id: object::new(ctx), price_info, } } ``` Now you can write proper unit tests: ```move #[test_only] module app::trading_tests; use app::trading; use pyth::price_info; use std::unit_test::{Self, assert_eq}; #[test] fun test_execute_trade_with_price() { let ctx = &mut tx_context::dummy(); // Create test price data using our extension let price_info = price_info::new_price_info_object_for_testing( /* ... */ ctx, ); // Test the trading logic let result = trading::execute_trade(&price_info, 1000); assert_eq!(result, 50_000); // Clean up unit_test::destroy(price_info); } ``` ## Project Structure It's good practice to organize extensions in a dedicated folder: ``` my_project/ ├── sources/ │ └── trading.move ├── tests/ │ ├── extensions/ │ │ └── pyth_price_info_ext.move │ └── trading_tests.move └── Move.toml ``` This keeps test utilities separate from production code and makes it clear which modules have been extended. ## Extending Your Own Modules Extensions aren't limited to foreign packages - you can also extend modules in your own package. This is useful for adding test helpers without cluttering your production code with `#[test_only]` functions: ```move #[test_only] extend module app::trading; /// Test helper to check internal state public fun get_internal_value(/* ... */): u64 { // Access private fields for testing } #[test] fun test_internal_invariant() { // Test can live alongside the helper in the extension } ``` ## Other Use Cases Beyond oracle mocks, extensions are useful for: - **Creating and destroying objects with private fields**: When a dependency doesn't expose constructors for its types - **Exposing internal state through public accessors**: When you need to verify internal invariants in tests - **Mocking behavior**: When you need to simulate specific states that are hard to reach normally - **Testing error conditions**: When you need to create invalid states to test error handling ## Limitations Extensions have important constraints to be aware of: - **Mode attribute required**: Extensions must have a mode attribute like `#[test_only]`. When using `#[test_only]`, extensions only work when running `sui move test` and cannot be used in production builds. - **Additive only**: You can only add new declarations (functions, types, constants, use statements). You cannot modify, override, or shadow existing items in the target module. - **Root package only**: Only extensions defined in your root package are applied. If a dependency defines extensions, they are ignored in your build. - **Edition compatibility**: Extension code is subject to the same edition features as the target module. If the target module uses an older edition, your extension code must be compatible with that edition. - **Edition requirement**: Extensions are currently available only in the `2024.alpha` edition. Ensure your `Move.toml` specifies it. ## Further Reading - [Module Extensions | Reference](./../../reference/extensions) - detailed specification of the extension syntax and semantics - [Integrating Pyth in Sui](https://docs.pyth.network/price-feeds/core/use-real-time-data/pull-integration/sui) --- # Running Lints The Move compiler ships with a set of _lints_ - static checks that flag suspicious patterns in the code at compile time. Tests verify that the code does what it should; lints catch code that compiles and may even pass tests, but does something a more experienced Move developer would not write: transfers that break composability, comparisons that never do what they look like they do, or an `entry` function that can never be called. Running lints regularly - and keeping the package free of warnings - is a cheap way to maintain code quality. ## Running Lints The `sui move lint` command compiles the package and runs the full set of linters: ```bash sui move lint ``` To also check the code in the `tests` directory, add the `--test` flag: ```bash sui move lint --test ``` The same checks are available on other commands via the `--lint` flag - for example, `sui move test --lint` runs the tests and the full lint set in one go. Consider a module with a function that transfers a newly created object to the transaction sender: ```move module book::mint; public struct Item has key, store { id: UID } public fun mint(ctx: &mut TxContext) { let item = Item { id: object::new(ctx) }; transfer::transfer(item, ctx.sender()); } ``` Running the linter prints a warning with an explanation and a pointer to the exact expression: ``` warning[Lint W99001]: non-composable transfer to sender ┌─ ./sources/mint.move:7:5 │ 5 │ public fun mint(ctx: &mut TxContext) { │ ---- Returning an object from a function, allows a caller to use the object and enables composability via programmable transactions. 6 │ let item = Item { id: object::new(ctx) }; 7 │ transfer::transfer(item, ctx.sender()); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ │ │ │ │ │ Transaction sender address coming from here │ Transfer of an object to transaction sender address │ = This warning can be suppressed with '#[allow(lint(self_transfer))]' applied to the 'module' or module member ('const', 'fun', or 'struct') ``` The fix suggested by this particular lint is to return the `Item` from the function instead of transferring it, and let the caller decide what to do with the object. ## Default and Extra Lints Lints come in two tiers. The _default_ tier contains the most important Sui-specific checks, and runs on every compilation - a plain `sui move build` or `sui move test` reports these warnings too. The _extra_ tier adds two more Sui checks and a set of code style lints; it runs when linting is explicitly requested - by `sui move lint` or the `--lint` flag. ## Suppressing Lints Lints are heuristics, and sometimes the flagged code is intentional. A lint can be suppressed with the `#[allow(lint())]` attribute, applied to a module or a module member, using the lint name printed in the warning: ```move public struct Account has key { id: UID } /// An account object, deliberately created for and owned by the sender. #[allow(lint(self_transfer))] public fun new_account(ctx: &mut TxContext) { transfer::transfer( Account { id: object::new(ctx) }, ctx.sender(), ); } ``` A single attribute can suppress multiple lints: `#[allow(lint(share_owned, self_transfer))]`. Treat suppressions like any other exception - keep them narrow (prefer a function over the whole module) and explain the reason in a comment or doc comment. ## Lints in CI To enforce a warning-free codebase, add the `--warnings-are-errors` flag - the command then fails with a non-zero exit code on any warning, including lints: ```bash sui move lint --test --warnings-are-errors ``` For tooling that consumes the output programmatically, `--json-errors` switches diagnostics to JSON format. ## Lint Reference The linter groups its checks into two sets: the _default_ lints that run on every compilation, and the _extra_ lints that only run under the `--lint` flag. ### Default Lints These run on every compilation: | Lint | Code | What it flags | | --- | --- | --- | | `share_owned` | W99000 | Sharing an object that may have been previously owned; share objects in the transaction that creates them | | `self_transfer` | W99001 | Transferring a new object to the sender instead of returning it; hurts composability | | `custom_state_change` | W99002 | A custom transfer/share/freeze policy on a type with `store`; the `public_*` [storage functions](./../storage/storage-functions) can bypass it | | `coin_field` | W99003 | A struct field of type `Coin`; [`Balance`](./../programmability/balance-and-coin) is cheaper and usually the right choice | | `freeze_wrapped` | W99004 | Freezing an object that wraps other objects | | `collection_equality` | W99005 | Comparing [dynamic collections](./../programmability/dynamic-collections) with `==`; only the `id` and `size` are compared, never the contents | | `public_random` | W99006 | A `public` function taking [`Random`](./../programmability/randomness); exposes randomness to composition attacks | | `missing_key` | W99007 | A struct with an `id: UID` field but no `key` ability | | `public_entry` | W99010 | Unnecessary [`entry`](./../move-advanced/entry-functions) modifier on a `public` function | | `uncallable_function` | W99011 | A function that can never be called in a transaction, such as an `entry` function taking `&mut Clock` | ### Extra Lints Enabled by `sui move lint` or the `--lint` flag: | Lint | Code | What it flags | | --- | --- | --- | | `freezing_capability` | W99008 | Freezing a type that looks like a [capability](./../programmability/capability) | | `prefer_mut_tx_context` | W99009 | A `public` function taking `&TxContext`; prefer `&mut TxContext` to keep the signature future-proof | The extra tier also includes code style lints (codes `W04xxx`): `constant_naming`, `while_true`, `unnecessary_math`, `unneeded_return`, `abort_without_constant`, `loop_without_exit`, `unnecessary_conditional`, `self_assignment`, `redundant_ref_deref`, `unnecessary_unit`, `always_equal_operands`, and `combinable_comparisons`. Each flags a small readability or correctness issue and suggests the simpler equivalent. ## Summary | Command | Description | | --- | --- | | `sui move lint` | Compile the package and run the full lint set | | `sui move lint --test` | Also lint the code in the `tests` directory | | `sui move lint --warnings-are-errors` | Fail on any warning - for CI | | `sui move build` / `sui move test` | Run the default lint tier | | `sui move test --lint` | Run tests with the full lint set | | `--no-lint` | Disable linters entirely | ## Further Reading - [Code Quality Checklist](./../guides/code-quality-checklist) - a broader review checklist that lints automate a part of. - [Move CLI reference](https://docs.sui.io/references/cli/move) in the Sui Documentation. --- # Generating Coverage Reports Code coverage is a metric that shows which parts of your code are executed during tests. It helps identify untested code paths and ensures your tests are comprehensive. The `--coverage` flag on `sui move test` generates coverage data, and `sui move coverage` provides tools to analyze it. ## Running Tests with Coverage To generate coverage data, run your tests with the `--coverage` flag: ```bash sui move test --coverage ``` This will run all tests and collect coverage information. The coverage data is stored in a `.coverage_map.mvcov` file in the package root (next to `Move.toml`) and can be analyzed using the `sui move coverage` subcommands. ## Coverage Summary The `sui move coverage summary` command displays a high-level overview of coverage for all modules: ```bash sui move coverage summary ``` This outputs a table showing the coverage percentage for each module: ``` +-------------------------+ | Move Coverage Summary | +-------------------------+ Module 0000000000000000000000000000000000000000000000000000000000000000::my_module >>> % Module coverage: 85.71 Module 0000000000000000000000000000000000000000000000000000000000000000::another_module >>> % Module coverage: 100.00 Module 0000000000000000000000000000000000000000000000000000000000000000::untested_module >>> % Module coverage: 0.00 +-------------------------+ | % Move Coverage: 62.50 | +-------------------------+ ``` > Modules are listed under the full 32-byte form of their package address - `0x0` in this example. To see coverage broken down by individual functions, add the `--summarize-functions` flag: ```bash sui move coverage summary --summarize-functions ``` For programmatic processing, you can output the results in CSV format: ```bash sui move coverage summary --csv ``` ## Source Coverage The `source` subcommand shows which lines of a specific module were executed: ```bash sui move coverage source --module ``` This displays the source code with coverage annotations, showing which lines were covered (executed during tests) and which were not. This is useful for identifying specific code paths that need additional test coverage. ## LCOV Format For integration with external tools and CI/CD pipelines, you can generate coverage reports in [LCOV format](https://github.com/linux-test-project/lcov). LCOV is a widely-supported format that works with many coverage visualization tools. First, run tests with the `--trace` flag to generate the necessary trace data: ```bash sui move test --coverage --trace ``` Then generate the LCOV report: ```bash sui move coverage lcov ``` This creates an `lcov.info` file in the current directory. The file contains detailed coverage information that can be used with tools like: - [genhtml](https://github.com/linux-test-project/lcov) - Generate HTML coverage reports - [VS Code Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) - Visualize coverage in your editor - [Codecov](https://codecov.io/) / [Coveralls](https://coveralls.io/) - Upload to coverage tracking services ### Generating HTML Reports To generate an HTML report from the LCOV file, use `genhtml` (part of the LCOV package): ```bash genhtml lcov.info -o coverage_html ``` This creates a `coverage_html` directory with an interactive HTML report you can open in a browser. ### Differential Coverage The `lcov` command supports differential coverage analysis with the `--differential-test` flag. This shows which lines are covered exclusively by a specific test: ```bash sui move coverage lcov --differential-test ``` Lines hit only by the specified test show as covered, while lines hit by both the specified test and other tests show as uncovered. This helps identify what unique coverage each test provides. ### Single Test Coverage To generate coverage for a single test only: ```bash sui move coverage lcov --only-test ``` This is useful for understanding the coverage footprint of individual tests. ## Bytecode Coverage For advanced debugging, you can view coverage against disassembled bytecode: ```bash sui move coverage bytecode --module ``` This shows coverage at the bytecode level, which can be useful for understanding exactly which instructions were executed. ## Summary | Command | Description | | --- | --- | | `sui move test --coverage` | Run tests and collect coverage data | | `sui move test --coverage --trace` | Run tests with trace data (required for LCOV) | | `sui move coverage summary` | Show coverage percentage per module | | `sui move coverage summary --summarize-functions` | Show coverage broken down by function | | `sui move coverage summary --csv` | Output coverage summary in CSV format | | `sui move coverage source --module ` | Show line-by-line coverage for a module | | `sui move coverage lcov` | Generate LCOV report (`lcov.info`) | | `sui move coverage lcov --differential-test ` | Show lines covered exclusively by a test | | `sui move coverage lcov --only-test ` | Generate coverage for a single test | | `sui move coverage bytecode --module ` | Show coverage against disassembled bytecode | --- # Gas Profiling Understanding gas consumption helps optimize your Move code and estimate transaction costs. The Move testing framework provides built-in tools to measure gas usage during test execution. In addition to that, a special utility `sui analyze-trace` is available for more thorough analysis of gas usage. > The statistics shown by `-s` only reflect **computation units** - they do not include storage > costs. Additionally, compiler computation units don't map directly to actual onchain gas charges; > they show relative computational complexity, useful for comparing implementations against each > other. To get actual gas costs, publish your package to testnet and measure real transactions. ## Simple Measurement: Test Statistics Use the `-s` or `--statistics` flag with `sui move test` to see execution time and gas consumption for each test: ```bash sui move test -s ``` The output shows a table with three columns: ```table Test Statistics: ┌────────────────────────────────────────────────────────┬────────────┬───────────────────────────┐ │ Test Name │ Time │ Gas Used │ ├────────────────────────────────────────────────────────┼────────────┼───────────────────────────┤ │ book::my_module::test_simple_operation │ 0.006 │ 998001 │ ├────────────────────────────────────────────────────────┼────────────┼───────────────────────────┤ │ book::my_module::test_complex_operation │ 0.007 │ 998068 │ ├────────────────────────────────────────────────────────┼────────────┼───────────────────────────┤ │ book::my_module::test_with_objects │ 0.006 │ 998001 │ └────────────────────────────────────────────────────────┴────────────┴───────────────────────────┘ Test result: OK. Total tests: 3; passed: 3; failed: 0 ``` - **Test Name**: Fully qualified name of the test function - **Time**: Execution time in seconds - **Gas Used**: Gas units consumed by the test > Every test's total includes a large fixed base cost - even an empty test reports roughly 998000 > gas units. When comparing tests, look at the difference between their totals rather than the > absolute values. ## CSV Output For programmatic analysis or importing into spreadsheets, use the `csv` option: ```bash sui move test -s csv ``` This produces comma-separated output: ``` name,nanos,gas book::my_module::test_simple_operation,5992125,998001 book::my_module::test_complex_operation,6870583,998068 book::my_module::test_with_objects,6022917,998001 ``` The time is in nanoseconds, which allows for more precise measurements when comparing similar operations. ## Gas Limits Use the `-i` or `--gas-limit` flag to set a maximum gas budget for tests. Tests exceeding this limit will timeout: ```bash sui move test -i 1000 ``` > The limit is measured in internal execution gas units, which do not map one-to-one to the values > in the `Gas Used` column - a trivial test that reports ~998000 gas passes comfortably with a > limit of 1000. Output when a test exceeds the gas limit: ``` [ TIMEOUT ] book::my_module::test_complex_operation [ PASS ] book::my_module::test_simple_operation [ PASS ] book::my_module::test_with_objects Test failures: Failures in book::my_module: ┌── test_complex_operation ────── │ Test timed out └────────────────── Test result: FAILED. Total tests: 3; passed: 2; failed: 1 ``` This is useful for: - **Identifying expensive operations**: Find tests that consume unexpected amounts of gas - **Enforcing gas budgets**: Ensure critical paths stay within acceptable limits - **Testing gas exhaustion**: Verify your code handles out-of-gas scenarios correctly (see [Expected Failures](./testing-basics.md#expected-failures)) ## Comparing Implementations Use statistics to compare gas consumption between different implementations: ```move module book::comparison; use std::unit_test::assert_eq; public fun sum_loop(n: u64): u64 { let mut sum = 0; n.do!(|i| sum = sum + i); sum } public fun sum_formula(n: u64): u64 { n * (n - 1) / 2 } #[test] fun test_sum_loop() { let result = sum_loop(1000); assert_eq!(result, 499500); } #[test] fun test_sum_formula() { let result = sum_formula(1000); assert_eq!(result, 499500); } ``` Running with statistics reveals the difference: ```bash sui move test comparison -s ``` ```table ┌────────────────────────────────────┬────────────┬───────────────────────────┐ │ Test Name │ Time │ Gas Used │ ├────────────────────────────────────┼────────────┼───────────────────────────┤ │ book::comparison::test_sum_loop │ 0.003 │ 998078 │ ├────────────────────────────────────┼────────────┼───────────────────────────┤ │ book::comparison::test_sum_formula │ 0.001 │ 998001 │ └────────────────────────────────────┴────────────┴───────────────────────────┘ ``` The loop costs 77 gas units on top of the base cost, while the formula adds nothing measurable. ## Trace Analysis For deeper profiling, you can generate execution traces from tests and visualize them with [speedscope](https://www.speedscope.app/). This shows a flamegraph of gas consumption broken down by function calls, making it easy to spot exactly where gas is being spent. ### Step 1: Generate Traces Run tests with the `--trace` flag to produce trace files: ```bash sui move test --trace ``` Trace files are written to the `traces/` directory in the package root (next to `Move.toml`). ### Step 2: Generate a Gas Profile Use `sui analyze-trace` with the `gas-profile` subcommand to convert a trace into a profile: ```bash sui analyze-trace -p traces/ gas-profile ``` This outputs a `gas_profile_.json` file in the current directory. You can specify a different output directory with the `-o` flag, which goes before the `gas-profile` subcommand: ```bash sui analyze-trace -p traces/ -o ./profiles gas-profile ``` ### Step 3: Visualize with Speedscope Install [speedscope](https://www.speedscope.app/) and open the profile: ```bash npm install -g speedscope speedscope gas_profile_.json ``` Speedscope provides three views: - **Time Order**: Shows the call stack from left to right in invocation order. Bar width corresponds to gas consumption. - **Left Heavy**: Groups repeated calls together, ordered by total gas consumption - useful for finding the most expensive code paths. - **Sandwich**: Lists gas consumption per function with **Total** (including called functions) and **Self** (function only) columns. ## Further Reading - [Running Tests](./testing-basics.md) - Basic test execution and expected failures - [Test Utilities](./test-utilities.md) - Assertion macros and test helpers - [Collections](./../programmability/collections.md) - Choosing efficient data structures - [Trace Analysis](https://docs.sui.io/references/cli/trace-analysis) - Sui CLI trace analysis reference --- # Advanced Move Usage This chapter covers advanced features of the Move language, including various extended behaviors for advanced programming. This includes advanced usage of the language itself, plus the package and build system. - [Compilation Modes](./modes) - including unpublishable code in named build modes. - [Entry Functions](./entry-functions) - the `entry` modifier and the static hot-potato guarantee that makes it a safe transaction boundary. --- # Modes Modes let you include **unpublishable** code only when you explicitly opt into a named build `mode`. Think of them as generalizations of the `#[test_only]` [test annotation](../move-basics/testing) for any purpose you choose (e.g. `debug`, `benchmark`, `spec`, or any other feature). Modes at a glance: * Annotate items with `#[mode(name, ...)]` or use the shorthand `#[test_only]` for the built-in `test` mode. * The `#[test_only]` attribute is syntactic sugar for `#[mode(test)]`. * Build with `--mode ` (or `--test` for unit testing). Items whose mode list contains a name you enabled are compiled in. Items whose mode list does **not** match are compiled **out**. * Code compiled with any mode enabled is **not publishable**. This keeps debug/test scaffolding from ever making it onchain. * Items with **no** `#[mode(...)]`/`#[test_only]` annotation are always included. > Tip: Modes are filters enforced at compile-time - they don’t affect bytecode at runtime. Use them > for helpers, simulators, and other mock types and functions that should never be published. ## Syntax Like `#[test_only]`, You can attach a mode attribute to modules and to individual members: ```move // Entire module is included only when a matching mode is enabled #[mode(debug)] module my_pkg::debug_tools { public fun dump_state() { /* ... */ } } module my_pkg::library { // This function exists only in `debug` or `test` builds #[mode(debug, test)] public fun assert_invariants() { /* ... */ } // Test-only helper; equivalent to #[mode(test)] #[test_only] fun mk_fake() { /* ... */ } } ``` As we can see here, multiple modes can be listed in a single attribute: `#[mode(name1,name2,...)]`. This item will be included during compilation if **any** of the listed names is enabled. In addition, any definition without a mode annotation is always included. > Tip: The annotation `#[mode(test)]` is equivalent to `#[test_only]`. ## Building with modes Use the Sui CLI to opt into a mode when building or testing: ```bash # Build with a custom mode enabled sui move build --mode debug # Run tests; includes #[test_only] automatically sui move test --test # Combine: run unit tests with extra debug helpers sui move test --test --mode debug ``` Items annotated with a mode you enabled are compiled **in**; items annotated with a different, non-enabled mode are compiled **out**. Unannotated items are always compiled in. > **Publish safety**: Any artifact produced while a mode is enabled (including `--test`) is non-publishable. Always run a clean build **without** `--mode`/`--test` before `sui client publish`. ### Example - `test` mode (unit tests) `#[test_only]` is the built-in mode for unit testing. It works exactly like a mode named `test`. ```move #[mode(test)] module my_pkg::math_tests { use my_pkg::math; #[mode(test)] fun add_basic() { /* ... */ } // Private test helper fun mk_case() { /* ... */ } } ``` To build and run: ```bash # Includes modules and members marked #[test_only] sui move test --test ``` As described in the [testing](../move-basics/testing) documentation, this is a great way to keep test helpers and test-only public functions out of published packages. ### Example 2: Debug testing Suppose you have a `bank` module with a `transfer` function. You want to add debug logging in test runs where you can see internal state, but you only want to run that test with those logs during development (e.g., not during CI, etc). You can use a `debug` mode for this. ```move module my_pkg::bank { use std::error; public fun transfer(from: &signer, to: address, amount: u64) { // ... production logic ... } } // Debug-only wrappers & helpers #[mode(debug)] module my_pkg::bank_debug { use std::debug; use std::string::String; use my_pkg::bank; public fun transfer_debug(from: &signer, to: address, amount: u64) { // Perform debugging prints before the real call let begin: String = "[DEBUG] transfer begin"; debug::print(&begin); debug::print(&amount); debug::print(&to); // Main Call bank::transfer(from, to, amount); // More debugging prints let end: String = "[DEBUG] transfer end"; debug::print(&end); } } ``` Here, `bank::transfer` is the **only** production entry point, with not printing. The `#[mode(debug)]` exposes `bank_debug::{transfer_debug, dump_account, ...}`, however, which will **only** be included in `debug`-mode builds. Now, we can write tests that use this extra visibility without affecting production code or other tests: ```move #[test_only] module my_pkg::bank_tests { use my_pkg::bank; // Runs in all builds (no mode needed) #[test] fun transfer_basic() { // create signers, call bank::transfer(...) } // Runs only with `--test --mode debug` #[mode(debug)] #[test] fun transfer_with_logs() { use my_pkg::bank_debug; // only exists in debug builds // create signers, then: bank_debug::transfer_debug(&signer, @bob, 100); // assertions same as normal test; plus you see prints } } ``` Now we can execute this test with extra logging by enabling the `debug` mode: ```bash # Standard tests (no debug helpers compiled in) sui move test # Debug tests with extra logging sui move test --mode debug ``` This allows us to produce production bytecode, continuous integration tests, and debug logging tests, each at different times, without code duplication or complex branching. ## Publication Code built with any mode enabled is non-publishable. Always do a clean build without `--mode` or `--test` before publishing: ```bash sui move build # no --mode, no --test ``` ## See also * [Testing basics](../move-basics/testing) in the Move Book. * [Modes](/reference/modes) in the Move Reference. --- # Entry Functions An [`entry`](./../move-basics/visibility#entry-modifier) function is a special kind of transaction-callable function - one that deliberately _limits_ the options of its caller. As covered in the [Visibility Modifiers](./../move-basics/visibility) chapter, `entry` is not a visibility level, and it is not how functions are normally made callable from a [transaction](./../concepts/what-is-a-transaction) - a `public` function already is, and `public` remains the default. The `entry` modifier is only meaningful on _non-public_ functions - private or `public(package)` - and what it creates is a function with a narrowed contract, in two directions: - _who can call it:_ outside of its own module (or package), the function can be invoked only as a command in a transaction - no other package can wrap it, act on its result, or build it into larger logic; - _what the call can be combined with:_ the arguments passed to it must be free of obligations created by other commands in the same transaction - they are checked to behave as if the `entry` function were the only command. The first restriction follows directly from visibility rules and was covered in Move Basics. This chapter describes the second - a static guarantee about the arguments, and the rules behind it. The material requires familiarity with the [hot potato pattern](./../programmability/hot-potato-pattern), the [abilities](./../move-basics/abilities-introduction), and how transactions are structured, which is why it lives here rather than in Move Basics. ## The Hot Potato Guarantee Arguments to a non-`public` `entry` function (either private or `public(package)`) cannot be _entangled_ with a [hot potato](./../programmability/hot-potato-pattern) - a value whose type has neither `store` nor `drop`, and which therefore must be dealt with before the transaction ends. In practice this means the arguments behave as if the `entry` function were the only command in the transaction: no earlier command can force behavior on the transaction after the `entry` function is called. > This guarantee is checked _statically_, before the transaction begins execution. A transaction > that violates it fails verification and is not executed. These rules were introduced in Sui v1.62, > replacing an older, more restrictive set. The canonical motivation is a _flash loan_ - borrowing funds that must be repaid within the same transaction. A simplified lender looks like this: ```move module flash::loan; use sui::balance::Balance; use sui::sui::SUI; public struct Bank has key { id: UID, holdings: Balance, } /// A hot potato: no `store`, no `drop`. Once issued, the transaction /// cannot succeed until it is destroyed by calling `repay`. public struct Loan { amount: u64, } public fun issue(bank: &mut Bank, amount: u64): (Balance, Loan) { assert!(bank.holdings.value() >= amount); let loaned = bank.holdings.split(amount); (loaned, Loan { amount }) } public fun repay(bank: &mut Bank, loan: Loan, repayment: Balance) { let Loan { amount } = loan; assert!(repayment.value() == amount); bank.holdings.join(repayment); } ``` A developer writing an `entry` function that accepts a `Coin` may want to be sure the coin is really "owned" by the sender, and not borrowed from such a bank with an outstanding repayment obligation. The `entry` rules provide exactly that. ## The Rules The verification tracks how many hot potato values are outstanding and which values they could influence. Some terminology: - A _value_ is any argument of a transaction command: a transaction input, the result of a previous command, or the gas coin. - A value is _hot_ if its type has neither `store` nor `drop`. This leaves three possible shapes: a type with no abilities at all, a type with only `copy`, or a type with only `key` (a type cannot have both `key` and `copy`, since `sui::object::UID` does not have `copy`). - Every value belongs to a _clique_ - a group of values that have been used together as arguments to a command, along with the results of that command. Each clique counts its outstanding hot values. The algorithm walks the commands of the transaction in order: 1. Each transaction input starts in its own clique with a count of zero. 2. When values are used together in a command - by value or by reference - their cliques are merged, and their counts are added together. 3. The count is decremented for each hot value _moved_ into the command (taken by value, not copied). 4. If the command calls a non-`public` `entry` function, the count of the merged clique must be zero at this point. Note that this means an `entry` function _can_ take hot values - they must just be the last hot values in their clique. 5. The results of the command join the merged clique, and the count is incremented for each hot result. Let's walk through it. Given a module with the following functions: ```move module book::example; use sui::coin::Coin; use sui::sui::SUI; public struct HotPotato() public fun hot(coin: &mut Coin): HotPotato { /* ... */ HotPotato() } public fun cool(potato: HotPotato) { let HotPotato() = potato; } entry fun spend(coin: &mut Coin) { /* ... */ } ``` The following transaction is rejected. The call to `hot` produced a hot potato, so `Input(0)` is in a clique with an outstanding hot value when `spend` is called: ```text // Invalid transaction // Input 0: Coin // cliques: { Input(0) } => 0 0: book::example::hot(Input(0)); // cliques: { Input(0), Result(0) } => 1 1: book::example::spend(Input(0)); // INVALID, Input(0)'s clique has a count > 0 2: book::example::cool(Result(0)); ``` Destroying the hot potato first brings the count back to zero, and the same call becomes valid: ```text // Valid transaction // Input 0: Coin // cliques: { Input(0) } => 0 0: book::example::hot(Input(0)); // cliques: { Input(0), Result(0) } => 1 1: book::example::cool(Result(0)); // cliques: { Input(0) } => 0 2: book::example::spend(Input(0)); // Valid! Input(0)'s clique has a count of 0 ``` The clique is what makes the rule robust: entanglement spreads through _any_ shared usage, not just direct one. Using the `flash::loan` module, the `Coin` below was created from the loaned `Balance` and never touched the `Loan` directly - yet it is in the same clique, and cannot be passed to the `entry` function until the loan is repaid: ```text // Invalid transaction // Input 0: flash::loan::Bank // Input 1: u64 // cliques: { Input(0) } => 0, { Input(1) } => 0 0: flash::loan::issue(Input(0), Input(1)); // cliques: { Input(0), NestedResult(0,0), NestedResult(0,1) } => 1 1: sui::coin::from_balance(NestedResult(0,0)); // cliques: { Input(0), NestedResult(0,1), Result(1) } => 1 2: book::example::spend(Result(1)); // INVALID, Result(1)'s clique has count > 0 3: sui::coin::into_balance(Result(1)); 4: flash::loan::repay(Input(0), NestedResult(0,1), Result(3)); ``` If the loan were repaid before calling `spend`, the transaction would pass verification. ## Shared Objects There is one special case: when a command takes a _shared object_ by value, the count of the merged clique is set to infinity. A non-`public` `entry` function can still take a shared object by value directly, but it cannot take a value whose clique previously interacted with one. The reason is that a shared object taken by value can force behavior in the rest of the transaction much like a hot potato does: it cannot be wrapped or transferred, so it must be either re-shared or deleted before the transaction ends. But unlike a hot potato, this obligation is not visible in the type's abilities, so the verification has to assume the worst. [Party objects](./../appendix/transfer-functions) taken by value fall under the same restriction, although in narrower cases than shared objects. > Because the rules are applied statically, before execution, they are deliberately pessimistic: a > dynamic check could be more precise, but a static one is easier to describe and to rely on. ## Further Reading - [Visibility Modifiers](./../move-basics/visibility) in Move Basics, for the basics of `entry`. - [Visibility](./../../reference/functions#visibility) in the Move Reference. --- # Move 2024 Migration Guide Move 2024 is the current edition of the Move language maintained by Mysten Labs, and the edition this book teaches. This guide is written for readers migrating code - or knowledge - from the original edition (referred to below as _Move 2020_): it lists what changed, feature by feature, with a before-and-after example for each. > This guide is a high-level overview. Every feature listed here has a dedicated section in the > book, linked from its heading - refer to them for the full story. ## Using the 2024 Edition The edition is specified in the `[package]` section of the [Package Manifest](./../concepts/manifest). The stable `2024` edition is the default choice and the one to prefer; the `2024.beta` and `2024.alpha` editions give early access to features that are still in development and may change: ```toml [package] name = "my_package" edition = "2024" ``` ## Migration Tool The Move CLI has a migration tool that updates legacy code to the new edition. To use the migration tool, run the following command in the package directory: ```bash $ sui move migrate ``` The migration tool handles the mechanical changes: the `let mut` syntax, the `public` modifier on structs, and the `public(package)` visibility in place of `friend` declarations. ## Module Label _See [Module](./../move-basics/module#module-block)._ A module no longer needs to wrap its body in a block: the _module label_ syntax declares the module once, and everything that follows belongs to it - saving a level of indentation in the entire file. The block syntax is still supported, but only useful for declaring multiple modules in one file, which is not a recommended practice: ```move // Move 2020: module block module book::my_module { public struct Book {} } // Move 2024: module label module book::my_module; public struct Book {} ``` ## Mutable Bindings with `let mut` _See [Primitive Types](./../move-basics/primitive-types#variables-and-assignment)._ Move 2024 requires the `mut` keyword to declare a variable that can be reassigned or mutably borrowed. The compiler emits an error on an attempt to change a variable declared without `mut`: ```move // Move 2020 let x: u64 = 10; x = 20; // Move 2024 let mut x: u64 = 10; x = 20; ``` Additionally, the `mut` keyword is used in tuple destructuring and function arguments, placed before the variable name: ```move // takes by value and mutates fun takes_by_value_and_mutates(mut v: Value): Value { v.field = 10; v } // in tuple destructuring fun destruct() { let (mut x, y) = point::get_point(); } // in struct unpack fun unpack() { let Point { x, mut y } = point::get_point(); } ``` ## Struct Visibility _See [Custom Types with Struct](./../move-basics/struct#defining-a-struct)._ In Move 2024, struct declarations require a visibility modifier. Currently, the only available visibility is `public`: ```move // Move 2020 struct Book {} // Move 2024 public struct Book {} ``` Note that `public` applies to the struct _type_ - the fields stay internal to the module, and only the defining module can pack and unpack the struct, exactly as before. ## Friends Are Deprecated _See [Visibility Modifiers](./../move-basics/visibility#package-visibility)._ The `friend` declarations and the `public(friend)` visibility are deprecated. In their place, the `public(package)` visibility makes a function callable from any module of the same package - with no declaration required. The `friend book::module_name;` statements are gone entirely: ```move // Move 2020 friend book::friend_module; public(friend) fun protected_function() {} // Move 2024: no friend declaration needed public(package) fun protected_function() {} ``` ## Method Syntax _See [Struct Methods](./../move-basics/struct-methods)._ Functions whose first argument is a type defined in the same module become _methods_ of that type, callable with the dot syntax anywhere the type is used: ```move public fun count(c: &Counter): u64 { /* ... */ } fun use_counter(c: &Counter) { // Move 2020 let count = counter::count(c); // Move 2024 let count = c.count(); } ``` The standard library and the Sui Framework make full use of this: native and standard types come with associated methods out of the box: ```move // vector to string and ascii string let str: String = b"Hello, World!".to_string(); let ascii: ascii::String = b"Hello, World!".to_ascii_string(); // address to bytes let bytes = @0xa11ce.to_bytes(); ``` ## `use fun` and Method Aliases _See [Struct Methods](./../move-basics/struct-methods#method-aliases)._ The `use fun` declaration associates a function with a type under a chosen method name. An alias can be declared for any type locally to the module; or publicly - with `public use fun` - if the type is defined in the same module: ```move // Local: the type is foreign to the module use fun my_custom_function as vector.do_magic; // Exported: the type is defined in the same module public use fun kiosk_owner_cap_for as KioskOwnerCap.kiosk; ``` ## Index Syntax for Borrowing _See [Vector](./../move-basics/vector#reading-elements) and [Index Syntax](./../../reference/index-syntax) in the Move Reference._ Square brackets replace explicit `borrow` and `borrow_mut` calls on collection types: ```move fun play_vec() { let mut v = vector[1, 2, 3, 4]; let first = &v[0]; // calls vector::borrow(&v, 0) let first_mut = &mut v[0]; // calls vector::borrow_mut(&mut v, 0) let first_copy = v[0]; // calls *vector::borrow(&v, 0) } ``` The syntax is supported by `vector` and the collection types of the Sui Framework: `VecMap`, `Table`, `Bag`, `ObjectTable`, `ObjectBag`, and `LinkedTable`. A custom type can implement it by marking its borrow functions with the `#[syntax(index)]` attribute: ```move #[syntax(index)] public fun borrow(c: &List, key: String): &T { /* ... */ } #[syntax(index)] public fun borrow_mut(c: &mut List, key: String): &mut T { /* ... */ } ``` ## String Literals _See [String](./../move-basics/string#string-literals)._ Move 2020 offered only byte-string literals, and constructing a `String` required an explicit conversion. The new edition adds the string literal `"..."`, with the type _inferred_ from context - it becomes a `String`, an `ascii::String`, or a `vector`, whichever is expected: ```move // Move 2020: bytes, converted at runtime let str: String = string::utf8(b"Hello"); // Move 2024: the literal is checked and typed at compile time let str: String = "Hello"; let ascii: std::ascii::String = "ASCII"; ``` The contents are validated at compile time: a literal used as an `ascii::String` must contain only ASCII characters, or the code will not compile. ## Enums and `match` _See [Enums and Match](./../move-basics/enum-and-match)._ Move 2024 introduces _enums_ - user-defined types with multiple variants - and the `match` expression for handling them. Together they allow expressing varying data structures under a single type, something previously emulated with multiple structs and runtime checks: ```move /// One type - three different shapes of data. public enum Segment has copy, drop { Empty, String(String), Special { content: vector, encoding: u8 }, } public fun is_empty(s: &Segment): bool { match (s) { Segment::Empty => true, _ => false, } } ``` The `match` expression is not limited to enums: it works on primitive values and structs as well, requires the arms to be exhaustive, and supports the `_` wildcard for the remaining cases. ## Macros _See [Macro Functions](./../move-basics/macros)._ Move 2024 introduces _macro functions_ - functions expanded at the call site during compilation, which can take _lambdas_ as arguments. Macro names are followed by the `!` mark: ```move // can be called as `for!(0, 10, |i| call(i));` macro fun for($start: u64, $stop: u64, $body: |u64|) { let mut i = $start; let stop = $stop; while (i < stop) { $body(i); i = i + 1 } } ``` The familiar `assert!` is no longer special-cased compiler magic - it is a regular macro, and its error-code argument is now optional. The standard library ships a rich set of macros which quickly became the idiomatic way to write iteration: ```move let v = vector[1, 2, 3]; // instead of a hand-written while loop: let doubled = v.map!(|n| n * 2); let sum = v.fold!(0, |acc, n| acc + n); v.do!(|n| std::debug::print(&n)); ``` ## Abort Without a Code _See [Aborting Execution](./../move-basics/assert-and-abort#omitting-the-abort-code)._ The abort code is now optional: a bare `abort` (and `assert!` without a second argument) derives the code automatically, encoding the module and source line of the failure. It is a good fit for branches that are not expected to be reachable: ```move // Move 2020: a code was always required if (!is_valid) abort 0; // Move 2024 if (!is_valid) abort; assert!(is_valid); ``` ## Clever Errors _See [Aborting Execution](./../move-basics/assert-and-abort#error-messages)._ Error constants marked with the `#[error]` attribute can carry a human-readable message - a `vector` instead of a bare `u64`. On abort, tooling decodes the constant name, the message, and the source line, removing the need to look up numeric codes: ```move #[error] const ENotAuthorized: vector = "The caller is not authorized to perform this action"; public fun protected_action(/* ... */) { assert!(is_authorized, ENotAuthorized); } ``` ## Extending Modules in Tests _See [Extending Modules](./../testing/extend-foreign-module)._ The `extend module` declaration adds test-only members to an existing module - including a module from a foreign package - with full access to its private types. It solves the long-standing problem of testing against dependencies that ship no test utilities: ```move #[test_only] extend module pyth::price_info; // Functions defined here can pack and unpack the private // types of `pyth::price_info` - in tests only. ``` > Module extensions are still in development and currently require the `2024.alpha` edition. ## Further Reading - [Move 2024 Migration Guide](https://blog.sui.io/move-2024-migration-guide) on the Sui Blog. --- # Upgradeability Practices > This guide builds on the [Package Upgrades](./../programmability/package-upgrades) section, which > explains the mechanics of upgrades: versions, the `UpgradeCap`, and state migrations. To talk about best practices for upgradeability, we need to first understand what can be upgraded in a package. The base premise of upgradeability is that an upgrade should not break public compatibility with the previous version. The parts of the module which can be used in dependent packages should not change their static signature. This applies to modules - a module can not be removed from a package, public structs - they can be used in function signatures and public functions - they can be called from other packages. ```move // module can not be removed from the package module book::upgradable; // dependencies can be changed (if they are not used in public signatures) use std::string::String; use sui::event; // can be removed // public structs can not be removed and can't be changed public struct Book has key { id: UID, title: String, } // the same rule applies to event structs public struct BookCreated has copy, drop { /* ... */ } // public functions can not be removed and their signature can never change // but the implementation can be changed public fun create_book(ctx: &mut TxContext): Book { create_book_internal(ctx) // can be removed and changed event::emit(BookCreated { /* ... */ }) } // package-visibility functions can be removed and changed public(package) fun create_book_package(ctx: &mut TxContext): Book { create_book_internal(ctx) } // entry functions can be removed and changed as long as they're not public entry fun create_book_entry(ctx: &mut TxContext): Book { create_book_internal(ctx) } // private functions can be removed and changed fun create_book_internal(ctx: &mut TxContext): Book { abort } ``` ## Versioning objects To discard previous versions of the package, the objects can be versioned. As long as the object contains a version field, and the code which uses the object expects and asserts a specific version, the code can be force-migrated to the new version. Normally, after an upgrade, admin functions can be used to update the version of the shared state, so that the new version of code can be used, and the old version aborts with a version mismatch. ```move module book::versioned_state; const EVersionMismatch: u64 = 0; const VERSION: u8 = 1; /// The shared state (can be owned too) public struct SharedState has key { id: UID, version: u8, /* ... */ } public fun mutate(state: &mut SharedState) { assert!(state.version == VERSION, EVersionMismatch); // ... } ``` ## Versioning configuration with dynamic fields There's a common pattern in Sui which allows changing the stored configuration of an object while retaining the same object signature. This is done by keeping the base object simple and versioned and adding an actual configuration object as a dynamic field. Using this _anchor_ pattern, the configuration can be changed with package upgrades while keeping the same base object signature. ```move module book::versioned_config; use sui::vec_map::VecMap; use std::string::String; /// The base object public struct Config has key { id: UID, version: u16 } /// The actual configuration public struct ConfigV1 has store { data: Bag, metadata: VecMap } // ... ``` --- # Building Against Limits To guarantee the safety and security of the network, Sui has certain limits and restrictions. These limits are in place to prevent abuse and to ensure that the network remains stable and efficient. This guide provides an overview of these limits and restrictions, and how to build your application to work within them. The limits are defined in the protocol configuration and are enforced by the network. If any of the limits are exceeded, the transaction will either be rejected or aborted. The limits, being a part of the protocol, can only be changed through a network upgrade. ## Transaction Size The size of a transaction is limited to 128KB. This includes the size of the transaction payload, the size of the transaction signature, and the size of the transaction metadata. If a transaction exceeds this limit, it will be rejected by the network. ## Object Size The size of an object is limited to 256KB. This includes the size of the object data. If an object exceeds this limit, it will be rejected by the network. While a single object cannot bypass this limit, for more extensive storage options, one could use a combination of a base object with other attached to it using dynamic fields (eg Bag). ## Single Pure Argument Size The size of a single pure argument is limited to 16KB. A transaction argument bigger than this limit will result in execution failure. So in order to create a vector of more than ~500 addresses (given that a single address is 32 bytes), it needs to be joined dynamically either in Transaction Block or in a Move function. Standard functions like `vector::append()` can join two vectors of ~16KB resulting in a ~32KB of data as a single value. ## Maximum Number of Objects (and Dynamic Fields) Created The maximum number of objects that can be created in a single transaction is 2048. If a transaction attempts to create more than 2048 objects, it will be rejected by the network. This also affects [dynamic fields](./../programmability/dynamic-fields.md), as both the key and the value are objects. So the maximum number of [dynamic fields](./../programmability/dynamic-fields.md) that can be created in a single transaction is 1000. The limitation applies to dynamic object fields as well. ## Maximum Number of Dynamic Fields Accessed The maximum number of dynamic fields that can be accessed in a single transaction is 1000. If a transaction attempts to access more than 1000 dynamic fields, it will be rejected by the network. ## Maximum Number of Events The maximum number of events that can be emitted in a single transaction is 1024. If a transaction attempts to emit more than 1024 events, it will be aborted. --- # Better Error Handling Whenever execution encounters an abort, transaction fails and abort code is returned to the caller. Move VM returns the module name that aborted the transaction and the abort code. This behavior is not fully transparent to the caller of the transaction, especially when a single function contains multiple calls to the same function which may abort. In this case, the caller will not know which call aborted the transaction, and it will be hard to debug the issue or provide meaningful error message to the user. ```move module book::module_a; use book::module_b; public fun do_something() { let field_1 = module_b::get_field(1); // may abort with 0 /* ... a lot of logic ... */ let field_2 = module_b::get_field(2); // may abort with 0 /* ... some more logic ... */ let field_3 = module_b::get_field(3); // may abort with 0 } ``` The example above illustrates the case when a single function contains multiple calls which may abort. If the caller of the `do_something` function receives an abort code `0`, it will be hard to understand which call to `module_b::get_field` aborted the transaction. To address this problem, there are common patterns that can be used to improve error handling. ## Rule 1: Handle All Possible Scenarios It is considered a good practice to provide a safe "check" function that returns a boolean value indicating whether an operation can be performed safely. If the `module_b` provides a function `has_field` that returns a boolean value indicating whether a field exists, the `do_something` function can be rewritten as follows: ```move module book::module_a; use book::module_b; const ENoField: u64 = 0; public fun do_something() { assert!(module_b::has_field(1), ENoField); let field_1 = module_b::get_field(1); /* ... */ assert!(module_b::has_field(2), ENoField); let field_2 = module_b::get_field(2); /* ... */ assert!(module_b::has_field(3), ENoField); let field_3 = module_b::get_field(3); } ``` By adding custom checks before each call to `module_b::get_field`, the developer of the `module_a` takes control over the error handling. And it allows implementing the second rule. ## Rule 2: Abort with Different Codes The second trick, once the abort codes are handled by the caller module, is to use different abort codes for different scenarios. This way, the caller module can provide a meaningful error message to the user. The `module_a` can be rewritten as follows: ```move module book::module_a; use book::module_b; const ENoFieldA: u64 = 0; const ENoFieldB: u64 = 1; const ENoFieldC: u64 = 2; public fun do_something() { assert!(module_b::has_field(1), ENoFieldA); let field_1 = module_b::get_field(1); /* ... */ assert!(module_b::has_field(2), ENoFieldB); let field_2 = module_b::get_field(2); /* ... */ assert!(module_b::has_field(3), ENoFieldC); let field_3 = module_b::get_field(3); } ``` Now, the caller module can provide a meaningful error message to the user. If the caller receives an abort code `0`, it can be translated to "Field 1 does not exist". If the caller receives an abort code `1`, it can be translated to "Field 2 does not exist". And so on. ## Rule 3: Return `bool` Instead of `assert` A developer is often tempted to add a public function that would assert all the conditions and abort the execution. However, it is a better practice to create a function that returns a boolean value instead. This way, the caller module can handle the error and provide a meaningful error message to the user. ```move module book::some_app_assert; const ENotAuthorized: u64 = 0; public fun do_a() { assert_is_authorized(); // ... } public fun do_b() { assert_is_authorized(); // ... } /// Don't do this public fun assert_is_authorized() { assert!(/* some condition */ true, ENotAuthorized); } ``` This module can be rewritten as follows: ```move module book::some_app; const ENotAuthorized: u64 = 0; public fun do_a() { assert!(is_authorized(), ENotAuthorized); // ... } public fun do_b() { assert!(is_authorized(), ENotAuthorized); // ... } public fun is_authorized(): bool { /* some condition */ true } // a private function can still be used to avoid code duplication for a case // when the same condition with the same abort code is used in multiple places fun assert_is_authorized() { assert!(is_authorized(), ENotAuthorized); } ``` Utilizing these three rules will make the error handling more transparent to the caller of the transaction, and it will allow other developers to use custom abort codes in their modules. --- # Code Quality Checklist The rapid evolution of the Move language and its ecosystem has rendered many older practices outdated. This guide serves as a checklist for developers to review their code and ensure it aligns with current best practices in Move development. Please read carefully and apply as many recommendations as possible to your code. ## Code Organization Some of the issues mentioned in this guide can be fixed by using [Move Formatter](https://www.npmjs.com/package/@mysten/prettier-plugin-move) either as a CLI tool, or [as a CI check](https://github.com/marketplace/actions/move-formatter), or [as a plugin for VSCode (Cursor)](https://marketplace.visualstudio.com/items?itemName=mysten.prettier-move). ## Package Manifest ### Use Right Edition All of the features in this guide require Move 2024 Edition, and it has to be specified in the package manifest. ```toml [package] name = "my_package" edition = "2024.beta" # or (just) "2024" ``` ### Implicit Framework Dependency Starting with Sui 1.45 you no longer need to specify framework dependency in the `Move.toml`: ```toml # old, pre 1.45 [dependencies] Sui = { ... } # modern day, Sui, Bridge, MoveStdlib and SuiSystem are imported implicitly! [dependencies] ``` ### Prefix Named Addresses If your package has a generic name (e.g., `token`) - especially if your project includes multiple packages - make sure to add a prefix to the named address: ```toml # bad! not indicative of anything, and can conflict [addresses] math = "0x0" # good! clearly states project, unlikely to conflict [addresses] my_protocol_math = "0x0" ``` ## Imports, Module and Constants ### Using Module Label ```move // bad: increases indentation, legacy style module my_package::my_module { public struct A {} } // good! module my_package::my_module; public struct A {} ``` ### No Single `Self` in `use` Statements ```move // correct, member + self import use my_package::other::{Self, OtherMember}; // bad! `{Self}` is redundant use my_package::my_module::{Self}; // good! use my_package::my_module; ``` ### Group `use` Statements with `Self` ```move // bad! use my_package::my_module; use my_package::my_module::OtherMember; // good! use my_package::my_module::{Self, OtherMember}; ``` ### Error Constants Are in `EPascalCase` ```move // bad! all-caps are used for regular constants const NOT_AUTHORIZED: u64 = 0; // good! clear indication it's an error constant const ENotAuthorized: u64 = 0; ``` ### Regular Constants Are `ALL_CAPS` ```move // bad! PascalCase is associated with error consts const MyConstant: vector = "my const"; // good! clear indication that it's a constant value const MY_CONSTANT: vector = "my const"; ``` ## Structs ### Capabilities are Suffixed with `Cap` ```move // bad! if it's a capability, add a `Cap` suffix public struct Admin has key, store { id: UID, } // good! reviewer knows what to expect from type public struct AdminCap has key, store { id: UID, } ``` ### No `Potato` in Names ```move // bad! it has no abilities, we already know it's a Hot-Potato type public struct PromisePotato {} // good! public struct Promise {} ``` ### Events Should Be Named in Past Tense ```move // bad! not clear what this struct does public struct RegisterUser has copy, drop { user: address } // good! clear, it's an event public struct UserRegistered has copy, drop { user: address } ``` ### Use Positional Structs for Dynamic Field Keys + `Key` Suffix ```move // not as bad, but goes against canonical style public struct DynamicField has copy, drop, store {} // good! canonical style, Key suffix public struct DynamicFieldKey() has copy, drop, store; ``` ## Functions ### No `public entry`, Only `public` or `entry` ```move // bad! entry is not required for a function to be callable in a transaction public entry fun do_something() { /* ... */ } // good! public functions are more permissive, can return value public fun do_something_2(): T { /* ... */ } ``` ### Write Composable Functions for PTBs ```move // bad! not composable, harder to test! public fun mint_and_transfer(ctx: &mut TxContext) { /* ... */ transfer::transfer(nft, ctx.sender()); } // good! composable! public fun mint(ctx: &mut TxContext): NFT { /* ... */ } // good! intentionally not composable entry fun mint_and_keep(ctx: &mut TxContext) { /* ... */ } ``` ### Objects Go First (Except for Clock) ```move // bad! hard to read! public fun call_app( value: u8, app: &mut App, is_smth: bool, cap: &AppCap, clock: &Clock, ctx: &mut TxContext, ) { /* ... */ } // good! public fun call_app( app: &mut App, cap: &AppCap, value: u8, is_smth: bool, clock: &Clock, ctx: &mut TxContext, ) { /* ... */ } ``` ### Capabilities Go Second ```move // bad! breaks method associativity public fun authorize_action(cap: &AdminCap, app: &mut App) { /* ... */ } // good! keeps Cap visible in the signature and maintains `.calls()` public fun authorize_action(app: &mut App, cap: &AdminCap) { /* ... */ } ``` ### Getters Named After Field + `_mut` ```move // bad! unnecessary `get_` public fun get_name(u: &User): String { /* ... */ } // good! clear that it accesses field `name` public fun name(u: &User): String { /* ... */ } // good! for mutable references use `_mut` public fun details_mut(u: &mut User): &mut Details { /* ... */ } ``` ## Function Body: Struct Methods ### Common Coin Operations ```move // bad! legacy code, hard to read! let paid = coin::split(&mut payment, amount, ctx); let balance = coin::into_balance(paid); // good! struct methods make it easier! let balance = payment.split(amount, ctx).into_balance(); // even better (in this example - no need to create temporary coin) let balance = payment.balance_mut().split(amount); // also can do this! let coin = balance.into_coin(ctx); ``` ### Do Not Import `std::string::utf8` ```move // bad! unfortunately, very common! use std::string::utf8; let str = utf8(b"hello, world!"); // good! the literal is checked at compile time let str: String = "hello, world!"; // also, for ASCII string let ascii: ascii::String = "hello, world!"; ``` > The `.to_string()` and `.to_ascii_string()` methods on `vector` still have their place - > converting bytes that are not known at compile time. For literals, prefer the string literal > syntax. ### UID has `delete` ```move // bad! object::delete(id); // good! id.delete(); ``` ### `ctx` has `sender()` ```move // bad! tx_context::sender(ctx); // good! ctx.sender() ``` ### Vector Has a Literal. And Associated Functions ```move // bad! let mut my_vec = vector::empty(); vector::push_back(&mut my_vec, 10); let first_el = vector::borrow(&my_vec); assert!(vector::length(&my_vec) == 1); // good! let mut my_vec = vector[10]; let first_el = my_vec[0]; assert!(my_vec.length() == 1); ``` ### Collections Support Index Syntax ```move let x: VecMap = /* ... */; // bad! x.get(&10); x.get_mut(&10); // good! &x[&10]; &mut x[&10]; ``` ## Option -> Macros ### Destroy And Call Function ```move // bad! if (opt.is_some()) { let inner = opt.destroy_some(); call_function(inner); }; // good! there's a macro for it! opt.do!(|value| call_function(value)); ``` ### Destroy Some With Default ```move let opt = option::none(); // bad! let value = if (opt.is_some()) { opt.destroy_some() } else { abort EError }; // good! there's a macro! let value = opt.destroy_or!(default_value); // you can even do abort on `none` let value = opt.destroy_or!(abort ECannotBeEmpty); ``` ## Loops -> Macros ### Do Operation N Times ```move // bad! hard to read! let mut i = 0; while (i < 32) { do_action(); i = i + 1; }; // good! any uint has this macro! 32u8.do!(|_| do_action()); ``` ### New Vector From Iteration ```move // harder to read! let mut i = 0; let mut elements = vector[]; while (i < 32) { elements.push_back(i); i = i + 1; }; // easy to read! vector::tabulate!(32, |i| i); ``` ### Do Operation on Every Element of a Vector ```move // bad! let mut i = 0; while (i < vec.length()) { call_function(&vec[i]); i = i + 1; }; // good! vec.do_ref!(|e| call_function(e)); ``` ### Destroy a Vector and Call a Function on Each Element ```move // bad! while (!vec.is_empty()) { call(vec.pop_back()); }; // good! vec.destroy!(|e| call(e)); ``` ### Fold Vector Into a Single Value ```move // bad! let mut aggregate = 0; let mut i = 0; while (i < source.length()) { aggregate = aggregate + source[i]; i = i + 1; }; // good! let aggregate = source.fold!(0, |acc, v| { acc + v }); ``` ### Filter Elements of the Vector > Note: `T: drop` in the `source` vector ```move // bad! let mut filtered = []; let mut i = 0; while (i < source.length()) { if (source[i] > 10) { filtered.push_back(source[i]); }; i = i + 1; }; // good! let filtered = source.filter!(|e| e > 10); ``` ## Other ### Ignored Values In Unpack Can Be Ignored Altogether ```move // bad! very sparse! let MyStruct { id, field_1: _, field_2: _, field_3: _ } = value; id.delete(); // good! 2024 syntax let MyStruct { id, .. } = value; id.delete(); ``` ## Testing ### Merge `#[test]` and `#[expected_failure(...)]` ```move // bad! #[test] #[expected_failure] fun value_passes_check() { abort } // good! #[test, expected_failure] fun value_passes_check() { abort } ``` ### Do Not Clean Up `expected_failure` Tests ```move // bad! clean up is not necessary #[test, expected_failure(abort_code = my_app::EIncorrectValue)] fun try_take_missing_object_fail() { let mut test = test_scenario::begin(@0); my_app::call_function(test.ctx()); test.end(); } // good! easy to see where test is expected to fail #[test, expected_failure(abort_code = my_app::EIncorrectValue)] fun try_take_missing_object_fail() { let mut test = test_scenario::begin(@0); my_app::call_function(test.ctx()); abort // will differ from EIncorrectValue } ``` ### Do Not Prefix Tests With `test_` in Testing Modules ```move // bad! the module is already called _tests module my_package::my_module_tests; #[test] fun test_this_feature() { /* ... */ } // good! better function name as the result #[test] fun this_feature_works() { /* ... */ } ``` ### Do Not Use `TestScenario` Where Not Necessary ```move // bad! no need, only using ctx let mut test = test_scenario::begin(@0); let nft = app::mint(test.ctx()); app::destroy(nft); test.end(); // good! there's a dummy context for simple cases let ctx = &mut tx_context::dummy(); app::mint(ctx).destroy(); ``` ### Do Not Use Abort Codes in `assert!` in Tests ```move // bad! may match application error codes by accident assert!(is_success, 0); // good! assert!(is_success); ``` ### Use `assert_eq!` Whenever Possible ```move // bad! old-style code assert!(result == "expected_value", 0); // good! will print both values if fails use std::unit_test::assert_eq; assert_eq!(result, expected_value); ``` ### Use "Black Hole" `destroy` Function ```move // bad! nft.destroy_for_testing(); app.destroy_for_testing(); // good! - no need to define special functions for cleanup use sui::test_utils::destroy; destroy(nft); destroy(app); ``` ## Comments ### Doc Comments Start With `///` ```move // bad! tooling doesn't support JavaDoc-style comments /** * Cool method * @param ... */ public fun do_something() { /* ... */ } // good! will be rendered as a doc comment in docgen and IDE's /// Cool method! public fun do_something() { /* ... */ } ``` ### Complex Logic? Leave a Comment `//` Being friendly and helping reviewers understand the code! ```move // good! // Note: can underflow if a value is smaller than 10. // TODO: add an `assert!` here let value = external_call(value, ctx); ``` --- # Using Move Registry Every package published on Sui is identified by its address. Addresses are precise but hard to work with: they are not memorable, they differ between networks, and they say nothing about what the package does or who published it. [Move Registry (MVR)](https://www.moveregistry.com) solves this by mapping human-readable names, like `@potatoes/date`, to published package addresses. With MVR, adding an external dependency to your package is a single command, and the toolchain resolves the name to the right address for the network you are building against. This guide walks through the full cycle of using an external package: finding it in the registry, adding it to the manifest, calling it in code, and testing the result. It assumes you have the MVR CLI installed - if you don't, refer to the [Install MVR](./../before-we-begin/install-move-registry-cli) section. ## Package Names MVR names follow the `@organization/package-name` pattern: the organization part is backed by a [SuiNS](https://suins.io) name, and the package name is registered under it by the organization's owner. A name points to a published package on a specific network, so the same name can resolve to different addresses on _mainnet_ and _testnet_. Additionally, since packages on Sui are versioned, a name can also carry a version suffix, such as `@potatoes/date/1`; without it, the name resolves to the latest version. In this guide we use the [`@potatoes/date`](https://www.moveregistry.com/package/@potatoes/date) package - a small library that converts a timestamp into a `Date` structure and prints it in ISO 8601, UTC (RFC 7231), or a custom format. ## Finding a Package Packages can be discovered on the [MVR website](https://www.moveregistry.com) or directly from the terminal with the `mvr search` command. The query can be a part of a package name or description, or an `@organization/` prefix to list everything published by one organization: ```bash $ mvr search "@potatoes/" ``` ```plaintext - @potatoes/codec # High performant encoding library for Sui, features: base64, base64url, urlencode, hex (base16) Networks: mainnet, testnet - @potatoes/date # Date and time printing / formatting tool, which supports RFC 7231 (UTC), ISO-8601 and custom # formats, as well as constructing the date from string Networks: mainnet, testnet ``` The `Networks` line is important: a name can only be resolved on a network where the package is published. If a dependency is only available on _mainnet_, a build against _testnet_ will fail to resolve it. ## Adding a Dependency To add a package to your project, run the `mvr add` command in the directory containing the package manifest: ```bash $ mvr add @potatoes/date ``` The command inserts a new record into the `[dependencies]` section of the `Move.toml`: ```toml [dependencies] date = { r.mvr = "@potatoes/date" } ``` Unlike a git dependency, which points to a repository and revision, this record contains only the registry name. The `r.` prefix stands for _external resolver_ - a plugin that the Sui CLI calls during build to turn the name into a concrete package address and source location. The `mvr` binary is that resolver, which is why it must be installed and available in the `PATH`. To pin the dependency to a specific version, add the version suffix to the name: ```toml [dependencies] date = { r.mvr = "@potatoes/date/1" } ``` ## Building the Package The dependency is fetched and resolved as a part of the regular build. During `sui move build`, the CLI calls the MVR resolver for each `r.mvr` record, using the currently active environment to pick the network: ```plaintext Output from mvr: │ [mvr] resolving: "@potatoes/date" on network: testnet Output from mvr: │ [mvr] resolving: "@potatoes/ascii/1" on network: testnet INCLUDING DEPENDENCY MoveStdlib INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY ascii INCLUDING DEPENDENCY date BUILDING postcard ``` Note the second resolver call: `@potatoes/date` itself depends on `@potatoes/ascii`, and the resolver fetches it automatically. Transitive MVR dependencies require no extra records in your manifest. The result of the resolution is recorded in the `Move.lock` file, pinning each dependency to an exact source revision per network. The lock file should be checked into version control, so that every build of your package uses the same dependency versions. ## Using the Dependency Once the dependency is added, its modules can be imported with a regular `use` statement. The address part of the path is the named address declared by the dependency itself - for `@potatoes/date` it is `date`, so the `date` module in it is imported as `date::date`. The example below defines a `Postcard` object which stores a human-readable timestamp of its creation, using the `Date` type from the dependency and the `Clock` object to get the current time: ```move /// Module: postcard module postcard::postcard; use date::date; use std::string::String; use sui::clock::Clock; /// A postcard which prints the date and time of its creation. public struct Postcard has key, store { id: UID, message: String, sent_at: String, } /// Create a new `Postcard` with a message and a human-readable timestamp. public fun new(message: String, clock: &Clock, ctx: &mut TxContext): Postcard { let date = date::from_clock(clock); Postcard { id: object::new(ctx), message, sent_at: date.to_utc_string(), } } ``` The `date::from_clock` function reads the timestamp from the `Clock` object (see [Epoch and Time](./../programmability/epoch-and-time)) and converts it into a `Date` value, which is then printed as a UTC string. The package also provides `to_iso_string` for ISO 8601 output and `format` for custom formats: ```move // Jan 1, 2025, 12:30:00 UTC let date = date::new(1735734600000); assert!(date.to_utc_string() == "Wed, 01 Jan 2025 12:30:00 GMT"); assert!(date.to_iso_string() == "2025-01-01T12:30:00.000Z"); assert!(date.format("DD MMM YYYY, HH:mm") == "01 Jan 2025, 12:30"); ``` ## Testing External dependencies take part in tests like any other code. The test below creates a `Clock` with a known timestamp and checks that the `Postcard` prints it correctly: ```move #[test] fun test_postcard() { let ctx = &mut tx_context::dummy(); let mut clock = sui::clock::create_for_testing(ctx); // set time to Jan 1, 2025, 12:30:00 UTC clock.set_for_testing(1735734600000); let postcard = new("Hello from Move!", &clock, ctx); assert!(postcard.sent_at == "Wed, 01 Jan 2025 12:30:00 GMT"); transfer::public_transfer(postcard, ctx.sender()); clock.destroy_for_testing(); } ``` Running `sui move test` resolves the dependencies, builds the package, and executes the test: ```plaintext Running Move unit tests [ PASS ] postcard::postcard::test_postcard Test result: OK. Total tests: 1; passed: 1; failed: 0 ``` ## Summary - Move Registry (MVR) maps human-readable names, like `@potatoes/date`, to published package addresses, per network. - The `mvr search` command (or the [MVR website](https://www.moveregistry.com)) helps discover packages and shows which networks they are published on. - The `mvr add` command adds a dependency record to the `Move.toml`; the name is resolved during build by the `mvr` binary, based on the active environment. - Resolved dependencies are pinned in the `Move.lock` file, which should be checked into version control. - Modules of the dependency are imported using the named address declared by the dependency itself. ## Further Reading - [Move Registry documentation](https://docs.suins.io/move-registry) - including how to publish and register your own package. - [Package Manifest](./../concepts/manifest) section of this book. --- # Appendix A: Glossary - Fast Path - term used to describe a transaction that does not involve shared objects, and can be executed without the need for consensus. - Parallel Execution - term used to describe the ability of the Sui runtime to execute transactions in parallel, including the ones that involve shared objects. - Internal Type - type that is defined within the module. Fields of this type can not be accessed from outside the module, and, in case of "key"-only abilities, can not be used in `public_*` transfer functions. ## Abilities - key - ability that allows the struct to be used as a key in the storage. On Sui, the key ability marks an object and requires the first field to be a `id: UID`. - store - ability that allows the struct to be stored inside other objects. This ability relaxes restrictions applied to internal structs, allowing `public_*` transfer functions to accept them as arguments. It also enables the object to be stored as a dynamic field. - copy - ability that allows the struct to be copied. On Sui, the `copy` ability conflicts with the `key` ability, and can not be used together with it. - drop - ability that allows the struct to be ignored or discarded. On Sui, the `drop` ability cannot be used together with the `key` ability, as objects are not allowed to be ignored. --- # Appendix B: Reserved Addresses Reserved addresses are special addresses that have a specific purpose on Sui. They stay the same between environments and are used for specific native operations. - `0x1` - address of the [Standard Library](./../move-basics/standard-library.md) (alias `std`) - `0x2` - address of the [Sui Framework](./../programmability/sui-framework.md) (alias `sui`) - `0x5` - address of the `SuiSystem` object - `0x6` - address of the system [`Clock` object](./../programmability/epoch-and-time.md) - `0x8` - address of the system [`Random` object](./../programmability/randomness.md) - `0xc` - address of the system [`CoinRegistry` object](./../programmability/balance-and-coin.md#currency-and-the-coin-registry) - `0xd` - address of the system `DisplayRegistry` object (see [Object Display](./../programmability/display.md)) - `0x403` - address of the `DenyList` system object - `0xacc` - address of the system `AccumulatorRoot` object --- # Appendix C: Transfer Functions ## Transfer Functions Comparison | Function | Public Function | End State | Permissions | | ------------------------- | ----------------------- | ------------- | ------------------------- | | [`transfer`][transfer] | `public_transfer` | Address Owned | Full | | [`share_object`][share] | `public_share_object` | Shared | Ref, Mut Ref, Delete | | [`freeze_object`][freeze] | `public_freeze_object` | Frozen | Ref | | [`party_transfer`][party] | `public_party_transfer` | Party | [See Party table](#party) | ## States Comparison | State | Description | | ------------- | --------------------------------------------------------- | | Address Owned | Object can be accessed fully by an address (or an object) | | Shared | Object can be referenced and deleted by anyone | | Frozen | Object can be accessed via immutable reference | | Party | Depends on the Party settings ([see Party table](#party)) | ## Party | Function | Description | | -------------- | -------------------------------------------- | | `single_owner` | Object has same permissions as Address Owned | [transfer]: https://docs.sui.io/references/framework/sui_sui/transfer#sui_transfer_transfer [share]: https://docs.sui.io/references/framework/sui_sui/transfer#sui_transfer_share_object [freeze]: https://docs.sui.io/references/framework/sui_sui/transfer#sui_transfer_freeze_object [party]: https://docs.sui.io/references/framework/sui_sui/transfer#sui_transfer_party_transfer --- # Appendix D: Publications This section lists publications related to Move and Sui. - [The Move Borrow Checker](https://arxiv.org/abs/2205.05181) by Sam Blackshear, John Mitchell, Todd Nowacki, Shaz Qadeer. - [Resources: A Safe Language Abstraction for Money](https://arxiv.org/abs/2004.05106) by Sam Blackshear, David L. Dill, Shaz Qadeer, Clark W. Barrett, John C. Mitchell, Oded Padon, Yoni Zohar. - [Robust Safety for Move](https://arxiv.org/abs/2110.05043) by Marco Patrignani, Sam Blackshear --- # Appendix E: Contributing To contribute to this book, please, submit a pull request to the [GitHub repository](https://github.com/MystenLabs/move-book). The repository contains the source files for the book, written in mdBook format. --- # Appendix F: Acknowledgements [The Rust Book](https://doc.rust-lang.org/book) has been a great inspiration for this book. I am personally grateful to the authors of the book, Steve Klabnik and Carol Nichols, for their work, as I have learned a lot from it. This book is a small tribute to their work and an attempt to bring a similar learning experience to the Move community. ## The Move Community Just as important is the Move community, which has shaped this book from its very first edition - written back when the language had no official documentation. Readers who asked questions, pointed out confusing passages, reported mistakes, and opened issues and pull requests made the book what it is today. The full list of contributors is available on [GitHub](https://github.com/MystenLabs/move-book/graphs/contributors), and the [Contributing](./contributing) appendix describes how to join them. Finally, none of this would exist without the people who created Move and continue to develop it today - the original Move team and the many contributors advancing the language. The [Move Reference](/reference) published alongside this book is adapted from their work. --- # The Move Reference --- # The Move Reference _by the Move contributors, adapted for Sui with contributions from the Move community_ Welcome to Move, a next generation language for secure asset programming. Its primary use case is in blockchain environments, where Move programs are used to construct state changes. Move allows developers to write programs that flexibly manage and transfer assets, while providing the security and protections against attacks on those assets. However, Move has been developed with use cases in mind outside a blockchain context as well. Move takes its cue from [Rust](https://www.rust-lang.org/) by using resource types with move (hence the name) semantics as an explicit representation of digital assets, such as currency. --- # Modules **Modules** are the core program unit that define types along with functions that operate on these types. Struct types define the schema of Move's [storage](./abilities#key), and module functions define the rules interacting with values of those types. While modules themselves are also stored in storage, they are not accessible from within a Move program. In a blockchain environment, the modules are stored on chain in a process typically referred to as "publishing". After being published, [`entry`](./functions#entry-modifier) and [`public`](./functions#visibility) functions can be invoked according to the rules of that particular Move instance. ## Syntax A module has the following syntax: ```text module
:: { ( | | | )* } ``` where `
` is a valid [address](./primitive-types/address) specifying the module's package. For example: ```move module 0::test; use std::debug; const ONE: u64 = 1; public struct Example has copy, drop { i: u64 } public fun print(x: u64) { let sum = x + ONE; let example = Example { i: sum }; debug::print(&sum) } ``` ## Names The `module test_addr::test` part specifies that the module `test` will be published under the numerical [address](./primitive-types/address) value assigned for the name `test_addr` in the [package settings](./packages). Modules should normally be declared using [named addresses](./primitive-types/address) (as opposed to using the numerical value directly). For example: ```move module test_addr::test; use std::debug; use test_addr::another_test; public struct Example has copy, drop { a: address } public fun print() { let example = Example { a: @test_addr }; debug::print(&example) } ``` These named addresses commonly match the name of the [package](./packages). Because named addresses only exist at the source language level and during compilation, named addresses will be fully substituted for their value at the bytecode level. For example if we had the following code: ```move fun example() { my_addr::m::foo(@my_addr); } ``` and we compiled it with `my_addr` set to `0xC0FFEE`, then it would be operationally equivalent to the following: ```move fun example() { 0xC0FFEE::m::foo(@0xC0FFEE); } ``` While at the source level these two different accesses are equivalent, it is a best practice to always use the named address and not the numerical value assigned to that address. Module names can start with a lowercase letter from `a` to `z` or an uppercase letter from `A` to `Z`. After the first character, module names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. ```move module a::my_module {} module a::foo_bar_42 {} ``` Typically, module names start with a lowercase letter. A module named `my_module` should be stored in a source file named `my_module.move`. ## Members All members inside a `module` block can appear in any order. Fundamentally, a module is a collection of [`types`](./structs) and [`functions`](./functions). The [`use`](./uses) keyword refers to members from other modules. The [`const`](./constants) keyword defines constants that can be used in the functions of a module. The [`friend`](./friends) syntax is a deprecated concept for specifying a list of trusted modules. The concept has been superseded by [`public(package)`](./functions#visibility) --- # Primitive Types The primitive types are the basic building blocks of the language. These primitive types can be used on their own or can be used to build more complex, user-defined types, e.g. in a [`struct`](./structs). - [Integers](./primitive-types/integers) - [Bool](./primitive-types/bool) - [Address](./primitive-types/address) - [Vector](./primitive-types/vector) These primitive types are used in conjunction with other types - [References](./primitive-types/references) - [Tuples and Unit](./primitive-types/tuples) --- # Integers Move supports six unsigned integer types: `u8`, `u16`, `u32`, `u64`, `u128`, and `u256`. Values of these types range from 0 to a maximum that depends on the size of the type. | Type | Value Range | | -------------------------------- | ------------------------ | | Unsigned 8-bit integer, `u8` | 0 to 28 - 1 | | Unsigned 16-bit integer, `u16` | 0 to 216 - 1 | | Unsigned 32-bit integer, `u32` | 0 to 232 - 1 | | Unsigned 64-bit integer, `u64` | 0 to 264 - 1 | | Unsigned 128-bit integer, `u128` | 0 to 2128 - 1 | | Unsigned 256-bit integer, `u256` | 0 to 2256 - 1 | ## Literals Literal values for these types are specified either as a sequence of digits (e.g.,`112`) or as hex literals, e.g., `0xFF`. The type of the literal can optionally be added as a suffix, e.g., `112u8`. If the type is not specified, the compiler will try to infer the type from the context where the literal is used. If the type cannot be inferred, it is assumed to be `u64`. Number literals can be separated by underscores for grouping and readability. (e.g.,`1_234_5678`, `1_000u128`, `0xAB_CD_12_35`). If a literal is too large for its specified (or inferred) size range, an error is reported. ### Examples ```move // literals with explicit annotations; let explicit_u8 = 1u8; let explicit_u16 = 1u16; let explicit_u32 = 1u32; let explicit_u64 = 2u64; let explicit_u128 = 3u128; let explicit_u256 = 1u256; let explicit_u64_underscored = 154_322_973u64; // literals with simple inference let simple_u8: u8 = 1; let simple_u16: u16 = 1; let simple_u32: u32 = 1; let simple_u64: u64 = 2; let simple_u128: u128 = 3; let simple_u256: u256 = 1; // literals with more complex inference let complex_u8 = 1; // inferred: u8 // right hand argument to shift must be u8 let _unused = 10 << complex_u8; let x: u8 = 38; let complex_u8 = 2; // inferred: u8 // arguments to `+` must have the same type let _unused = x + complex_u8; let complex_u128 = 133_876; // inferred: u128 // inferred from function argument type function_that_takes_u128(complex_u128); // literals can be written in hex let hex_u8: u8 = 0x1; let hex_u16: u16 = 0x1BAE; let hex_u32: u32 = 0xDEAD80; let hex_u64: u64 = 0xCAFE; let hex_u128: u128 = 0xDEADBEEF; let hex_u256: u256 = 0x1123_456A_BCDE_F; ``` ## Operations ### Arithmetic Each of these types supports the same set of checked arithmetic operations. For all of these operations, both arguments (the left and right side operands) _must_ be of the same type. If you need to operate over values of different types, you will need to first perform a [cast](#casting). Similarly, if you expect the result of the operation to be too large for the integer type, perform a [cast](#casting) to a larger size before performing the operation. All arithmetic operations abort instead of behaving in a way that mathematical integers would not (e.g., overflow, underflow, divide-by-zero). | Syntax | Operation | Aborts If | | ------ | ------------------- | ---------------------------------------- | | `+` | addition | Result is too large for the integer type | | `-` | subtraction | Result is less than zero | | `*` | multiplication | Result is too large for the integer type | | `%` | modular division | The divisor is `0` | | `/` | truncating division | The divisor is `0` | ### Bitwise The integer types support the following bitwise operations that treat each number as a series of individual bits, either 0 or 1, instead of as numerical integer values. Bitwise operations do not abort. | Syntax | Operation | Description | | ------------------- | ----------- | ----------------------------------------------------- | | `&` | bitwise and | Performs a boolean and for each bit pairwise | | | | bitwise or | Performs a boolean or for each bit pairwise | | `^` | bitwise xor | Performs a boolean exclusive or for each bit pairwise | ### Bit Shifts Similar to the bitwise operations, each integer type supports bit shifts. But unlike the other operations, the right hand side operand (how many bits to shift by) must _always_ be a `u8` and need not match the left side operand (the number you are shifting). Bit shifts can abort if the number of bits to shift by is greater than or equal to `8`, `16`, `32`, `64`, `128` or `256` for `u8`, `u16`, `u32`, `u64`, `u128` and `u256` respectively. | Syntax | Operation | Aborts if | | ------ | ----------- | ----------------------------------------------------------------------- | | `<<` | shift left | Number of bits to shift by is greater than the size of the integer type | | `>>` | shift right | Number of bits to shift by is greater than the size of the integer type | ### Comparisons Integer types are the _only_ types in Move that can use the comparison operators. Both arguments need to be of the same type. If you need to compare integers of different types, you must [cast](#casting) one of them first. Comparison operations do not abort. | Syntax | Operation | | ------ | ------------------------ | | `<` | less than | | `>` | greater than | | `<=` | less than or equal to | | `>=` | greater than or equal to | ### Equality Like all types with [`drop`](./../abilities), all integer types support the ["equal"](./../equality) and ["not equal"](./../equality) operations. Both arguments need to be of the same type. If you need to compare integers of different types, you must [cast](#casting) one of them first. Equality operations do not abort. | Syntax | Operation | | ------ | --------- | | `==` | equal | | `!=` | not equal | For more details see the section on [equality](./../equality) ## Casting Integer types of one size can be cast to integer types of another size. Integers are the only types in Move that support casting. Casts _do not_ truncate. Casting aborts if the result is too large for the specified type. | Syntax | Operation | Aborts if | | ---------- | ---------------------------------------------------- | -------------------------------------- | | `(e as T)` | Cast integer expression `e` into an integer type `T` | `e` is too large to represent as a `T` | Here, the type of `e` must be `8`, `16`, `32`, `64`, `128` or `256` and `T` must be `u8`, `u16`, `u32`, `u64`, `u128`, or `u256`. For example: - `(x as u8)` - `(y as u16)` - `(873u16 as u32)` - `(2u8 as u64)` - `(1 + 3 as u128)` - `(4/2 + 12345 as u256)` ## Ownership As with the other scalar values built-in to the language, integer values are implicitly copyable, meaning they can be copied without an explicit instruction such as [`copy`](./../variables#move-and-copy). --- # Bool `bool` is Move's primitive type for boolean `true` and `false` values. ## Literals Literals for `bool` are either `true` or `false`. ## Operations ### Logical `bool` supports three logical operations: | Syntax | Description | Equivalent Expression | | ------------------------- | ---------------------------- | ------------------------------------------------------------------- | | `&&` | short-circuiting logical and | `p && q` is equivalent to `if (p) q else false` | | || | short-circuiting logical or | p || q is equivalent to `if (p) true else q` | | `!` | logical negation | `!p` is equivalent to `if (p) false else true` | ### Control Flow `bool` values are used in several of Move's control-flow constructs: - [`if (bool) { ... }`](./../control-flow/conditionals) - [`while (bool) { .. }`](./../control-flow/loops) - [`assert!(bool, u64)`](./../abort-and-assert) ## Ownership As with the other scalar values built-in to the language, boolean values are implicitly copyable, meaning they can be copied without an explicit instruction such as [`copy`](.././variables#move-and-copy). --- # Address `address` is a built-in type in Move that is used to represent locations (sometimes called accounts) in storage. An `address` value is a 256-bit (32 byte) identifier. Move uses addresses to differentiate packages of [modules](./../modules), where each package has its own address and modules. Specific deployments of Move might also use the `address` value for [storage](./../abilities#key) operations. > For Sui, `address` is used to represent "accounts", and also objects via strong type wrappers > (with `sui::object::UID` and `sui::object::ID`). Although an `address` is a 256 bit integer under the hood, Move addresses are intentionally opaque---they cannot be created from integers, they do not support arithmetic operations, and they cannot be modified. Specific deployments of Move might have `native` functions to enable some of these operations (e.g., creating an `address` from bytes `vector`), but these are not part of the Move language itself. While there are runtime address values (values of type `address`), they _cannot_ be used to access modules at runtime. ## Addresses and Their Syntax Addresses come in two flavors, named or numerical. The syntax for a named address follows the same rules for any named identifier in Move. The syntax of a numerical address is not restricted to hex-encoded values, and any valid [`u256` numerical value](./integers) can be used as an address value, e.g., `42`, `0xCAFE`, and `10_000` are all valid numerical address literals. To distinguish when an address is being used in an expression context or not, the syntax when using an address differs depending on the context where it's used: - When an address is used as an expression, the address must be prefixed by the `@` character, i.e., [`@`](./integers) or `@`. - Outside of expression contexts, the address may be written without the leading `@` character, i.e., [``](./integers) or ``. In general, you can think of `@` as an operator that takes an address from being a namespace item to being an expression item. ## Named Addresses Named addresses are a feature that allow identifiers to be used in place of numerical values in any spot where addresses are used, and not just at the value level. Named addresses are declared and bound as top level elements (outside of modules and scripts) in Move packages, or passed as arguments to the Move compiler. Named addresses only exist at the source language level and will be fully substituted for their value at the bytecode level. Because of this, modules and module members should be accessed through the module's named address and not through the numerical value assigned to the named address during compilation. So while `use my_addr::foo` is equivalent to `use 0x2::foo` (if `my_addr` is assigned `0x2`), it is a best practice to always use the `my_addr` name. ### Examples ```move // shorthand for // 0x0000000000000000000000000000000000000000000000000000000000000001 let a1: address = @0x1; // shorthand for // 0x0000000000000000000000000000000000000000000000000000000000000042 let a2: address = @0x42; // shorthand for // 0x00000000000000000000000000000000000000000000000000000000DEADBEEF let a3: address = @0xDEADBEEF; // shorthand for // 0x000000000000000000000000000000000000000000000000000000000000000A let a4: address = @0x0000000000000000000000000000000A; // Assigns `a5` the value of the named address `std` let a5: address = @std; // Any valid numerical value can be used as an address let a6: address = @66; let a7: address = @42_000; module 66::some_module { // Not in expression context, so no @ needed use 0x1::other_module; // Not in expression context so no @ needed use std::vector; // Can use a named address as a namespace item ... } module std::other_module { // Can use a named address when declaring a module ... } ``` --- # Vector `vector` is the only primitive collection type provided by Move. A `vector` is a homogeneous collection of `T`'s that can grow or shrink by pushing/popping values off the "end". A `vector` can be instantiated with any type `T`. For example, `vector`, `vector
`, `vector<0x42::my_module::MyData>`, and `vector>` are all valid vector types. ## Literals ### General `vector` Literals Vectors of any type can be created with `vector` literals. | Syntax | Type | Description | | --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------ | | `vector[]` | `vector[]: vector` where `T` is any single, non-reference type | An empty vector | | `vector[e1, ..., en]` | `vector[e1, ..., en]: vector` where `e_i: T` s.t. `0 < i <= n` and `n > 0` | A vector with `n` elements (of length `n`) | In these cases, the type of the `vector` is inferred, either from the element type or from the vector's usage. If the type cannot be inferred, or simply for added clarity, the type can be specified explicitly: ```move vector[]: vector vector[e1, ..., en]: vector ``` #### Example Vector Literals ```move (vector[]: vector); (vector[0u8, 1u8, 2u8]: vector); (vector[]: vector); (vector
[@0x42, @0x100]: vector
); ``` ### `vector` literals A common use-case for vectors in Move is to represent "byte arrays", which are represented with `vector`. These values are often used for cryptographic purposes, such as a public key or a hash result. These values are so common that specific syntax is provided to make the values more readable, as opposed to having to use `vector[]` where each individual `u8` value is specified in numeric form. There are currently two supported types of `vector` literals, _byte strings_ and _hex strings_. #### Byte Strings Byte strings are quoted string literals prefixed by a `b`, e.g. `b"Hello!\n"`. These are ASCII encoded strings that allow for escape sequences. Currently, the supported escape sequences are: | Escape Sequence | Description | | --------------- | ---------------------------------------------- | | `\n` | New line (or Line feed) | | `\r` | Carriage return | | `\t` | Tab | | `\\` | Backslash | | `\0` | Null | | `\"` | Quote | | `\xHH` | Hex escape, inserts the hex byte sequence `HH` | #### Hex Strings Hex strings are quoted string literals prefixed by a `x`, e.g. `x"48656C6C6F210A"`. Each byte pair, ranging from `00` to `FF`, is interpreted as hex encoded `u8` value. So each byte pair corresponds to a single entry in the resulting `vector`. #### Example String Literals ```move fun byte_and_hex_strings() { assert!(b"" == x"", 0); assert!(b"Hello!\n" == x"48656C6C6F210A", 1); assert!(b"\x48\x65\x6C\x6C\x6F\x21\x0A" == x"48656C6C6F210A", 2); assert!( b"\"Hello\tworld!\"\n \r \\Null=\0" == x"2248656C6C6F09776F726C6421220A200D205C4E756C6C3D00", 3 ); } ``` ## Operations `vector` supports the following operations via the `std::vector` module in the Move standard library: | Function | Description | Aborts? | | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `vector::empty(): vector` | Create an empty vector that can store values of type `T` | Never | | `vector::singleton(t: T): vector` | Create a vector of size 1 containing `t` | Never | | `vector::push_back(v: &mut vector, t: T)` | Add `t` to the end of `v` | Never | | `vector::pop_back(v: &mut vector): T` | Remove and return the last element in `v` | If `v` is empty | | `vector::borrow(v: &vector, i: u64): &T` | Return an immutable reference to the `T` at index `i` | If `i` is not in bounds | | `vector::borrow_mut(v: &mut vector, i: u64): &mut T` | Return a mutable reference to the `T` at index `i` | If `i` is not in bounds | | `vector::destroy_empty(v: vector)` | Delete `v` | If `v` is not empty | | `vector::append(v1: &mut vector, v2: vector)` | Add the elements in `v2` to the end of `v1` | Never | | `vector::contains(v: &vector, e: &T): bool` | Return true if `e` is in the vector `v`. Otherwise, returns false | Never | | `vector::swap(v: &mut vector, i: u64, j: u64)` | Swaps the elements at the `i`th and `j`th indices in the vector `v` | If `i` or `j` is out of bounds | | `vector::reverse(v: &mut vector)` | Reverses the order of the elements in the vector `v` in place | Never | | `vector::index_of(v: &vector, e: &T): (bool, u64)` | Return `(true, i)` if `e` is in the vector `v` at index `i`. Otherwise, returns `(false, 0)` | Never | | `vector::remove(v: &mut vector, i: u64): T` | Remove the `i`th element of the vector `v`, shifting all subsequent elements. This is O(n) and preserves ordering of elements in the vector | If `i` is out of bounds | | `vector::swap_remove(v: &mut vector, i: u64): T` | Swap the `i`th element of the vector `v` with the last element and then pop the element, This is O(1), but does not preserve ordering of elements in the vector | If `i` is out of bounds | More operations may be added over time. ## Example ```move use std::vector; let mut v = vector::empty(); vector::push_back(&mut v, 5); vector::push_back(&mut v, 6); assert!(*vector::borrow(&v, 0) == 5, 42); assert!(*vector::borrow(&v, 1) == 6, 42); assert!(vector::pop_back(&mut v) == 6, 42); assert!(vector::pop_back(&mut v) == 5, 42); ``` ## Destroying and copying `vector`s Some behaviors of `vector` depend on the abilities of the element type, `T`. For example, vectors containing elements that do not have `drop` cannot be implicitly discarded like `v` in the example above--they must be explicitly destroyed with `vector::destroy_empty`. Note that `vector::destroy_empty` will abort at runtime unless `vec` contains zero elements: ```move fun destroy_any_vector(vec: vector) { vector::destroy_empty(vec) // deleting this line will cause a compiler error } ``` But no error would occur for dropping a vector that contains elements with `drop`: ```move fun destroy_droppable_vector(vec: vector) { // valid! // nothing needs to be done explicitly to destroy the vector } ``` Similarly, vectors cannot be copied unless the element type has `copy`. In other words, a `vector` has `copy` if and only if `T` has `copy`. Note that it will be implicitly copied if needed: ```move let x = vector[10]; let y = x; // implicit copy let z = x; (y, z) ``` Keep in mind, copies of large vectors can be expensive. If this is a concern, annotating the `intended` usage can prevent accidental copies. For example, ```move let x = vector[10]; let y = move x; let z = x; // ERROR! x has been moved (y, z) ``` For more details see the sections on [type abilities](./../abilities) and [generics](./../generics). ## Ownership As mentioned [above](#destroying-and-copying-vectors), `vector` values can be copied only if the elements can be copied. In that case, the copy can be done via a [`copy`](./../variables#move-and-copy) or a [dereference `*`](./references#reading-and-writing-through-references). --- # References Move has two types of references: immutable `&` and mutable `&mut`. Immutable references are read only, and cannot modify the underlying value (or any of its fields). Mutable references allow for modifications via a write through that reference. Move's type system enforces an ownership discipline that prevents reference errors. ## Reference Operators Move provides operators for creating and extending references as well as converting a mutable reference to an immutable one. Here and elsewhere, we use the notation `e: T` for "expression `e` has type `T`". | Syntax | Type | Description | | ----------- | ----------------------------------------------------- | -------------------------------------------------------------- | | `&e` | `&T` where `e: T` and `T` is a non-reference type | Create an immutable reference to `e` | | `&mut e` | `&mut T` where `e: T` and `T` is a non-reference type | Create a mutable reference to `e`. | | `&e.f` | `&T` where `e.f: T` | Create an immutable reference to field `f` of struct `e`. | | `&mut e.f` | `&mut T` where `e.f: T` | Create a mutable reference to field `f` of struct`e`. | | `freeze(e)` | `&T` where `e: &mut T` | Convert the mutable reference `e` into an immutable reference. | The `&e.f` and `&mut e.f` operators can be used both to create a new reference into a struct or to extend an existing reference: ```move let s = S { f: 10 }; let f_ref1: &u64 = &s.f; // works let s_ref: &S = &s; let f_ref2: &u64 = &s_ref.f // also works ``` A reference expression with multiple fields works as long as both structs are in the same module: ```move public struct A { b: B } public struct B { c : u64 } fun f(a: &A): &u64 { &a.b.c } ``` Finally, note that references to references are not allowed: ```move let x = 7; let y: &u64 = &x; // highlight-error let z: &&u64 = &y; // ERROR! will not compile ``` ## Reading and Writing Through References Both mutable and immutable references can be read to produce a copy of the referenced value. Only mutable references can be written. A write `*x = v` discards the value previously stored in `x` and updates it with `v`. Both operations use the C-like `*` syntax. However, note that a read is an expression, whereas a write is a mutation that must occur on the left hand side of an equals. | Syntax | Type | Description | | ---------- | ----------------------------------- | ----------------------------------- | | `*e` | `T` where `e` is `&T` or `&mut T` | Read the value pointed to by `e` | | `*e1 = e2` | `()` where `e1: &mut T` and `e2: T` | Update the value in `e1` with `e2`. | In order for a reference to be read, the underlying type must have the [`copy` ability](../abilities) as reading the reference creates a new copy of the value. This rule prevents the copying of assets: ```move fun copy_coin_via_ref_bad(c: Coin) { let c_ref = &c; // highlight-error let counterfeit: Coin = *c_ref; // not allowed! pay(c); pay(counterfeit); } ``` Dually: in order for a reference to be written to, the underlying type must have the [`drop` ability](../abilities) as writing to the reference will discard (or "drop") the old value. This rule prevents the destruction of resource values: ```move fun destroy_coin_via_ref_bad(mut ten_coins: Coin, c: Coin) { let ref = &mut ten_coins; // highlight-error *ref = c; // ERROR! not allowed--would destroy 10 coins! } ``` ## `freeze` inference A mutable reference can be used in a context where an immutable reference is expected: ```move let mut x = 7; let y: &u64 = &mut x; ``` This works because the under the hood, the compiler inserts `freeze` instructions where they are needed. Here are a few more examples of `freeze` inference in action: ```move fun takes_immut_returns_immut(x: &u64): &u64 { x } // freeze inference on return value fun takes_mut_returns_immut(x: &mut u64): &u64 { x } fun expression_examples() { let mut x = 0; let mut y = 0; takes_immut_returns_immut(&x); // no inference takes_immut_returns_immut(&mut x); // inferred freeze(&mut x) takes_mut_returns_immut(&mut x); // no inference assert!(&x == &mut y, 42); // inferred freeze(&mut y) } fun assignment_examples() { let x = 0; let y = 0; let imm_ref: &u64 = &x; imm_ref = &x; // no inference imm_ref = &mut y; // inferred freeze(&mut y) } ``` ### Subtyping With this `freeze` inference, the Move type checker can view `&mut T` as a subtype of `&T`. As shown above, this means that anywhere for any expression where a `&T` value is used, a `&mut T` value can also be used. This terminology is used in error messages to concisely indicate that a `&mut T` was needed where a `&T` was supplied. For example ```move module a::example { fun read_and_assign(store: &mut u64, new_value: &u64) { *store = *new_value } fun subtype_examples() { let mut x: &u64 = &0; let mut y: &mut u64 = &mut 1; x = &mut 1; // valid // highlight-error y = &2; // ERROR! invalid! read_and_assign(y, x); // valid // highlight-error read_and_assign(x, y); // ERROR! invalid! } } ``` will yield the following error messages ```text error: ┌── example.move:11:9 ─── │ 12 │ y = &2; // invalid! │ ^ Invalid assignment to local 'y' · 12 │ y = &2; // invalid! │ -- The type: '&{integer}' · 9 │ let mut y: &mut u64 = &mut 1; │ -------- Is not a subtype of: '&mut u64' │ error: ┌── example.move:14:9 ─── │ 15 │ read_and_assign(x, y); // invalid! │ ^^^^^^^^^^^^^^^^^^^^^ Invalid call of 'a::example::read_and_assign'. Invalid argument for parameter 'store' · 8 │ let mut x: &u64 = &0; │ ---- The type: '&u64' · 3 │ fun read_and_assign(store: &mut u64, new_value: &u64) { │ -------- Is not a subtype of: '&mut u64' │ ``` The only other types that currently have subtyping are [tuples](./tuples) ## Ownership Both mutable and immutable references can always be copied and extended _even if there are existing copies or extensions of the same reference_: ```move fun reference_copies(s: &mut S) { let s_copy1 = s; // ok let s_extension = &mut s.f; // also ok let s_copy2 = s; // still ok ... } ``` This might be surprising for programmers familiar with Rust's ownership system, which would reject the code above. Move's type system is more permissive in its treatment of [copies](./../variables#move-and-copy), but equally strict in ensuring unique ownership of mutable references before writes. ### References Cannot Be Stored References and tuples are the _only_ types that cannot be stored as a field value of structs, which also means that they cannot exist in storage or [objects](./../abilities/object). All references created during program execution will be destroyed when a Move program terminates; they are entirely ephemeral. This also applies to all types without the `store` ability: any value of a non-`store` type must be destroyed before the program terminates. This is another difference between Move and Rust, which allows references to be stored inside of structs. One could imagine a fancier, more expressive, type system that would allow references to be stored in structs. We could allow references inside of structs that do not have the `store` [ability](./../abilities), but the core difficulty is that Move has a fairly complex system for tracking static reference safety. This aspect of the type system would also have to be extended to support storing references inside of structs. In short, Move's reference safety system would have to expand to support stored references, and it is something we are keeping an eye on as the language evolves. --- # Tuples and Unit Move does not fully support tuples as one might expect coming from another language with them as a [first-class value](https://en.wikipedia.org/wiki/First-class_citizen). However, in order to support multiple return values, Move has tuple-like expressions. These expressions do not result in a concrete value at runtime (there are no tuples in the bytecode), and as a result they are very limited: - They can only appear in expressions (usually in the return position for a function). - They cannot be bound to local variables. - They cannot be stored in structs. - Tuple types cannot be used to instantiate generics. Similarly, [unit `()`](https://en.wikipedia.org/wiki/Unit_type) is a type created by the Move source language in order to be expression based. The unit value `()` does not result in any runtime value. We can consider unit`()` to be an empty tuple, and any restrictions that apply to tuples also apply to unit. It might feel weird to have tuples in the language at all given these restrictions. But one of the most common use cases for tuples in other languages is for functions to allow functions to return multiple values. Some languages work around this by forcing the users to write structs that contain the multiple return values. However in Move, you cannot put references inside of [structs](./../structs). This required Move to support multiple return values. These multiple return values are all pushed on the stack at the bytecode level. At the source level, these multiple return values are represented using tuples. ## Literals Tuples are created by a comma separated list of expressions inside of parentheses. | Syntax | Type | Description | | --------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------ | | `()` | `(): ()` | Unit, the empty tuple, or the tuple of arity 0 | | `(e1, ..., en)` | `(e1, ..., en): (T1, ..., Tn)` where `e_i: Ti` s.t. `0 < i <= n` and `n > 0` | A `n`-tuple, a tuple of arity `n`, a tuple with `n` elements | Note that `(e)` does not have type `(e): (t)`, in other words there is no tuple with one element. If there is only a single element inside of the parentheses, the parentheses are only used for disambiguation and do not carry any other special meaning. Sometimes, tuples with two elements are called "pairs" and tuples with three elements are called "triples." ### Examples ```move module 0::example; // all 3 of these functions are equivalent // when no return type is provided, it is assumed to be `()` fun returns_unit_1() { } // there is an implicit () value in empty expression blocks fun returns_unit_2(): () { } // explicit version of `returns_unit_1` and `returns_unit_2` fun returns_unit_3(): () { () } fun returns_3_values(): (u64, bool, address) { (0, false, @0x42) } fun returns_4_values(x: &u64): (&u64, u8, u128, vector) { (x, 0, 1, b"foobar") } ``` ## Operations The only operation that can be done on tuples currently is destructuring. ### Destructuring For tuples of any size, they can be destructured in either a `let` binding or in an assignment. For example: ```move module 0x42::example; // all 3 of these functions are equivalent fun returns_unit() {} fun returns_2_values(): (bool, bool) { (true, false) } fun returns_4_values(x: &u64): (&u64, u8, u128, vector) { (x, 0, 1, b"foobar") } fun examples(cond: bool) { let () = (); let (mut x, mut y): (u8, u64) = (0, 1); let (mut a, mut b, mut c, mut d) = (@0x0, 0, false, b""); () = (); (x, y) = if (cond) (1, 2) else (3, 4); (a, b, c, d) = (@0x1, 1, true, b"1"); } fun examples_with_function_calls() { let () = returns_unit(); let (mut x, mut y): (bool, bool) = returns_2_values(); let (mut a, mut b, mut c, mut d) = returns_4_values(&0); () = returns_unit(); (x, y) = returns_2_values(); (a, b, c, d) = returns_4_values(&1); } ``` For more details, see [Move Variables](./../variables). ## Subtyping Along with references, tuples are the only types that have [subtyping](https://en.wikipedia.org/wiki/Subtyping) in Move. Tuples have subtyping only in the sense that subtype with references (in a covariant way). For example: ```move let x: &u64 = &0; let y: &mut u64 = &mut 1; // (&u64, &mut u64) is a subtype of (&u64, &u64) // since &mut u64 is a subtype of &u64 let (a, b): (&u64, &u64) = (x, y); // (&mut u64, &mut u64) is a subtype of (&u64, &u64) // since &mut u64 is a subtype of &u64 let (c, d): (&u64, &u64) = (y, y); // highlight-error-start // ERROR! (&u64, &mut u64) is NOT a subtype of (&mut u64, &mut u64) // since &u64 is NOT a subtype of &mut u64 let (e, f): (&mut u64, &mut u64) = (x, y); // highlight-error-end ``` ## Ownership As mentioned above, tuple values don't really exist at runtime. And currently they cannot be stored into local variables because of this (but it is likely that this feature will come at some point in the future). As such, tuples can only be moved currently, as copying them would require putting them into a local variable first. --- # Local Variables and Scope Local variables in Move are lexically (statically) scoped. New variables are introduced with the keyword `let`, which will shadow any previous local with the same name. Locals marked as `mut` are mutable and can be updated both directly and via a mutable reference. ## Declaring Local Variables ### `let` bindings Move programs use `let` to bind variable names to values: ```move let x = 1; let y = x + x; ``` `let` can also be used without binding a value to the local. ```move let x; ``` The local can then be assigned a value later. ```move let x; if (cond) { x = 1 } else { x = 0 } ``` This can be very helpful when trying to extract a value from a loop when a default value cannot be provided. ```move let x; let mut i = 0; loop { let (res, cond) = foo(i); if (!cond) { x = res; break }; i = i + 1; } ``` To modify a local variable _after_ it is assigned, or to borrow it mutably (`&mut`), it must be declared as `mut`. ```move let mut x = 0; if (cond) x = x + 1; foo(&mut x); ``` For more details see the section on [assignments](#assignments) below. ### Variables must be assigned before use Move's type system prevents a local variable from being used before it has been assigned. ```move let x; // highlight-error x + x // ERROR! x is used before being assigned ``` ```move let x; if (cond) x = 0; // highlight-error x + x // ERROR! x does not have a value in all cases ``` ```move let x; while (cond) x = 0; // highlight-error x + x // ERROR! x does not have a value in all cases ``` ### Valid variable names Variable names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, and digits `0` to `9`. Variable names must start with either an underscore `_` or a letter `a` through `z`. They _cannot_ start with uppercase letters. ```move // all valid let x = e; let _x = e; let _A = e; let x0 = e; let xA = e; let foobar_123 = e; // all invalid // highlight-error-start let X = e; // ERROR! let Foo = e; // ERROR! // highlight-error-end ``` ### Type annotations The type of a local variable can almost always be inferred by Move's type system. However, Move allows explicit type annotations that can be useful for readability, clarity, or debuggability. The syntax for adding a type annotation is: ```move let x: T = e; // "Variable x of type T is initialized to expression e" ``` Some examples of explicit type annotations: ```move module 0::example; public struct S { f: u64, g: u64 } fun annotated() { let u: u8 = 0; let b: vector = b"hello"; let a: address = @0x0; let (x, y): (&u64, &mut u64) = (&0, &mut 1); let S { f, g: f2 }: S = S { f: 0, g: 1 }; } ``` Note that the type annotations must always be to the right of the pattern: ```move // highlight-error-start // ERROR! should be let (x, y): (&u64, &mut u64) = ... let (x: &u64, y: &mut u64) = (&0, &mut 1); // highlight-error-end ``` ### When annotations are necessary In some cases, a local type annotation is required if the type system cannot infer the type. This commonly occurs when the type argument for a generic type cannot be inferred. For example: ```move // highlight-error-start let _v1 = vector[]; // ERROR! // ^^^^^^^^ Could not infer this type. Try adding an annotation // highlight-error-end let v2: vector = vector[]; // no error ``` In a rarer case, the type system might not be able to infer a type for divergent code (where all the following code is unreachable). Both [`return`](./functions#return-expression) and [`abort`](./abort-and-assert) are expressions and can have any type. A [`loop`](./control-flow/loops) has type `()` if it has a `break` (or `T` if has a `break e` where `e: T`), but if there is no break out of the `loop`, it could have any type. If these types cannot be inferred, a type annotation is required. For example, this code: ```move let a: u8 = return (); let b: bool = abort 0; let c: signer = loop (); // highlight-error-start let x = return (); // ERROR! // ^ Could not infer this type. Try adding an annotation let y = abort 0; // ERROR! // ^ Could not infer this type. Try adding an annotation let z = loop (); // ERROR! // ^ Could not infer this type. Try adding an annotation // highlight-error-end ``` Adding type annotations to this code will expose other errors about dead code or unused local variables, but the example is still helpful for understanding this problem. ### Multiple declarations with tuples `let` can introduce more than one local at a time using tuples. The locals declared inside the parenthesis are initialized to the corresponding values from the tuple. ```move let () = (); let (x0, x1) = (0, 1); let (y0, y1, y2) = (0, 1, 2); let (z0, z1, z2, z3) = (0, 1, 2, 3); ``` The type of the expression must match the arity of the tuple pattern exactly. ```move // highlight-error let (x, y) = (0, 1, 2); // ERROR! // highlight-error let (x, y, z, q) = (0, 1, 2); // ERROR! ``` You cannot declare more than one local with the same name in a single `let`. ```move // highlight-error let (x, x) = 0; // ERROR! ``` The mutability of the local variables declared can be mixed. ```move let (mut x, y) = (0, 1); x = 1; ``` ### Multiple declarations with structs `let` can also introduce more than one local variables at a time when destructuring (or matching against) a struct. In this form, the `let` creates a set of local variables that are initialized to the values of the fields from a struct. The syntax looks like this: ```move public struct T { f1: u64, f2: u64 } ``` ```move let T { f1: local1, f2: local2 } = T { f1: 1, f2: 2 }; // local1: u64 // local2: u64 ``` Similarly for positional structs ```move public struct P(u64, u64) ``` and ```move let P (local1, local2) = P ( 1, 2 ); // local1: u64 // local2: u64 ``` Here is a more complicated example: ```move module 0::example; public struct X(u64) public struct Y { x1: X, x2: X } fun new_x(): X { X(1) } fun example() { let Y { x1: X(f), x2 } = Y { x1: new_x(), x2: new_x() }; assert!(f + x2.0 == 2, 42); let Y { x1: X(f1), x2: X(f2) } = Y { x1: new_x(), x2: new_x() }; assert!(f1 + f2 == 2, 42); // `struct X` without `drop` ability and needs to be destroyed manually let X(_) = x2; } ``` Fields of structs can serve double duty, identifying the field to bind _and_ the name of the variable. This is sometimes referred to as punning. ```move let Y { x1, x2 } = e; ``` is equivalent to: ```move let Y { x1: x1, x2: x2 } = e; ``` As shown with tuples, you cannot declare more than one local with the same name in a single `let`. ```move // highlight-error let Y { x1: x, x2: x } = e; // ERROR! ``` And as with tuples, the mutability of the local variables declared can be mixed. ```move let Y { x1: mut x1, x2 } = e; ``` Furthermore, the mutability of annotation can be applied to the punned fields. Giving the equivalent example ```move let Y { mut x1, x2 } = e; ``` ### Destructuring against references In the examples above for structs, the bound value in the let was moved, destroying the struct value and binding its fields. ```move public struct T { f1: u64, f2: u64 } ``` ```move let T { f1: local1, f2: local2 } = T { f1: 1, f2: 2 }; // local1: u64 // local2: u64 ``` In this scenario the struct value `T { f1: 1, f2: 2 }` no longer exists after the `let`. If you wish instead to not move and destroy the struct value, you can borrow each of its fields. For example: ```move let t = T { f1: 1, f2: 2 }; let T { f1: local1, f2: local2 } = &t; // local1: &u64 // local2: &u64 ``` And similarly with mutable references: ```move let mut t = T { f1: 1, f2: 2 }; let T { f1: local1, f2: local2 } = &mut t; // local1: &mut u64 // local2: &mut u64 ``` This behavior can also work with nested structs. ```move module 0::example; public struct X(u64) public struct Y { x1: X, x2: X } fun new_x(): X { X(1) } fun example() { let mut y = Y { x1: new_x(), x2: new_x() }; let Y { x1: X(f), x2 } = &y; assert!(*f + x2.0 == 2, 42); let Y { x1: X(f1), x2: X(f2) } = &mut y; *f1 = *f1 + 1; *f2 = *f2 + 1; assert!(*f1 + *f2 == 4, 42); // `struct X and struct Y` without `drop` ability and needs to be destroyed manually let Y { x1: X(_), x2: X(_) } = y; } ``` ### Ignoring Values In `let` bindings, it is often helpful to ignore some values. Local variables that start with `_` will be ignored and not introduce a new variable ```move fun three(): (u64, u64, u64) { (0, 1, 2) } ``` ```move let (x1, _, z1) = three(); let (x2, _y, z2) = three(); assert!(x1 + z1 == x2 + z2, 42); ``` This can be necessary at times as the compiler will warn on unused local variables ```move let (x1, y, z1) = three(); // WARNING! // ^ unused local 'y' ``` ### General `let` grammar All of the different structures in `let` can be combined! With that we arrive at this general grammar for `let` statements: > _let-binding_ → **let** _pattern-or-list_ _type-annotation__opt_ > > _initializer__opt_ > _pattern-or-list_ → _pattern_ | **(** _pattern-list_ **)** > > _pattern-list_ → _pattern_ **,**_opt_ | _pattern_ **,** _pattern-list_ > > _type-annotation_ → **:** _type_ _initializer_ → **=** _expression_ The general term for the item that introduces the bindings is a _pattern_. The pattern serves to both destructure data (possibly recursively) and introduce the bindings. The pattern grammar is as follows: > _pattern_ -> _local-variable_ | _struct-type_ **\{** _field-binding-list_ **\}** > > _field-binding-list_ → _field-binding_ **,**_opt_ | _field-binding_ **,** > > _field-binding-list_ > _field-binding_ → _field_ | _field_ **:** _pattern_ A few concrete examples with this grammar applied: ```move let (x, y): (u64, u64) = (0, 1); // ^ local-variable // ^ pattern // ^ local-variable // ^ pattern // ^ pattern-list // ^^^^ pattern-list // ^^^^^^ pattern-or-list // ^^^^^^^^^^^^ type-annotation // ^^^^^^^^ initializer // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ let-binding let Foo { f, g: x } = Foo { f: 0, g: 1 }; // ^^^ struct-type // ^ field // ^ field-binding // ^ field // ^ local-variable // ^ pattern // ^^^^ field-binding // ^^^^^^^ field-binding-list // ^^^^^^^^^^^^^^^ pattern // ^^^^^^^^^^^^^^^ pattern-or-list // ^^^^^^^^^^^^^^^^^^^^ initializer // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ let-binding ``` ## Mutations ### Assignments After the local is introduced (either by `let` or as a function parameter), a `mut` local can be modified via an assignment: ```move x = e ``` Unlike `let` bindings, assignments are expressions. In some languages, assignments return the value that was assigned, but in Move, the type of any assignment is always `()`. ```move (x = e: ()) ``` Practically, assignments being expressions means that they can be used without adding a new expression block with braces (`{`...`}`). ```move let x; if (cond) x = 1 else x = 2; ``` The assignment uses the similar pattern syntax scheme as `let` bindings, but with absence of `mut`: ```move module 0::example; public struct X { f: u64 } fun new_x(): X { X { f: 1 } } // Note: this example will complain about unused variables and assignments. fun example() { let (mut x, mut y, mut f, mut g) = (0, 0, 0, 0); (X { f }, X { f: x }) = (new_x(), new_x()); assert!(f + x == 2, 42); (x, y, f, _, g) = (0, 0, 0, 0, 0); } ``` Note that a local variable can only have one type, so the type of the local cannot change between assignments. ```move let mut x; x = 0; // highlight-error x = false; // ERROR! ``` ### Mutating through a reference In addition to directly modifying a local with assignment, a `mut` local can be modified via a mutable reference `&mut`. ```move let mut x = 0; let r = &mut x; *r = 1; assert!(x == 1, 42); ``` This is particularly useful if either: (1) You want to modify different variables depending on some condition. ```move let mut x = 0; let mut y = 1; let r = if (cond) &mut x else &mut y; *r = *r + 1; ``` (2) You want another function to modify your local value. ```move let mut x = 0; modify_ref(&mut x); ``` This sort of modification is how you modify structs and vectors! ```move let mut v = vector[]; vector::push_back(&mut v, 100); assert!(*vector::borrow(&v, 0) == 100, 42); ``` For more details, see [Move references](./primitive-types/references). ## Scopes Any local declared with `let` is available for any subsequent expression, _within that scope_. Scopes are declared with expression blocks, `{`...`}`. Locals cannot be used outside of the declared scope. ```move let x = 0; { let y = 1; }; // highlight-error-start x + y // ERROR! // ^ unbound local 'y' // highlight-error-end ``` But, locals from an outer scope _can_ be used in a nested scope. ```move { let x = 0; { let y = x + 1; // valid } } ``` Locals can be mutated in any scope where they are accessible. That mutation survives with the local, regardless of the scope that performed the mutation. ```move let mut x = 0; x = x + 1; assert!(x == 1, 42); { x = x + 1; assert!(x == 2, 42); }; assert!(x == 2, 42); ``` ### Expression Blocks An expression block is a series of statements separated by semicolons (`;`). The resulting value of an expression block is the value of the last expression in the block. ```move { let x = 1; let y = 1; x + y } ``` In this example, the result of the block is `x + y`. A statement can be either a `let` declaration or an expression. Remember that assignments (`x = e`) are expressions of type `()`. ```move { let x; let y = 1; x = 1; x + y } ``` Function calls are another common expression of type `()`. Function calls that modify data are commonly used as statements. ```move { let v = vector[]; vector::push_back(&mut v, 1); v } ``` This is not just limited to `()` types---any expression can be used as a statement in a sequence! ```move { let x = 0; x + 1; // value is discarded x + 2; // value is discarded b"hello"; // value is discarded } ``` But! If the expression contains a resource (a value without the `drop` [ability](./abilities)), you will get an error. This is because Move's type system guarantees that any value that is dropped has the `drop` [ability](./abilities). (Ownership must be transferred or the value must be explicitly destroyed within its declaring module.) ```move { let x = 0; // highlight-error-start Coin { value: x }; // ERROR! // ^^^^^^^^^^^^^^^^^ unused value without the `drop` ability // highlight-error-end x } ``` If a final expression is not present in a block---that is, if there is a trailing semicolon `;`, there is an implicit [unit `()` value](https://en.wikipedia.org/wiki/Unit_type). Similarly, if the expression block is empty, there is an implicit unit `()` value. Both are equivalent ```move { x = x + 1; 1 / x; } ``` ```move { x = x + 1; 1 / x; () } ``` Similarly both are equivalent ```move { } ``` ```move { () } ``` An expression block is itself an expression and can be used anyplace an expression is used. (Note: The body of a function is also an expression block, but the function body cannot be replaced by another expression.) ```move let my_vector: vector> = { let mut v = vector[]; vector::push_back(&mut v, b"hello"); vector::push_back(&mut v, b"goodbye"); v }; ``` (The type annotation is not needed in this example and only added for clarity.) ### Shadowing If a `let` introduces a local variable with a name already in scope, that previous variable can no longer be accessed for the rest of this scope. This is called _shadowing_. ```move let x = 0; assert!(x == 0, 42); let x = 1; // x is shadowed assert!(x == 1, 42); ``` When a local is shadowed, it does not need to retain the same type as before. ```move let x = 0; assert!(x == 0, 42); let x = b"hello"; // x is shadowed assert!(x == b"hello", 42); ``` After a local is shadowed, the value stored in the local still exists, but will no longer be accessible. This is important to keep in mind with values of types without the [`drop` ability](./abilities), as ownership of the value must be transferred by the end of the function. ```move module 0::example; public struct Coin has store { value: u64 } fun unused_coin(): Coin { // highlight-error-start let x = Coin { value: 0 }; // ERROR! // ^ This local still contains a value without the `drop` ability x.value = 1; let x = Coin { value: 10 }; x // ^ Invalid return // highlight-error-end } ``` When a local is shadowed inside a scope, the shadowing only remains for that scope. The shadowing is gone once that scope ends. ```move let x = 0; { let x = 1; assert!(x == 1, 42); }; assert!(x == 0, 42); ``` Remember, locals can change type when they are shadowed. ```move let x = 0; { let x = b"hello"; assert!(x == b"hello", 42); }; assert!(x == 0, 42); ``` ## Move and Copy All local variables in Move can be used in two ways, either by `move` or `copy`. If one or the other is not specified, the Move compiler is able to infer whether a `copy` or a `move` should be used. This means that in all of the examples above, a `move` or a `copy` would be inserted by the compiler. A local variable cannot be used without the use of `move` or `copy`. `copy` will likely feel the most familiar coming from other programming languages, as it creates a new copy of the value inside of the variable to use in that expression. With `copy`, the local variable can be used more than once. ```move let x = 0; let y = copy x + 1; let z = copy x + 2; ``` Any value with the `copy` [ability](./abilities) can be copied in this way, and will be copied implicitly unless a `move` is specified. `move` takes the value out of the local variable _without_ copying the data. After a `move` occurs, the local variable is unavailable, even if the value's type has the `copy` [ability](./abilities). ```move let x = 1; // highlight-error-start let y = move x + 1; // ------ Local was moved here let z = move x + 2; // Error! // ^^^^^^ Invalid usage of local 'x' // highlight-error-end y + z ``` ### Safety Move's type system will prevent a value from being used after it is moved. This is the same safety check described in [`let` declaration](#let-bindings) that prevents local variables from being used before it is assigned a value. ### Inference As mentioned above, the Move compiler will infer a `copy` or `move` if one is not indicated. The algorithm for doing so is quite simple: - Any value with the `copy` [ability](./abilities) is given a `copy`. - Any reference (both mutable `&mut` and immutable `&`) is given a `copy`. - Except under special circumstances where it is made a `move` for predictable borrow checker errors. This will happen once the reference is no longer used. - Any other value is given a `move`. Given the structs ```move public struct Foo has copy, drop, store { f: u64 } public struct Coin has store { value: u64 } ``` we have the following example ```move let s = b"hello"; let foo = Foo { f: 0 }; let coin = Coin { value: 0 }; let coins = vector[Coin { value: 0 }, Coin { value: 0 }]; let s2 = s; // copy let foo2 = foo; // copy let coin2 = coin; // move let coins2 = coins; // move let x = 0; let b = false; let addr = @0x42; let x_ref = &x; let coin_ref = &mut coin2; let x2 = x; // copy let b2 = b; // copy let addr2 = @0x42; // copy let x_ref2 = x_ref; // copy let coin_ref2 = coin_ref; // copy ``` --- # Equality Move supports two equality operations `==` and `!=` ## Operations | Syntax | Operation | Description | | ------ | --------- | --------------------------------------------------------------------------- | | `==` | equal | Returns `true` if the two operands have the same value, `false` otherwise | | `!=` | not equal | Returns `true` if the two operands have different values, `false` otherwise | ### Typing Both the equal (`==`) and not-equal (`!=`) operations only work if both operands are the same type ```move 0 == 0; // `true` 1u128 == 2u128; // `false` b"hello" != x"00"; // `true` ``` Equality and non-equality also work over _all_ user defined types! ```move module 0::example; public struct S has copy, drop { f: u64, s: vector } fun always_true(): bool { let s = S { f: 0, s: b"" }; s == s } fun always_false(): bool { let s = S { f: 0, s: b"" }; s != s } ``` If the operands have different types, there is a type checking error ```move 1u8 == 1u128; // ERROR! // ^^^^^ expected an argument of type 'u8' b"" != 0; // ERROR! // ^ expected an argument of type 'vector' ``` ### Typing with references When comparing [references](./primitive-types/references), the type of the reference (immutable or mutable) does not matter. This means that you can compare an immutable `&` reference with a mutable one `&mut` of the same underlying type. ```move let i = &0; let m = &mut 1; i == m; // `false` m == i; // `false` m == m; // `true` i == i; // `true` ``` The above is equivalent to applying an explicit freeze to each mutable reference where needed ```move let i = &0; let m = &mut 1; i == freeze(m); // `false` freeze(m) == i; // `false` m == m; // `true` i == i; // `true` ``` But again, the underlying type must be the same type ```move let i = &0; let s = &b""; i == s; // ERROR! // ^ expected an argument of type '&u64' ``` ### Automatic Borrowing Starting in Move 2024 edition, the `==` and `!=` operators automatically borrow their operands if one of the operands is a reference and the other is not. This means that the following code works without any errors: ```move let r = &0; // In all cases, `0` is automatically borrowed as `&0` r == 0; // `true` 0 == r; // `true` r != 0; // `false` 0 != r; // `false` ``` This automatic borrow is always an immutable borrow. ## Restrictions Both `==` and `!=` consume the value when comparing them. As a result, the type system enforces that the type must have [`drop`](./abilities). Recall that without the [`drop` ability](./abilities), ownership must be transferred by the end of the function, and such values can only be explicitly destroyed within their declaring module. If these were used directly with either equality `==` or non-equality `!=`, the value would be destroyed which would break [`drop` ability](./abilities) safety guarantees! ```move module 0::example; public struct Coin has store { value: u64 } fun invalid(c1: Coin, c2: Coin) { c1 == c2 // ERROR! // ^^ ^^ These assets would be destroyed! } ``` But, a programmer can _always_ borrow the value first instead of directly comparing the value, and reference types have the [`drop` ability](./abilities). For example ```move module 0::example; public struct Coin has store { value: u64 } fun swap_if_equal(c1: Coin, c2: Coin): (Coin, Coin) { let are_equal = &c1 == c2; // valid, note `c2` is automatically borrowed if (are_equal) (c2, c1) else (c1, c2) } ``` ## Avoid Extra Copies While a programmer _can_ compare any value whose type has [`drop`](./abilities), a programmer should often compare by reference to avoid expensive copies. ```move let v1: vector = function_that_returns_vector(); let v2: vector = function_that_returns_vector(); assert!(copy v1 == copy v2, 42); // ^^^^ ^^^^ use_two_vectors(v1, v2); let s1: Foo = function_that_returns_large_struct(); let s2: Foo = function_that_returns_large_struct(); assert!(copy s1 == copy s2, 42); // ^^^^ ^^^^ use_two_foos(s1, s2); ``` This code is perfectly acceptable (assuming `Foo` has [`drop`](./abilities)), just not efficient. The highlighted copies can be removed and replaced with borrows ```move let v1: vector = function_that_returns_vector(); let v2: vector = function_that_returns_vector(); assert!(&v1 == &v2, 42); // ^ ^ use_two_vectors(v1, v2); let s1: Foo = function_that_returns_large_struct(); let s2: Foo = function_that_returns_large_struct(); assert!(&s1 == &s2, 42); // ^ ^ use_two_foos(s1, s2); ``` The efficiency of the `==` itself remains the same, but the `copy`s are removed and thus the program is more efficient. --- # Abort and Assert [`return`](./functions) and `abort` are two control flow constructs that end execution, one for the current function and one for the entire transaction. More information on [`return` can be found in the linked section](./functions#return-expression) ## `abort` `abort` is an expression that takes either takes no arguments, or just one - an **abort code** of type `u64`. For example: ```move abort abort 42 ``` The `abort` expression halts execution the current function and reverts all changes made to state by the current transaction (note though that this guarantee must be upheld by the adapter of the specific deployment of Move). There is no mechanism for "catching" or otherwise handling an `abort`. Luckily, in Move transactions are all or nothing, meaning any changes to storage are made all at once only if the transaction succeeds. For Sui, this means no objects are modified. Because of this transactional commitment of changes, after an abort there is no need to worry about backing out changes. While this approach is lacking in flexibility, it is incredibly simple and predictable. Similar to [`return`](./functions), `abort` is useful for exiting control flow when some condition cannot be met. In this example, the function will pop two items off of the vector, but will abort early if the vector does not have two items ```move fun pop_twice(v: &mut vector): (T, T) { if (v.length() < 2) abort 42; (v.pop_back(), v.pop_back()) } ``` This is even more useful deep inside a control-flow construct. For example, this function checks that all numbers in the vector are less than the specified `bound`. And aborts otherwise ```move fun check_vec(v: &vector, bound: u64) { let mut i = 0; let n = v.length(); while (i < n) { let cur = v[i]; if (cur > bound) abort 42; i = i + 1; } } ``` > Combine `macro` with `abort`: ```move fun check_vec(v: &vector, bound: u64) { v.do_ref!(|num| if (*num > bound) abort 42); } ``` ### `assert` `assert` is a builtin, macro operation provided by the Move compiler. It takes two arguments, a condition of type `bool` and a code of type `u64` ```move assert!(condition: bool, code: u64) ``` Since the operation is a macro, it must be invoked with the `!`. This is to convey that the arguments to `assert` are call-by-expression. In other words, `assert` is not a normal function and does not exist at the bytecode level. It is replaced inside the compiler with ```move if (condition) () else abort code ``` `assert` is more commonly used than just `abort` by itself. The `abort` examples above can be rewritten using `assert` ```move fun pop_twice(v: &mut vector): (T, T) { assert!(v.length() >= 2, 42); // Now uses 'assert' (v.pop_back(), v.pop_back()) } ``` and ```move fun check_vec(v: &vector, bound: u64) { let mut i = 0; let n = v.length(); while (i < n) { let cur = v[i]; assert!(cur <= bound, 42); // Now uses 'assert' i = i + 1; } } ``` > Combine `macro` with `assert`: ```move fun check_vec(v: &vector, bound: u64) { v.do_ref!(|num| assert!(*num <= bound, 42)); } ``` Note that because the operation is replaced with this `if-else`, the argument for the `code` is not always evaluated. For example: ```move assert!(true, 1 / 0) ``` Will not result in an arithmetic error, it is equivalent to ```move if (true) () else abort (1 / 0) ``` So the arithmetic expression is never evaluated! ### Abort codes in the Move VM When using `abort`, it is important to understand how the `u64` code will be used by the VM. Normally, after successful execution, the Move VM, and the adapter for the specific deployment, determine the changes made to storage. If an `abort` is reached, the VM will instead indicate an error. Included in that error will be two pieces of information: - The module that produced the abort (package/address value and module name) - The abort code. For example ```move module 0x2::example { public fun aborts() { abort 42 } } module 0x3::invoker { public fun always_aborts() { 0x2::example::aborts() } } ``` If a transaction, such as the function `always_aborts` above, calls `0x2::example::aborts`, the VM would produce an error that indicated the module `0x2::example` and the code `42`. This can be useful for having multiple aborts being grouped together inside a module. In this example, the module has two separate error codes used in multiple functions ```move module 0::example; use std::vector; const EEmptyVector: u64 = 0; const EIndexOutOfBounds: u64 = 1; // move i to j, move j to k, move k to i public fun rotate_three(v: &mut vector, i: u64, j: u64, k: u64) { let n = v.length(); assert!(n > 0, EEmptyVector); assert!(i < n, EIndexOutOfBounds); assert!(j < n, EIndexOutOfBounds); assert!(k < n, EIndexOutOfBounds); v.swap(i, k); v.swap(j, k); } public fun remove_twice(v: &mut vector, i: u64, j: u64): (T, T) { let n = v.length(); assert!(n > 0, EEmptyVector); assert!(i < n, EIndexOutOfBounds); assert!(j < n, EIndexOutOfBounds); assert!(i > j, EIndexOutOfBounds); (v.remove(i), v.remove(j)) } ``` ## The type of `abort` The `abort i` expression can have any type! This is because both constructs break from the normal control flow, so they never need to evaluate to the value of that type. The following are not useful, but they will type check ```move let y: address = abort 0; ``` This behavior can be helpful in situations where you have a branching instruction that produces a value on some branches, but not all. For example: ```move let b = if (x == 0) false else if (x == 1) true else abort 42; // ^^^^^^^^ `abort 42` has type `bool` ``` --- # Clever Errors Clever errors are a feature that allows for more informative error messages when an assertion fails or an abort is raised. They are a source feature and compile to a `u64` abort code value that contains the information needed to access the line number, constant name, and constant value given the clever error code and the module that the clever error constant was declared in. Because of this compilation, post-processing is required to go from the `u64` abort code value to a human-readable error message. The post-processing is automatically performed by the Sui GraphQL server, as well as the Sui CLI. If you want to manually decode a clever abort code, you can use the process outlined in [Inflating Clever Abort Codes](#inflating-clever-abort-codes) to do so. > Clever errors include source line information amongst other data. Because of this their value may > change due to any changes in the source file (e.g., due to auto-formatting, adding a new module > member, or adding a newline). ## Clever Abort Codes Clever abort codes allow you to use non-u64 constants as abort codes as long as the constants are annotated with the `#[error]` attribute. They can be used both in assertions, and as codes to `abort`. ```move module 0x42::a_module; #[error] const EIsThree: vector = b"The value is three"; // Will abort with `EIsThree` if `x` is 3 public fun double_except_three(x: u64): u64 { assert!(x != 3, EIsThree); x * x } // Will always abort with `EIsThree` public fun clever_abort() { abort EIsThree } ``` In this example, the `EIsThree` constant is a `vector`, which is not a `u64`. However, the `#[error]` attribute allows the constant to be used as an abort code, and will at runtime produce a `u64` abort code value that holds: 1. A set tag-bit that indicates that the abort code is a clever abort code. 2. The line number of where the abort occurred in the source file (e.g., 7). 3. The index in the module's identifier table for the constant's name (e.g., `EIsThree`). 4. The index of the constant's value in the module's constant table (e.g., `b"The value is three"`). In hex, if `double_except_three(3)` is called, it will abort with a `u64` abort code as follows: ``` 0x8000_0007_0001_0000 ^ ^ ^ ^ | | | | | | | | | | | +-- Constant value index = 0 (b"The value is three") | | +-- Constant name index = 1 (EIsThree) | +-- Line number = 7 (line of the assertion) +-- Tag bit = 0b1000_0000_0000_0000 ``` And could be rendered as a human-readable error message as (e.g.) ``` Error from '0x42::a_module::double_except_three' (line 7), abort 'EIsThree': "The value is three" ``` The exact formatting of this message may vary depending on the tooling used to decode the clever error however all of the information needed to generate a human-readable error message like the above is present in the `u64` abort code when coupled with the module where the error occurred. > Clever abort code values do _not_ need to be a `vector` -- it can be any valid constant type > in Move. ## Explicit Error Codes By default, a clever error derives its identifying information entirely from the source -- the line of the abort, and the name and value of the constant. The `#[error]` attribute also accepts an explicit `code` argument, written `#[error(code = )]`, which attaches a developer-chosen code to the error: ```move module 0x42::a_module; /// Tries to create an object twice with the same parent-key combination. #[error(code = 0)] const EObjectAlreadyExists: vector = b"Derived object is already claimed."; ``` The code is an unsigned 8-bit integer, and it is stored in its own field of the `u64` abort code, separate from the line number and from the constant's name and value. Unlike the line number, which shifts whenever the source file changes, the code is fixed by the developer, so it gives each error a stable numeric identifier that tooling can display and match on. When a code is present, decoders surface it alongside the rendered message, for example: ``` Error from '0x42::a_module::claim' (line 22), error code 0, 'EObjectAlreadyExists': "Derived object is already claimed." ``` The constant's name and value are still recorded, so the human-readable message renders just as it does for a bare `#[error]`. Assigning explicit codes this way is the convention used throughout the Sui Framework, where each module gives its error constants small, stable codes. ## Assertions with no Abort Codes Assertions and `abort` statements without an abort code will automatically derive an abort code from the source line number and will be encoded in the clever error format with the constant name and constant value information will be filled with sentinel values of `0xffff` each. E.g., ```move module 0x42::a_module; #[test] fun assert_false(x: bool) { assert!(false); } #[test] fun abort_no_code() { abort } ``` Both of these will produce a `u64` abort code value that holds: 1. A set tag-bit that indicates that the abort code is a clever abort code. 2. The line number of where the abort occurred in the source file (e.g., 6). 3. A sentinel value of `0xffff` for the index into the module's identifier table for the constant's name. 4. A sentinel value of `0xffff` for the index of the constant's value in the module's constant table. In hex, if `assert_false(3)` is called, it will abort with a `u64` abort code as follows: ``` 0x8000_0004_ffff_ffff ^ ^ ^ ^ | | | | | | | | | | | +-- Constant value index = 0xffff (sentinel value) | | +-- Constant name index = 0xffff (sentinel value) | +-- Line number = 4 (link of the assertion) +-- Tag bit = 0b1000_0000_0000_0000 ``` ## Clever Errors and Macros The line number information in clever abort codes are derived from the source file at the location where the abort occurs. In particular, for a function this will be the line number within in the function, however for macros, this will be the location where the macro is invoked. This can be quite useful when writing macros as it provides a way for users to use macros that may raise abort conditions and still get useful error messages. ```move module 0x42::macro_exporter; public macro fun assert_false() { assert!(false); } public macro fun abort_always() { abort } public fun assert_false_fun() { assert!(false); // Will always abort with the line number of this invocation } public fun abort_always_fun() { abort // Will always abort with the line number of this invocation } ``` Then in a module that uses these macros: ```move module 0x42::user_module; use 0x42::macro_exporter::{ assert_false, abort_always, assert_false_fun, abort_always_fun }; fun invoke_assert_false() { assert_false!(); // Will abort with the line number of this invocation } fun invoke_abort_always() { abort_always!(); // Will abort with the line number of this invocation } fun invoke_assert_false_fun() { assert_false_fun(); // Will abort with the line number of the assertion in `assert_false_fun` } fun invoke_abort_always_fun() { abort_always_fun(); // Will abort with the line number of the `abort` in `abort_always_fun` } ``` ## Inflating Clever Abort Codes Precisely, the layout of a clever abort code is as follows: ``` |||||| +--------+----------+--------------------+-------------------------+-----------------------+ | 1-bit | 15-bits | 16-bits | 16-bits | 16-bits | ``` Note that the Move abort will come with some additional information -- importantly in our case the module where the error occurred. This is important because the identifier index, and constant index are relative to the module's identifier and constant tables (if not set the sentinel values). The high bits that this layout labels _reserved_ also hold the explicit error code set with [`#[error(code = N)]`](#explicit-error-codes), in a dedicated 8-bit field, when one is provided. > To decode a clever abort code, you will need to know the module where the error occurred if either > the identifier index or constant index are not set to the sentinel value of `0xffff`. In pseudo-code, you can decode a clever abort code as follows: ```rust // Information available in the MoveAbort let clever_abort_code: u64 = ...; let (package_id, module_name): (PackageStorageId, ModuleName) = ...; let is_clever_abort = (clever_abort_code & 0x8000_0000_0000_0000) != 0; if is_clever_abort { // Get line number, identifier index, and constant index // Identifier and constant index are sentinel values if set to '0xffff' let line_number = ((clever_abort_code & 0x0000_ffff_0000_0000) >> 32) as u16; let identifier_index = ((clever_abort_code & 0x0000_0000_ffff_0000) >> 16) as u16; let constant_index = ((clever_abort_code & 0x0000_0000_0000_ffff)) as u16; // Print the line error message print!("Error from '{}::{}' (line {})", package_id, module_name, line_number); // No need to print anything or load the module if both are sentinel values if identifier_index == 0xffff && constant_index == 0xffff { return; } // Only needed if constant name and value are not 0xffff let module: CompiledModule = fetch_module(package_id, module_name); // Print the constant name (if any) if identifier_index != 0xffff { let constant_name = module.get_identifier_at_table_index(identifier_index); print!(", '{}'", constant_name); } // Print the constant value (if any) if constant_index != 0xffff { let constant_value = module .get_constant_at_table_index(constant_index) .deserialize_on_constant_type() .to_string(); print!(": {}", constant_value); } return; } ``` --- # Control Flow Move offers multiple constructs for control flow based on [boolean expressions](./primitive-types/bool), including common programming constructs such as `if` expressions and `while` and `for` loops, along with advanced control flow structures including labels for loops and escapable named blocks. It also supports more complex constructs based on structural pattern matching. - [Conditional Expressions](./control-flow/conditionals) - [Pattern Matching](./control-flow/pattern-matching) - [Loops](./control-flow/loops) - [Labeled Control FLow](./control-flow/labeled-control-flow) --- # Conditional `if` Expressions An `if` expression specifies that some code should only be evaluated if a certain condition is true. For example: ```move if (x > 5) x = x - 5 ``` The condition must be an expression of type `bool`. An `if` expression can optionally include an `else` clause to specify another expression to evaluate when the condition is false. ```move if (y <= 10) y = y + 1 else y = 10 ``` Either the "true" branch or the "false" branch will be evaluated, but not both. Either branch can be a single expression or an expression block. The conditional expressions may produce values so that the `if` expression has a result. ```move let z = if (x < 100) x else 100; ``` If the `else` clause is not specified, the false branch defaults to the unit value. The following are equivalent: ```move if (condition) true_branch // implied default: else () if (condition) true_branch else () ``` The expressions in the true and false branches must have compatible types. For example: ```move // x and y must be u64 integers let maximum: u64 = if (x > y) x else y; // highlight-error-start // ERROR! branches different types let z = if (maximum < 10) 10u8 else 100u64; // ERROR! branches different types, as default false-branch is () not u64 let y = if (maximum >= 10) maximum; // highlight-error-end ``` Commonly, `if` expressions are used in conjunction with [expression blocks](./../variables#expression-blocks). ```move let maximum = if (x > y) x else y; if (maximum < 10) { x = x + 10; y = y + 10; } else if (x >= 10 && y >= 10) { x = x - 10; y = y - 10; } ``` ## Grammar for Conditionals > _if-expression_ → **if (** _expression_ **)** _expression_ _else-clause__opt_ > > _else-clause_ → **else** _expression_ --- # Loop Constructs in Move Many programs require iteration over values, and Move provides `while` and `loop` forms to allow you to write code in these situations. In addition, you can also modify control flow of these loops during execution by using `break` (to exit the loop) and `continue` (to skip the remainder of this iteration and return to the top of the control flow structure). ## `while` Loops The `while` construct repeats the body (an expression of type unit) until the condition (an expression of type `bool`) evaluates to `false`. Here is an example of simple `while` loop that computes the sum of the numbers from `1` to `n`: ```move fun sum(n: u64): u64 { let mut sum = 0; let mut i = 1; while (i <= n) { sum = sum + i; i = i + 1 }; sum } ``` Infinite `while` loops are also allowed: ```move fun foo() { while (true) { } } ``` > It's a better way to use Macros instead of Loops to achieve a more concise and readable purpose. > This article only takes the above function `sum` as an example to experience the charm of macro functions: ```move fun sum(n: u64): u64 { vector::tabulate!(n, |i| i + 1).fold!(0, |sum, num| sum + num) } ``` ### Using `break` Inside of `while` Loops In Move, `while` loops can use `break` to exit early. For example, suppose we were looking for the position of a value in a vector, and would like to `break` if we find it: ```move fun find_position(values: &vector, target_value: u64): Option { let size = values.length(); let mut i = 0; let mut found = false; while (i < size) { if (values[i] == target_value) { found = true; break }; i = i + 1 }; if (found) { option::some(i) } else { option::none() } } ``` Here, if the borrowed vector value is equal to our target value, we set the `found` flag to `true` and then call `break`, which will cause the program to exit the loop. Finally, note that `break` for `while` loops cannot take a value: `while` loops always return the unit type `()` and thus `break` does, too. ### Using `continue` Inside of `while` Loops Similar to `break`, Move's `while` loops can invoke `continue` to skip over part of the loop body. This allows us to skip part of a computation if a condition is not met, such as in the following example: ```move fun sum_even(values: &vector): u64 { let size = values.length(); let mut i = 0; let mut even_sum = 0; while (i < size) { let number = values[i]; i = i + 1; if (number % 2 == 1) continue; even_sum = even_sum + number; }; even_sum } ``` This code will iterate over the provided vector. For each entry, if that entry is an even number, it will add it to the `even_sum`. If it is not, however, it will call `continue`, skipping the sum operation and returning to the `while` loop conditional check. ## `loop` Expressions The `loop` expression repeats the loop body (an expression with type `()`) until it hits a `break`: ```move fun sum(n: u64): u64 { let mut sum = 0; let mut i = 1; loop { i = i + 1; if (i >= n) break; sum = sum + i; }; sum } ``` Without a `break`, the loop will continue forever. In the example below, the program will run forever because the `loop` does not have a `break`: ```move fun foo() { let mut i = 0; loop { i = i + 1 } } ``` ### Using `break` with Values in `loop` Unlike `while` loops, which always return `()`, a `loop` may return a value using `break`. In doing so, the overall `loop` expression evaluates to a value of that type. For example, we can rewrite `find_position` from above using `loop` and `break`, immediately returning the index if we find it: ```move fun find_position(values: &vector, target_value: u64): Option { let size = values.length(); let mut i = 0; loop { if (values[i] == target_value) { break option::some(i) } else if (i >= size) { break option::none() }; i = i + 1; } } ``` This loop will break with an option result, and, as the last expression in the function body, will produce that value as the final function result. ### Using `continue` Inside of `loop` Expressions As you might expect, `continue` can also be used inside a `loop`. Here is the previous `sum_even` function rewritten using `loop` with `break `and` continue` instead of `while`. ```move fun sum_even(values: &vector): u64 { let size = values.length(); let mut i = 0; let mut even_sum = 0; loop { if (i >= size) break; let number = values[i]; i = i + 1; if (number % 2 == 1) continue; even_sum = even_sum + number; }; even_sum } ``` ## The Type of `while` and `loop` In Move, loops are typed expressions. A `while` expression always has type `()`. ```move let () = while (i < 10) { i = i + 1 }; ``` If a `loop` contains a `break`, the expression has the type of the break. A break with no value has the unit type `()`. ```move (loop { if (i < 10) i = i + 1 else break }: ()); let () = loop { if (i < 10) i = i + 1 else break }; let x: u64 = loop { if (i < 10) i = i + 1 else break 5 }; let x: u64 = loop { if (i < 10) { i = i + 1; continue} else break 5 }; ``` In addition, if a loop contains multiple breaks, they must all return the same type: ```move // invalid -- first break returns (), second returns 5 let x: u64 = loop { if (i < 10) break else break 5 }; ``` If `loop` does not have a `break`, `loop` can have any type much like `return`, `abort`, `break`, and `continue`. ```move (loop (): u64); (loop (): address); (loop (): &vector>); ``` If you need even more-precise control flow, such as breaking out of nested loops, the next chapter presents the use of labeled control flow in Move. --- # Labeled Control Flow Move supports labeled control flow when writing both loops and blocks of code, allowing you to `break` and `continue` loops and `return` from blocks (which can be particularly helpful in the presence of macros). ## Loops Loops allow you to define and transfer control to specific labels in a function. For example, we can nest two loops and use `break` and `continue` with those labels to precisely specify control flow. You can prefix any `loop` or `while` form with a `'label:` form to allow breaking or continuing directly there. To demonstrate this behavior, consider a function that takes nested vectors of numbers (i.e., `vector>`) to sum against some threshold, which behaves as follows: - If the sum of all the numbers are under the threshold, return that sum. - If adding a number to the current sum would surpass the threshold, return the current sum. We can write this by iterating over the vector of vectors as nested loops and labelling the outer one. If any addition in the inner loop would push us over the threshold, we can use `break` with the outer label to escape both loops at once: ```move fun sum_until_threshold(input: &vector>, threshold: u64): u64 { let mut sum = 0; let mut i = 0; let input_size = input.length(); 'outer: loop { // breaks to outer since it is the closest enclosing loop if (i >= input_size) break sum; let vec = &input[i]; let size = vec.length(); let mut j = 0; while (j < size) { let v_entry = vec[j]; if (sum + v_entry < threshold) { sum = sum + v_entry; } else { // the next element we saw would break the threshold, // so we return the current sum break 'outer sum }; j = j + 1; }; i = i + 1; } } ``` These sorts of labels can also be used with a nested loop form, providing precise control in larger bodies of code. For example, if we were processing a large table where each entry required iteration that might see us continuing the inner or outer loop, we could express that code using labels: ```move let x = 'outer: loop { ... 'inner: while (cond) { ... if (cond0) { break 'outer value }; ... if (cond1) { continue 'inner } else if (cond2) { continue 'outer } ... } ... }; ``` > It's a better way to use Macros instead of Loops, similarly, use `return` to control the flow. > Just like above function `sum_until_threshold`, can use `macro` to rewrite it: ```move fun sum_until_threshold(input: &vector>, threshold: u64): u64 { 'outer: { (*input).fold!(0, |sum, inner_vec| { inner_vec.fold!(sum, |sum, num| if (sum + num < threshold) sum + num else return 'outer sum) }) } } ``` ## Labeled Blocks Labeled blocks allow you to write Move programs that contain intra-function non-local control flow, including inside of macro lambdas and returning values: ```move fun named_return(n: u64): vector { let x = 'a: { if (n % 2 == 0) { return 'a b"even" }; b"odd" }; x } ``` In this simple example, the program checks if the input `n` is even. If it is, the program leaves the block labeled `'a:` with the value `b"even"`. If not, the code continues, ending the block labeled `'a:` with the value `b"odd"`. At the end, we set `x` to the value and then return it. This control flow feature works across macro bodies as well. For example, suppose we wanted to write a function to find the first even number in a vector, and that we have some macro `for_ref` that iterates the vector elements in a loop: ```move macro fun for_ref<$T>($vs: &vector<$T>, $f: |&$T|) { let vs = $vs; let mut i = 0; let end = vs.length(); while (i < end) { $f(vs.borrow(i)); i = i + 1; } } ``` Using `for_ref` and a label, we can write a lambda expression to pass `for_ref` that will escape the loop, returning the first even number it finds: ```move fun find_first_even(vs: vector): Option { 'result: { for_ref!(&vs, |n| if (*n % 2 == 0) { return 'result option::some(*n)}); option::none() } } ``` This function will iterate `vs` until it finds an even number, and return that (or return `option::none()` if no even number exists). This makes named labels a powerful tool for interacting with control flow macros such as `for!`, allowing you to customize iteration behavior in those contexts. ## Restrictions To clarify program behavior, you may only use `break` and `continue` with loop labels, while `return` will only work with block labels. To this end, the following programs produce errors: ```move fun bad_loop() { 'name: loop { return 'name 5 // ^^^^^ Invalid usage of 'return' with a loop block label } } fun bad_block() { 'name: { continue 'name; // ^^^^^ Invalid usage of 'break' with a loop block label break 'name; // ^^^^^ Invalid usage of 'break' with a loop block label } } ``` --- # Pattern Matching A `match` expression is a powerful control structure that allows you to compare a value against a series of patterns and then execute code based on which pattern matches first. Patterns can be anything from simple literals to complex, nested struct and enum definitions. As opposed to `if` expressions, which change control flow based on a `bool`-typed test expression, a `match` expression operates over a value of any type and selects one of many arms. A `match` expression can match Move values as well as mutable or immutable references, binding sub-patterns accordingly. For example: ```move fun run(x: u64): u64 { match (x) { 1 => 2, 2 => 3, x => x, } } run(1); // returns 2 run(2); // returns 3 run(3); // returns 3 run(0); // returns 0 ``` ## `match` Syntax A `match` takes an expression and a non-empty series of _match arms_ delimited by commas. Each match arm consists of a pattern (`p`), an optional guard (`if (g)` where `g` is an expression of type `bool`), an arrow (`=>`), and an arm expression (`e`) to execute when the pattern matches. For example, ```move match (expression) { pattern1 if (guard_expression) => expression1, pattern2 => expression2, pattern3 => { expression3, expression4, ... }, } ``` Match arms are checked in order from top to bottom, and the first pattern that matches (with a guard expression, if present, that evaluates to `true`) will be executed. Note that the series of match arms within a `match` must be exhaustive, meaning that every possible value of the type being matched must be covered by one of the patterns in the `match`. If the series of match arms is not exhaustive, the compiler will raise an error. ## Pattern Syntax A pattern is matched by a value if the value is equal to the pattern, and where variables and wildcards (e.g., `x`, `y`, `_`, or `..`) are "equal" to anything. Patterns are used to match values. Patterns can be | Pattern | Description | | -------------------- | ---------------------------------------------------------------------- | | Literal | A literal value, such as `1`, `true`, `@0x1` | | Constant | A constant value, e.g., `MyConstant` | | Variable | A variable, e.g., `x`, `y`, `z` | | Wildcard | A wildcard, e.g., `_` | | Constructor | A constructor pattern, e.g., `MyStruct { x, y }`, `MyEnum::Variant(x)` | | At-pattern | An at-pattern, e.g., `x @ MyEnum::Variant(..)` | | Or-pattern | An or-pattern, e.g., `MyEnum::Variant(..) \| MyEnum::OtherVariant(..)` | | Multi-arity wildcard | A multi-arity wildcard, e.g., `MyEnum::Variant(..)` | | Mutable-binding | A mutable-binding pattern, e.g., `mut x` | Patterns in Move have the following grammar: ```bnf pattern = | | | _ | C { : inner-pattern ["," : inner-pattern]* } // where C is a struct or enum variant | C ( inner-pattern ["," inner-pattern]* ... ) // where C is a struct or enum variant | C // where C is an enum variant | @ top-level-pattern | pattern | pattern | mut inner-pattern = pattern | .. // multi-arity wildcard ``` Some examples of patterns are: ```move // literal pattern 1 // constant pattern MyConstant // variable pattern x // wildcard pattern _ // constructor pattern that matches `MyEnum::Variant` with the fields `1` and `true` MyEnum::Variant(1, true) // constructor pattern that matches `MyEnum::Variant` with the fields `1` and binds the second field's value to `x` MyEnum::Variant(1, x) // multi-arity wildcard pattern that matches multiple fields within the `MyEnum::Variant` variant MyEnum::Variant(..) // constructor pattern that matches the `x` field of `MyStruct` and binds the `y` field to `other_variable` MyStruct { x, y: other_variable } // at-pattern that matches `MyEnum::Variant` and binds the entire value to `x` x @ MyEnum::Variant(..) // or-pattern that matches either `MyEnum::Variant` or `MyEnum::OtherVariant` MyEnum::Variant(..) | MyEnum::OtherVariant(..) // same as the above or-pattern, but with explicit wildcards MyEnum::Variant(_, _) | MyEnum::OtherVariant(_, _) // or-pattern that matches either `MyEnum::Variant` or `MyEnum::OtherVariant` and binds the u64 field to `x` MyEnum::Variant(x, _) | MyEnum::OtherVariant(_, x) // constructor pattern that matches `OtherEnum::V` and if the inner `MyEnum` is `MyEnum::Variant` OtherEnum::V(MyEnum::Variant(..)) ``` ### Patterns and Variables Patterns that contain variables bind them to the match subject or subject subcomponent being matched. These variables can then be used either in any match guard expressions, or on the right-hand side of the match arm. For example: ```move public struct Wrapper(u64) fun add_under_wrapper_unless_equal(wrapper: Wrapper, x: u64): Wrapper { match (wrapper) { Wrapper(y) if (y == x) => Wrapper(y), Wrapper(y) => Wrapper(y + x), } } add_under_wrapper_unless_equal(Wrapper(1), 2); // returns Wrapper(3) add_under_wrapper_unless_equal(Wrapper(2), 3); // returns Wrapper(5) add_under_wrapper_unless_equal(Wrapper(3), 3); // returns Wrapper(3) ``` ### Combining Patterns Patterns can be nested, but patterns can also be combined using the or operator (`|`). For example, `p1 | p2` succeeds if either pattern `p1` or `p2` matches the subject. This pattern can occur anywhere -- either as a top-level pattern or a sub-pattern within another pattern. ```move public enum MyEnum has drop { Variant(u64, bool), OtherVariant(bool, u64), } fun test_or_pattern(x: u64): u64 { match (x) { MyEnum::Variant(1 | 2 | 3, true) | MyEnum::OtherVariant(true, 1 | 2 | 3) => 1, MyEnum::Variant(8, true) | MyEnum::OtherVariant(_, 6 | 7) => 2, _ => 3, } } test_or_pattern(MyEnum::Variant(3, true)); // returns 1 test_or_pattern(MyEnum::OtherVariant(true, 2)); // returns 1 test_or_pattern(MyEnum::Variant(8, true)); // returns 2 test_or_pattern(MyEnum::OtherVariant(false, 7)); // returns 2 test_or_pattern(MyEnum::OtherVariant(false, 80)); // returns 3 ``` ### Restrictions on Some Patterns The `mut` and `..` patterns also have specific conditions placed on when, where, and how they can be used, as detailed in [Limitations on Specific Patterns](#limitations-on-specific-patterns). At a high level, the `mut` modifier can only be used on variable patterns, and the `..` pattern can only be used once within a constructor pattern -- and not as a top-level pattern. The following is an _invalid_ usage of the `..` pattern because it is used as a top-level pattern: ```move match (x) { .. => 1, // ERROR: `..` pattern can only be used within a constructor pattern } match (x) { MyStruct(.., ..) => 1, // ERROR: ^^ `..` pattern can only be used once within a constructor pattern } ``` ### Pattern Typing Patterns are not expressions, but they are nevertheless typed. This means that the type of a pattern must match the type of the value it matches. For example, the pattern `1` has an integer type, the pattern `MyEnum::Variant(1, true)` has type `MyEnum`, the pattern `MyStruct { x, y }` has type `MyStruct`, and `OtherStruct { x: true, y: 1}` has type `OtherStruct`. If you try to match on an expression that differs from the type of the pattern in the match, this will result in a type error. For example: ```move match (1) { // The `true` literal pattern is of type `bool` so this is a type error. true => 1, // TYPE ERROR: expected type u64, found bool _ => 2, } ``` Similarly, the following would also result in a type error because `MyEnum` and `MyStruct` are different types: ```move match (MyStruct { x: 0, y: 0 }) { MyEnum::Variant(..) => 1, // TYPE ERROR: expected type MyEnum, found MyStruct } ``` ## Matching Prior to delving into the specifics of pattern matching and what it means for a value to "match" a pattern, let's examine a few examples to provide an intuition for the concept. ```move fun test_lit(x: u64): u8 { match (x) { 1 => 2, 2 => 3, _ => 4, } } test_lit(1); // returns 2 test_lit(2); // returns 3 test_lit(3); // returns 4 test_lit(10); // returns 4 fun test_var(x: u64): u64 { match (x) { y => y, } } test_var(1); // returns 1 test_var(2); // returns 2 test_var(3); // returns 3 ... const MyConstant: u64 = 10; fun test_constant(x: u64): u64 { match (x) { MyConstant => 1, _ => 2, } } test_constant(MyConstant); // returns 1 test_constant(10); // returns 1 test_constant(20); // returns 2 fun test_or_pattern(x: u64): u64 { match (x) { 1 | 2 | 3 => 1, 4 | 5 | 6 => 2, _ => 3, } } test_or_pattern(3); // returns 1 test_or_pattern(5); // returns 2 test_or_pattern(70); // returns 3 fun test_or_at_pattern(x: u64): u64 { match (x) { x @ (1 | 2 | 3) => x + 1, y @ (4 | 5 | 6) => y + 2, z => z + 3, } } test_or_pattern(2); // returns 3 test_or_pattern(5); // returns 7 test_or_pattern(70); // returns 73 ``` The most important thing to note from these examples is that a pattern matches a value if the value is equal to the pattern, and wildcard/variable patterns match anything. This is true for literals, variables, and constants. For example, in the `test_lit` function, the value `1` matches the pattern `1`, the value `2` matches the pattern `2`, and the value `3` matches the wildcard `_`. Similarly, in the `test_var` function, both the value `1` and the value `2` matches the pattern `y`. A variable `x` matches (or "equals") any value, and a wildcard `_` matches any value (but only one value). Or-patterns are like a logical OR, where a value matches the pattern if it matches any of patterns in the or-pattern so `p1 | p2 | p3` should be read "matches p1, or p2, or p3". ### Matching Constructors Pattern matching includes the concept of constructor patterns. These patterns allow you to inspect and access deep within both structs and enums, and are one of the most powerful parts of pattern matching. Constructor patterns, coupled with variable bindings, allow you to match on values by their structure, and pull out the parts of the value you care about for usage on the right-hand side of the match arm. Take the following: ```move fun f(x: MyEnum): u64 { match (x) { MyEnum::Variant(1, true) => 1, MyEnum::OtherVariant(_, 3) => 2, MyEnum::Variant(..) => 3, MyEnum::OtherVariant(..) => 4, } } f(MyEnum::Variant(1, true)); // returns 1 f(MyEnum::Variant(2, true)); // returns 3 f(MyEnum::OtherVariant(false, 3)); // returns 2 f(MyEnum::OtherVariant(true, 3)); // returns 2 f(MyEnum::OtherVariant(true, 2)); // returns 4 ``` This is saying that "if `x` is `MyEnum::Variant` with the fields `1` and `true`, then return `1`. If it is `MyEnum::OtherVariant` with any value for the first field, and `3` for the second, then return `2`. If it is `MyEnum::Variant` with any fields, then return `3`. Finally, if it is `MyEnum::OtherVariant` with any fields, then return `4`". You can also nest patterns. So, if you wanted to match either 1, 2, or 10, instead of just matching 1 in the previous `MyEnum::Variant`, you could do so with an or-pattern: ```move fun f(x: MyEnum): u64 { match (x) { MyEnum::Variant(1 | 2 | 10, true) => 1, MyEnum::OtherVariant(_, 3) => 2, MyEnum::Variant(..) => 3, MyEnum::OtherVariant(..) => 4, } } f(MyEnum::Variant(1, true)); // returns 1 f(MyEnum::Variant(2, true)); // returns 1 f(MyEnum::Variant(10, true)); // returns 1 f(MyEnum::Variant(10, false)); // returns 3 ``` ### Ability Constraints Additionally, match bindings are subject to the same ability restrictions as other aspects of Move. In particular, the compiler will signal an error if you try to match a value (not-reference) without `drop` using a wildcard, as the wildcard expects to drop the value. Similarly, if you bind a non-`drop` value using a binder, it must be used in the right-hand side of the match arm. In addition, if you fully destruct that value, you have unpacked it, matching the semantics of [non-`drop` struct unpacking](./../structs#destroying-structs-via-pattern-matching). See the [abilities section on `drop`](./../abilities#drop) for more details about the `drop` capability. ```move public struct NonDrop(u64) fun drop_nondrop(x: NonDrop): u64 { match (x) { NonDrop(1) => 1, _ => 2 // ERROR: cannot wildcard match on a non-droppable value } } fun destructure_nondrop(x: NonDrop): u64 { match (x) { NonDrop(1) => 1, NonDrop(_) => 2 // OK! } } fun use_nondrop(x: NonDrop): NonDrop { match (x) { NonDrop(1) => NonDrop(8), x => x } } ``` ## Exhaustiveness The `match` expression in Move must be _exhaustive_: every possible value of the type being matched must be covered by one of the patterns in one of the match's arms. If the series of match arms is not exhaustive, the compiler will raise an error. Note that any arm with a guard expression does not contribute to match exhaustion, as it might fail to match at runtime. As an example, a match on a `u8` is exhaustive only if it matches on _every_ number from 0 to 255 inclusive, unless there is a wildcard or variable pattern present. Similarly, a match on a `bool` would need to match on both `true` and `false`, unless there is a wildcard or variable pattern present. For structs, because there is only one type of constructor for the type, only one constructor needs to be matched, but the fields within the struct need to be matched exhaustively as well. Conversely, enums may define multiple variants, and each variant must be matched (including any sub-fields) for the match to be considered exhaustive. Because underscores and variables are wildcards that match anything, they count as matching all values of the type they are matching on in that position. Additionally, the multi-arity wildcard pattern `..` can be used to match on multiple values within a struct or enum variant. To see some examples of _non-exhaustive_ matches, consider the following: ```move public enum MyEnum { Variant(u64, bool), OtherVariant(bool, u64), } public struct Pair(T, T) fun f(x: MyEnum): u8 { match (x) { MyEnum::Variant(1, true) => 1, MyEnum::Variant(_, _) => 1, MyEnum::OtherVariant(_, 3) => 2, // ERROR: not exhaustive as the value `MyEnum::OtherVariant(_, 4)` is not matched. } } fun match_pair_bool(x: Pair): u8 { match (x) { Pair(true, true) => 1, Pair(true, false) => 1, Pair(false, false) => 1, // ERROR: not exhaustive as the value `Pair(false, true)` is not matched. } } ``` These examples can then be made exhaustive by adding a wildcard pattern to the end of the match arm, or by fully matching on the remaining values: ```move fun f(x: MyEnum): u8 { match (x) { MyEnum::Variant(1, true) => 1, MyEnum::Variant(_, _) => 1, MyEnum::OtherVariant(_, 3) => 2, // Now exhaustive since this will match all values of MyEnum::OtherVariant MyEnum::OtherVariant(..) => 2, } } fun match_pair_bool(x: Pair): u8 { match (x) { Pair(true, true) => 1, Pair(true, false) => 1, Pair(false, false) => 1, // Now exhaustive since this will match all values of Pair Pair(false, true) => 1, } } ``` ## Guards As previously mentioned, you can add a guard to a match arm by adding an `if` clause after the pattern. This guard will run _after_ the pattern has been matched but _before_ the expression on the right hand side of the arrow is evaluated. If the guard expression evaluates to `true` then the expression on the right hand side of the arrow will be evaluated, if it evaluates to `false` then it will be considered a failed match and the next match arm in the `match` expression will be checked. ```move fun match_with_guard(x: u64): u64 { match (x) { 1 if (false) => 1, 1 => 2, _ => 3, } } match_with_guard(1); // returns 2 match_with_guard(0); // returns 3 ``` Guard expressions can reference variables bound in the pattern during evaluation. However, note that _variables are only available as immutable reference in guards_ regardless of the pattern being matched -- even if there are mutability specifiers on the variable or if the pattern is being matched by value. ```move fun incr(x: &mut u64) { *x = *x + 1; } fun match_with_guard_incr(x: u64): u64 { match (x) { x if ({ incr(&mut x); x == 1 }) => 1, // ERROR: ^^^ invalid borrow of immutable value _ => 2, } } fun match_with_guard_incr2(x: &mut u64): u64 { match (x) { x if ({ incr(&mut x); x == 1 }) => 1, // ERROR: ^^^ invalid borrow of immutable value _ => 2, } } ``` Additionally, it is important to note any match arms that have guard expressions will not be considered either for exhaustivity purposes because the compiler has no way of evaluating the guard expression statically. ## Limitations on Specific Patterns There are some restrictions on when the `..` and `mut` pattern modifiers can be used in a pattern. ### Mutability Usage A `mut` modifier can be placed on a variable pattern to specify that the _variable_ is to be mutated in the right-hand expression of the match arm. Note that since the `mut` modifier only signifies that the variable is to be mutated, not the underlying data, this can be used on all types of match (by value, immutable reference, and mutable reference). Note that the `mut` modifier can only be applied to variables, and not other types of patterns. ```move public struct MyStruct(u64) fun top_level_mut(x: MyStruct): u64 { match (x) { mut MyStruct(y) => 1, // ERROR: cannot use mut on a non-variable pattern } } fun mut_on_immut(x: &MyStruct): u64 { match (x) { MyStruct(mut y) => { y = &(*y + 1); *y } } } fun mut_on_value(x: MyStruct): u64 { match (x) { MyStruct(mut y) => { *y = *y + 1; *y }, } } fun mut_on_mut(x: &mut MyStruct): u64 { match (x) { MyStruct(mut y) => { *y = *y + 1; *y }, } } let mut x = MyStruct(1); mut_on_mut(&mut x); // returns 2 x.0; // returns 2 mut_on_immut(&x); // returns 3 x.0; // returns 2 mut_on_value(x); // returns 3 ``` ### `..` Usage The `..` pattern can only be used within a constructor pattern as a wildcard that matches any number of fields -- the compiler expands the `..` to inserting `_` in any missing fields in the constructor pattern (if any). So `MyStruct(_, _, _)` is the same as `MyStruct(..)`, `MyStruct(1, _, _)` is the same as `MyStruct(1, ..)`. Because of this, there are some restrictions on how, and where the `..` pattern can be used: - It can only be used **once** within the constructor pattern; - In positional arguments it can be used at the beginning, middle, or end of the patterns within the constructor; - In named arguments it can only be used at the end of the patterns within the constructor; ```move public struct MyStruct(u64, u64, u64, u64) has drop; public struct MyStruct2 { x: u64, y: u64, z: u64, w: u64, } fun wild_match(x: MyStruct): u64 { match (x) { MyStruct(.., 1) => 1, // OK! The `..` pattern can be used at the beginning of the constructor pattern MyStruct(1, ..) => 2, // OK! The `..` pattern can be used at the end of the constructor pattern MyStruct(1, .., 1) => 3, // OK! The `..` pattern can be used at the middle of the constructor pattern MyStruct(1, .., 1, 1) => 4, MyStruct(..) => 5, } } fun wild_match2(x: MyStruct2): u64 { match (x) { MyStruct2 { x: 1, .. } => 1, MyStruct2 { x: 1, w: 2 .. } => 2, MyStruct2 { .. } => 3, } } ``` --- # Functions Functions are declared inside of modules and define the logic and behavior of the module. Functions can be reused, either being called from other functions or as entry points for execution. ## Declaration Functions are declared with the `fun` keyword followed by the function name, type parameters, parameters, a return type, and finally the function body. ```text ? ? ? fun <[type_parameters: constraint],*>([identifier: type],*): ``` For example ```move fun foo(x: u64, y: T1, z: T2): (T2, T1, u64) { (z, y, x) } ``` ### Visibility Module functions, by default, can only be called within the same module. These internal (sometimes called private) functions cannot be called from other modules or as entry points. ```move module a::m { fun foo(): u64 { 0 } fun calls_foo(): u64 { foo() } // valid } module b::other { fun calls_m_foo(): u64 { a::m::foo() // ERROR! // ^^^^^^^^^^^ 'foo' is internal to 'a::m' } } ``` To allow access from other modules, the function must be declared `public` or `public(package)`. Tangential to visibility, an [`entry`](#entry-modifier) function can be called as an entry point for execution. #### `public` visibility A `public` function can be called by _any_ function defined in _any_ module. As shown in the following example, a `public` function can be called by: - other functions defined in the same module, - functions defined in another module, or - as an entry point for execution. ```move module a::m { public fun foo(): u64 { 0 } fun calls_foo(): u64 { foo() } // valid } module b::other { fun calls_m_foo(): u64 { a::m::foo() // valid } } ``` Fore more details on the entry point to execution see [the section below](#entry-modifier). #### `public(package)` visibility The `public(package)` visibility modifier is a more restricted form of the `public` modifier to give more control about where a function can be used. A `public(package)` function can be called by: - other functions defined in the same module, or - other functions defined in the same package (the same address) ```move module a::m { public(package) fun foo(): u64 { 0 } fun calls_foo(): u64 { foo() } // valid } module a::n { fun calls_m_foo(): u64 { a::m::foo() // valid, also in `a` } } module b::other { fun calls_m_foo(): u64 { a::m::foo() // ERROR! // ^^^^^^^^^^^ 'foo' can only be called from a module in `a` } } ``` #### DEPRECATED `public(friend)` visibility Before the addition of `public(package)`, `public(friend)` was used to allow limited public access to functions in the same package, but where the list of allowed modules had to be explicitly enumerated by the callee's module. see [Friends](./friends) for more details. ### `entry` modifier In addition to `public` functions, you might have some functions in your modules that you want to use as the entry point to execution. The `entry` modifier is designed to allow module functions to initiate execution, without having to expose the functionality to other modules. Essentially, the combination of `public` and `entry` functions define the "main" functions of a module, and they specify where Move programs can start executing. Keep in mind though, an `entry` function _can_ still be called by other Move functions. So while they _can_ serve as the start of a Move program, they aren't restricted to that case. For example: ```move module a::m { entry fun foo(): u64 { 0 } fun calls_foo(): u64 { foo() } // valid! } module a::n { fun calls_m_foo(): u64 { a::m::foo() // ERROR! // ^^^^^^^^^^^ 'foo' is internal to 'a::m' } } ``` `entry` functions may have restrictions on their parameters and return types. Although, these restrictions are specific to each individual deployment of Move. [The documentation for `entry` functions on Sui can be found here.](https://docs.sui.io/concepts/sui-move-concepts#entry-functions) To enable easier testing, `entry` functions can be called from [`#[test]` and `#[test_only]`](./unit-testing) contexts. ```move module a::m { entry fun foo(): u64 { 0 } } module a::m_test { #[test] fun my_test(): u64 { a::m::foo() } // valid! #[test_only] fun my_test_helper(): u64 { a::m::foo() } // valid! } ``` ### `macro` modifier Unlike normal functions, `macro` functions do not exist at runtime. Instead, these functions are substituted inline at each call site during compilation. These `macro` functions leverage this compilation process to provide functionality beyond standard functions, such as accepting higher-order _lambda_-style functions as arguments. These lambda arguments, also expanded during compilation, allow you to pass parts of the function body to the macro as arguments. For instance, consider the following simple loop macro, where the loop body is supplied as a lambda: ```move macro fun n_times($n: u64, $body: |u64| -> ()) { let n = $n; let mut i = 0; while (i < n) { $body(i); i = i + 1; } } fun example() { let mut sum = 0; n_times!(10, |x| sum = sum + x ); } ``` See the chapter on [macros](./functions/macros) for more information. ### Name Function names can start with letters `a` to `z`. After the first character, function names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. ```move fun fOO() {} fun bar_42() {} fun bAZ_19() {} ``` ### Type Parameters After the name, functions can have type parameters ```move fun id(x: T): T { x } fun example(x: T1, y: T2): (T1, T1, T2) { (copy x, x, y) } ``` For more details, see [Move generics](./generics). ### Parameters Functions parameters are declared with a local variable name followed by a type annotation ```move fun add(x: u64, y: u64): u64 { x + y } ``` We read this as `x` has type `u64` A function does not have to have any parameters at all. ```move fun useless() { } ``` This is very common for functions that create new or empty data structures ```move module a::example; public struct Counter { count: u64 } fun new_counter(): Counter { Counter { count: 0 } } ``` ### Return type After the parameters, a function specifies its return type. ```move fun zero(): u64 { 0 } ``` Here `: u64` indicates that the function's return type is `u64`. Using [tuples](./primitive-types/tuples), a function can return multiple values: ```move fun one_two_three(): (u64, u64, u64) { (0, 1, 2) } ``` If no return type is specified, the function has an implicit return type of unit `()`. These functions are equivalent: ```move fun just_unit(): () { () } fun just_unit() { () } fun just_unit() { } ``` As mentioned in the [tuples section](./primitive-types/tuples), these tuple "values" do not exist as runtime values. This means that a function that returns unit `()` does not return any value during execution. ### Function body A function's body is an expression block. The return value of the function is the last value in the sequence ```move fun example(): u64 { let mut x = 0; x = x + 1; x // returns 'x' } ``` See [the section below for more information on returns](#returning-values) For more information on expression blocks, see [Move variables](./variables). ### Native Functions Some functions do not have a body specified, and instead have the body provided by the VM. These functions are marked `native`. Without modifying the VM source code, a programmer cannot add new native functions. Furthermore, it is the intent that `native` functions are used for either standard library code or for functionality needed for the given Move environment. Most `native` functions you will likely see are in standard library code, such as `vector` ```move module std::vector { native public fun length(v: &vector): u64; ... } ``` ## Calling When calling a function, the name can be specified either through an alias or fully qualified ```move module a::example { public fun zero(): u64 { 0 } } module b::other { use a::example::{Self, zero}; fun call_zero() { // With the `use` above all of these calls are equivalent a::example::zero(); example::zero(); zero(); } } ``` When calling a function, an argument must be given for every parameter. ```move module a::example { public fun takes_none(): u64 { 0 } public fun takes_one(x: u64): u64 { x } public fun takes_two(x: u64, y: u64): u64 { x + y } public fun takes_three(x: u64, y: u64, z: u64): u64 { x + y + z } } module b::other { fun call_all() { a::example::takes_none(); a::example::takes_one(0); a::example::takes_two(0, 1); a::example::takes_three(0, 1, 2); } } ``` Type arguments can be either specified or inferred. Both calls are equivalent. ```move module a::example { public fun id(x: T): T { x } } module b::other { fun call_all() { a::example::id(0); a::example::id(0); } } ``` For more details, see [Move generics](./generics). ## Returning values The result of a function, its "return value", is the final value of its function body. For example ```move fun add(x: u64, y: u64): u64 { x + y } ``` The return value here is the result of `x + y`. [As mentioned above](#function-body), the function's body is an [expression block](./variables). The expression block can sequence various statements, and the final expression in the block will be the value of that block ```move fun double_and_add(x: u64, y: u64): u64 { let double_x = x * 2; let double_y = y * 2; double_x + double_y } ``` The return value here is the result of `double_x + double_y` ### `return` expression A function implicitly returns the value that its body evaluates to. However, functions can also use the explicit `return` expression: ```move fun f1(): u64 { return 0 } fun f2(): u64 { 0 } ``` These two functions are equivalent. In this slightly more involved example, the function subtracts two `u64` values, but returns early with `0` if the second value is too large: ```move fun safe_sub(x: u64, y: u64): u64 { if (y > x) return 0; x - y } ``` Note that the body of this function could also have been written as `if (y > x) 0 else x - y`. However `return` really shines is in exiting deep within other control flow constructs. In this example, the function iterates through a vector to find the index of a given value: ```move fun index_of(v: &vector, target: &T): Option { let mut i = 0; let n = v.length(); while (i < n) { if (&v[i] == target) return option::some(i); i = i + 1 }; option::none() } ``` Using `return` without an argument is shorthand for `return ()`. That is, the following two functions are equivalent: ```move fun foo() { return } fun foo() { return () } ``` --- # Macro Functions Macro functions are a way of defining functions that are expanded during compilation at each call site. The arguments of the macro are not evaluated eagerly like a normal function, and instead are substituted by expression. In addition, the caller can supply code to the macro via [lambdas](#lambdas). These expression substitution mechanics make `macro` functions similar [to macros found in other programming languages](); however, they are more constrained in Move than you might expect from other languages. The parameters and return values of `macro` functions are still typed--though this can be partially relaxed with the [`_` type](./../generics#_-type). The upside of this restriction however, is that `macro` functions can be used anywhere a normal function can be used, which is notably helpful with [method syntax](./../method-syntax). A more extensive [syntactic macro]() system may come in the future. ## Syntax `macro` functions have a similar syntax to normal functions. However, all type parameter names and all parameter names must start with a `$`. Note that `_` can still be used by itself, but not as a prefix, and `$_` must be used instead. ```text ? macro fun <[$type_parameters: constraint],*>([$identifier: type],*): ``` For example, the following `macro` function takes a vector and a lambda, and applies the lambda to each element of the vector to construct a new vector. ```move macro fun map<$T, $U>($v: vector<$T>, $f: |$T| -> $U): vector<$U> { let mut v = $v; v.reverse(); let mut i = 0; let mut result = vector[]; while (!v.is_empty()) { result.push_back($f(v.pop_back())); i = i + 1; }; result } ``` The `$` is there to indicate that the parameters (both type and value parameters) do not behave like their normal, non-macro counterparts. For type parameters, they can be instantiated with any type (even a reference type `&` or `&mut`), and they will satisfy any constraint. Similarly for parameters, they will not be evaluated eagerly, and instead the argument expression will be substituted at each usage. ## Lambdas Lambdas are a new type of expression that can only be used with `macro`s. These are used to pass code from the caller into the body of the `macro`. While the substitution is done at compile time, they are used similarly to [anonymous functions](https://en.wikipedia.org/wiki/Anonymous_function), [lambdas](https://en.wikipedia.org/wiki/Lambda_calculus), or [closures]() in other languages. As seen in the example above (`$f: |$T| -> $U`), lambda types are defined with the syntax ```text |,*| (-> )? ``` A few examples ```move |u64, u64| -> u128 // a lambda that takes two u64s and returns a u128 |&mut vector| -> &mut u8 // a lambda that takes a &mut vector and returns a &mut u8 ``` If the return type is not annotated, it is unit `()` by default. ```move // the following are equivalent |&mut vector, u64| |&mut vector, u64| -> () ``` Lambda expressions are then defined at the call site of the `macro` with the syntax ```text |( (: )?),*| |( (: )?),*| -> { } ``` Note that if the return type is annotated, the body of the lambda must be enclosed in `{}`. Using the `map` macro defined above ```move let v = vector[1, 2, 3]; let doubled: vector = map!(v, |x| 2 * x); let bytes: vector> = map!(v, |x| std::bcs::to_bytes(&x)); ``` And with type annotations ```move let doubled: vector = map!(v, |x: u64| 2 * x); // return type annotation optional let bytes: vector> = map!(v, |x: u64| -> vector { std::bcs::to_bytes(&x) }); ``` ### Capturing Lambda expressions can also refer to variables in the scope where the lambda is defined. This is sometimes called "capturing". ```move let res = foo(); let incremented = map!(vector[1, 2, 3], |x| x + res); ``` Any variable can be captured, including mutable and immutable references. See the [Examples](#iterating-over-a-vector) section for more complicated usages. ### Limitations Currently, lambdas can only be used directly in the call of a `macro` function. They cannot be bound to a variable. For example, the following is code will produce an error: ```move let f = |x| 2 * x; // ^^^^^^^^^ Error! Lambdas must be used directly in 'macro' calls let doubled: vector = map!(vector[1, 2, 3], f); ``` ## Typing Like normal functions, `macro` functions are typed--the types of the parameters and return value must be annotated. However, the body of the function is not type checked until the macro is expanded. This means that not all usages of a given macro may be valid. For example ```move macro fun add_one<$T>($x: $T): $T { $x + 1 } ``` The above macro will not type check if `$T` is not a primitive integer type. This can be particularly useful in conjunction with [method syntax](./../method-syntax), where the function is not resolved until after the macro is expanded. ```move macro fun call_foo<$T, $U>($x: $T): &$U { $x.foo() } ``` This macro will only expand successfully if `$T` has a method `foo` that returns a reference `&$U`. As described in the [hygiene](#hygiene) section, `foo` will be resolved based on the scope where `call_foo` was defined--not where it was expanded. ### Type Parameters Type parameters can be instantiated with any type, including reference types `&` and `&mut`. They can also be instantiated with [tuple types](./../primitive-types/tuples), though the utility of this is limited currently since tuples cannot be bound to a variable. This relaxation forces the constraints of a type parameter to be satisfied at the call site in a way that does not normally occur. It is generally recommended however to add all necessary constraints to a type parameter. For example ```move public struct NoAbilities() public struct CopyBox has copy, drop { value: T } macro fun make_box<$T>($x: $T): CopyBox<$T> { CopyBox { value: $x } } ``` This macro will expand only if `$T` is instantiated with a type with the `copy` ability. ```move make_box!(1); // Valid! make_box!(NoAbilities()); // Error! 'NoAbilities' does not have the copy ability ``` The suggested declaration of `make_box` would be to add the `copy` constraint to the type parameter. This then communicates to the caller that the type must have the `copy` ability. ```move macro fun make_box<$T: copy>($x: $T): CopyBox<$T> { CopyBox { value: $x } } ``` One might reasonably ask then, why have this relaxation if the recommendation is not to use it? The constraints on type parameters simply cannot be enforced in all cases because the bodies are not checked until expansion. In the following example, the `copy` constraint on `$T` is not necessary in the signature, but is necessary in the body. ```move macro fun read_ref<$T>($r: &$T): $T { *$r } ``` If however, you want to have an extremely relaxed type signature, it is instead recommended to use the [`_` type](#_-type). ### `_` Type Normally, the [`_` placeholder type](./../generics#_-type) is used in expressions to allow for partial annotations of type arguments. However, with `macro` functions, the `_` type can be used in place of type parameters to relax the signature for any type. This should increase the ergonomics of declaring "generic" `macro` functions. For example, we could take any combination of integers and add them together. ```move macro fun add($x: _, $y: _, $z: _): u256 { ($x as u256) + ($y as u256) + ($z as u256) } ``` Additionally, the `_` type can be instantiated _multiple_ times with different types. For example ```move public struct Box has copy, drop, store { value: T } macro fun create_two($f: |_| -> Box<_>): (Box, Box) { ($f(0u8), $f(0u16)) } ``` If we declared the function with type parameters instead, the types would have to unify to a common type, which is not possible in this case. ```move macro fun create_two<$T>($f: |$T| -> Box<$T>): (Box, Box) { ($f(0u8), $f(0u16)) // ^^^^ Error! expected `u8` but found `u16` } ... let (a, b) = create_two!(|value| Box { value }); ``` In this case, `$T` must be instantiated with a single type, but inference finds that `$T` must be bound to both `u8` and `u16`. There is a tradeoff however, as the `_` type conveys less meaning and intention for the caller. Consider `map` macro from above re-declared with `_` instead of `$T` and `$U`. ```move macro fun map($v: vector<_>, $f: |_| -> _): vector<_> { ``` There is no longer any indication of behavior of `$f` at the type level. The caller must gain understanding from comments or the body of the macro. ## Expansion and Substitution The body of the `macro` is substituted into the call site at compile time. Each parameter is replaced by the _expression_, not the value, of its argument. For lambdas, additional local variables can have values bound within the context of the `macro` body. Taking a very simple example ```move macro fun apply($f: |u64| -> u64, $x: u64): u64 { $f($x) } ``` With the call site ```move let incremented = apply!(|x| x + 1, 5); ``` This will roughly be expanded to ```move let incremented = { let x = { 5 }; { x + 1 } }; ``` Again, the value of `x` is not substituted, but the expression `5` is. This might mean that an argument is evaluated multiple times, or not at all, depending on the body of the `macro`. ```move macro fun dup($f: |u64, u64| -> u64, $x: u64): u64 { $f($x, $x) } ``` ```move let sum = dup!(|x, y| x + y, foo()); ``` is expanded to ```move let sum = { let x = { foo() }; let y = { foo() }; { x + y } }; ``` Note that `foo()` will be called twice. Which would not happen if `dup` were a normal function. It is often recommended to create predictable evaluation behavior by binding arguments to local variables. ```move macro fun dup($f: |u64, u64| -> u64, $x: u64): u64 { let a = $x; $f(a, a) } ``` Now that same call site will expand to ```move let sum = { let a = { foo() }; { let x = { a }; let y = { a }; { x + y } } }; ``` ### Hygiene In the example above, the `dup` macro had a local variable `a` that was used to bind the argument `$x`. You might ask, what would happen if the variable was instead named `x`? Would that conflict with the `x` in the lambda? The short answer is, no. `macro` functions are [hygienic](https://en.wikipedia.org/wiki/Hygienic_macro), meaning that the expansion of `macro`s and lambdas will not accidentally capture variables from another scope. The compiler does this by associating a unique number with each scope. When the `macro` is expanded, the macro body gets its own scope. Additionally, the arguments are re-scoped on each usage. Modifying the `dup` macro to use `x` instead of `a` ```move macro fun dup($f: |u64, u64| -> u64, $x: u64): u64 { let a = $x; $f(a, a) } ``` The expansion of the call site ```move // let sum = dup!(|x, y| x + y, foo()); let sum = { let x#1 = { foo() }; { let x#2 = { x#1 }; let y#2 = { x#1 }; { x#2 + y#2 } } }; ``` This is an approximation of the compiler's internal representation, some details are omitted for the simplicity of this example. And each usage of an argument is re-scoped so that the different usages do not conflict. ```move macro fun apply_twice($f: |u64| -> u64, $x: u64): u64 { $f($x) + $f($x) } ``` ```move let result = apply_twice!(|x| x + 1, { let x = 5; x }); ``` Expands to ```move let result = { { let x#1 = { let x#2 = { 5 }; x#2 }; { x#1 + x#1 } } + { let x#3 = { let x#4 = { 5 }; x#4 }; { x#3 + x#3 } } }; ``` Similar to variable hygiene, [method resolution](./../method-syntax) is also scoped to the macro definition. For example ```move public struct S { f: u64, g: u64 } fun f(s: &S): u64 { s.f } fun g(s: &S): u64 { s.g } use fun f as foo; macro fun call_foo($s: &S): u64 { let s = $s; s.foo() } ``` The method call `foo` will in this case always resolve to the function `f`, even if `call_foo` is used in a scope where `foo` is bound to a different function, such as `g`. ```move fun example(s: &S): u64 { use fun g as foo; call_foo!(s) // expands to 'f(s)', not 'g(s)' } ``` Due to this though, unused `use fun` declarations might not get warnings in modules with `macro` functions. ### Control Flow Similar to variable hygiene, control flow constructs are also always scoped to where they are defined, not to where they are expanded. ```move macro fun maybe_div($x: u64, $y: u64): u64 { let x = $x; let y = $y; if (y == 0) return 0; x / y } ``` At the call site, `return` will always return from the `macro` body, not from the caller. ```move let result: vector = vector[maybe_div!(10, 0)]; ``` Will expand to ```move let result: vector = vector['a: { let x = { 10 }; let y = { 0 }; if (y == 0) return 'a 0; x / y }]; ``` Where `return 'a 0` will return to the block `'a: { ... }` and not to the caller's body. See the section on [labeled control flow](./../control-flow/labeled-control-flow) for more details. Similarly, `return` in a lambda will return from the lambda, not from the `macro` body and not from the outer function. ```move macro fun apply($f: |u64| -> u64, $x: u64): u64 { $f($x) } ``` and ```move let result = apply!(|x| { if (x == 0) return 0; x + 1 }, 100); ``` will expand to ```move let result = { let x = { 100 }; 'a: { if (x == 0) return 'a 0; x + 1 } }; ``` In addition to returning from the lambda, a label can be used to return to the outer function. In the `vector::any` macro, a `return` with a label is used to return from the entire `macro` early ```move public macro fun any<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool { let v = $v; 'any: { v.do_ref!(|e| if ($f(e)) return 'any true); false } } ``` The `return 'any true` exits from the "loop" early when the condition is met. Otherwise, the macro "returns" `false`. ### Method Syntax When applicable, `macro` functions can be called using [method syntax](./../method-syntax). When using method syntax, the evaluation of the arguments will change in that the first argument (the "receiver" of the method) will be evaluated outside of the macro expansion. This example is contrived, but will concisely demonstrate the behavior. ```move public struct S() has copy, drop; public fun foo(): S { abort 0 } public macro fun maybe_s($s: S, $cond: bool): S { if ($cond) $s else S() } ``` Even though `foo()` will abort, its return type can be used to start a method call. `$s` will not be evaluated if `$cond` is `false`, and under a normal non-method call, an argument of `foo()` would not be evaluated and would not abort. The following example demonstrates `$s` not being evaluated with an argument of `foo()`. ```move maybe_s!(foo(), false) // does not abort ``` It becomes more clear as to why it does not abort when looking at the expanded form ```move if (false) foo() else S() ``` However, when using method syntax, the first argument is evaluated before the macro is expanded. So the same argument of `foo()` for `$s` will now be evaluated and will abort. ```move foo().maybe_s!(false) // aborts ``` We can see this more clearly when looking the expanded form ```move let tmp = foo(); // aborts if (false) tmp else S() ``` Conceptually, the receiver for a method call is bound to a temporary variable before the macro is expanded, which forces the evaluation and thus the abort. ### Parameter Limitations The parameters of a `macro` function must always be used as expressions. They cannot be used in situations where the argument might be re-interpreted. For example, the following is not allowed ```move macro fun no($x: _): _ { $x.f } ``` The reason is that if the argument `$x` was not a reference, it would be borrowed first, which would could re-interpret the argument. To get around this limitation, you should bind the argument to a local variable. ```move macro fun yes($x: _): _ { let x = $x; x.f } ``` ## Examples ### Lazy arguments: assert_eq ```move macro fun assert_eq<$T>($left: $T, $right: $T, $code: u64) { let left = $left; let right = $right; if (left != right) { std::debug::print(&b"assertion failed.\n left: "); std::debug::print(&left); std::debug::print(&b"\n does not equal right: "); std::debug::print(&right); abort $code; } } ``` In this case the argument to `$code` is not evaluated unless the assertion fails. ```move assert_eq!(vector[true, false], vector[true, false], 1 / 0); // division by zero is not evaluated ``` ### Any integer square root This macro calculates the integer square root for any integer type, besides `u256`. `$T` is the type of the input and `$bitsize` is the number of bits in that type, for example `u8` has 8 bits. `$U` should be set to the next larger integer type, for example `u16` for `u8`. In this `macro`, the type of the integer literals are `1` and `0` are annotated, e.g. `(1: $U)` allowing for the type of the literal to differ with each call. Similarly, `as` can be used with the type parameters `$T` and `$U`. This macro will then only successfully expand if `$T` and `$U` are instantiated with the integer types. ```move macro fun num_sqrt<$T, $U>($x: $T, $bitsize: u8): $T { let x = $x; let mut bit = (1: $U) << $bitsize; let mut res = (0: $U); let mut x = x as $U; while (bit != 0) { if (x >= res + bit) { x = x - (res + bit); res = (res >> 1) + bit; } else { res = res >> 1; }; bit = bit >> 2; }; res as $T } ``` ### Iterating over a vector The two `macro`s iterate over a vector, immutably and mutably respectively. ```move macro fun for_imm<$T>($v: &vector<$T>, $f: |&$T|) { let v = $v; let n = v.length(); let mut i = 0; while (i < n) { $f(&v[i]); i = i + 1; } } macro fun for_mut<$T>($v: &mut vector<$T>, $f: |&mut $T|) { let v = $v; let n = v.length(); let mut i = 0; while (i < n) { $f(&mut v[i]); i = i + 1; } } ``` A few examples of usage ```move fun imm_examples(v: &vector) { // print all elements for_imm!(v, |x| std::debug::print(x)); // sum all elements let mut sum = 0; for_imm!(v, |x| sum = sum + x); // find the max element let mut max = 0; for_imm!(v, |x| if (x > max) max = x); } fun mut_examples(v: &mut vector) { // increment each element for_mut!(v, |x| *x = *x + 1); // set each element to the previous value, and the first to last value let mut prev = v[v.length() - 1]; for_mut!(v, |x| { let tmp = *x; *x = prev; prev = tmp; }); // set the max element to 0 let mut max = &mut 0; for_mut!(v, |x| if (*x > *max) max = x); *max = 0; } ``` ### Non-loop lambda usage Lambdas do not need to be used in loops, and are often useful for conditionally applying code. ```move macro fun inspect<$T>($opt: &Option<$T>, $f: |&$T|) { let opt = $opt; if (opt.is_some()) $f(opt.borrow()) } macro fun is_some_and<$T>($opt: &Option<$T>, $f: |&$T| -> bool): bool { let opt = $opt; if (opt.is_some()) $f(opt.borrow()) else false } macro fun map<$T, $U>($opt: Option<$T>, $f: |$T| -> $U): Option<$U> { let opt = $opt; if (opt.is_some()) { option::some($f(opt.destroy_some())) } else { opt.destroy_none(); option::none() } } ``` And some examples of usage ```move fun examples(opt: Option) { // print the value if it exists inspect!(&opt, |x| std::debug::print(x)); // check if the value is 0 let is_zero = is_some_and!(&opt, |x| *x == 0); // upcast the u64 to a u256 let str_opt = map!(opt, |x| x as u256); } ``` --- # Structs and Resources A _struct_ is a user-defined data structure containing typed fields. Structs can store any non-reference, non-tuple type, including other structs. Structs can be used to define all "asset" values or unrestricted values, where the operations performed on those values can be controlled by the struct's [abilities](./abilities). By default, structs are linear and ephemeral. By this we mean that they: cannot be copied, cannot be dropped, and cannot be stored in storage. This means that all values have to have ownership transferred (linear) and the values must be dealt with by the end of the program's execution (ephemeral). We can relax this behavior by giving the struct [abilities](./abilities) which allow values to be copied or dropped and also to be stored in storage or to define storage schemas. ## Defining Structs Structs must be defined inside a module, and the struct's fields can either be named or positional: ```move module a::m; public struct Foo { x: u64, y: bool } public struct Bar {} public struct Baz { foo: Foo, } // ^ note: it is fine to have a trailing comma public struct PosFoo(u64, bool) public struct PosBar() public struct PosBaz(Foo) ``` Structs cannot be recursive, so the following definitions are invalid: ```move public struct Foo { x: Foo } // ^ ERROR! recursive definition public struct A { b: B } public struct B { a: A } // ^ ERROR! recursive definition public struct D(D) // ^ ERROR! recursive definition ``` ### Visibility As you may have noticed, all structs are declared as `public`. This means that the type of the struct can be referred to from any other module. However, the fields of the struct, and the ability to create or destroy the struct, are still internal to the module that defines the struct. In the future, we plan on adding to declare structs as `public(package)` or as internal, much like [functions](./functions#visibility). ### Abilities As mentioned above: by default, a struct declaration is linear and ephemeral. So to allow the value to be used in these ways (e.g., copied, dropped, stored in an [object](./abilities/object), or used to define a storable [object](./abilities/object)), structs can be granted [abilities](./abilities) by annotating them with `has `: ```move module a::m { public struct Foo has copy, drop { x: u64, y: bool } } ``` The ability declaration can occur either before or after the struct's fields. However, only one or the other can be used, and not both. If declared after the struct's fields, the ability declaration must be terminated with a semicolon: ```move module a::m; public struct PreNamedAbilities has copy, drop { x: u64, y: bool } public struct PostNamedAbilities { x: u64, y: bool } has copy, drop; public struct PostNamedAbilitiesInvalid { x: u64, y: bool } has copy, drop // ^ ERROR! missing semicolon public struct NamedInvalidAbilities has copy { x: u64, y: bool } has drop; // ^ ERROR! duplicate ability declaration public struct PrePositionalAbilities has copy, drop (u64, bool) public struct PostPositionalAbilities (u64, bool) has copy, drop; public struct PostPositionalAbilitiesInvalid (u64, bool) has copy, drop // ^ ERROR! missing semicolon public struct InvalidAbilities has copy (u64, bool) has drop; // ^ ERROR! duplicate ability declaration ``` For more details, see the section on [annotating a struct's abilities](./abilities#annotating-structs-and-enums). ### Naming Structs must start with a capital letter `A` to `Z`. After the first letter, struct names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. ```move public struct Foo {} public struct BAR {} public struct B_a_z_4_2 {} public struct P_o_s_Foo() ``` This naming restriction of starting with `A` to `Z` is in place to give room for future language features. It may or may not be removed later. ## Using Structs ### Creating Structs Values of a struct type can be created (or "packed") by indicating the struct name, followed by value for each field. For a struct with named fields, the order of the fields does not matter, but the field name needs to be provided. For a struct with positional fields, the order of the fields must match the order of the fields in the struct definition, and it must be created using `()` instead of `{}` to enclose the parameters. ```move module a::m; public struct Foo has drop { x: u64, y: bool } public struct Baz has drop { foo: Foo } public struct Positional(u64, bool) has drop; fun example() { let foo = Foo { x: 0, y: false }; let baz = Baz { foo: foo }; // Note: positional struct values are created using parentheses and // based on position instead of name. let pos = Positional(0, false); let pos_invalid = Positional(false, 0); // ^ ERROR! Fields are out of order and the types don't match. } ``` For structs with named fields, you can use the following shorthand if you have a local variable with the same name as the field: ```move let baz = Baz { foo: foo }; // is equivalent to let baz = Baz { foo }; ``` This is sometimes called "field name punning". ### Destroying Structs via Pattern Matching Struct values can be destroyed by binding or assigning them in patterns using similar syntax to constructing them. ```move module a::m; public struct Foo { x: u64, y: bool } public struct Bar(Foo) public struct Baz {} public struct Qux() fun example_destroy_foo() { let foo = Foo { x: 3, y: false }; let Foo { x, y: foo_y } = foo; // ^ shorthand for `x: x` // two new bindings // x: u64 = 3 // foo_y: bool = false } fun example_destroy_foo_wildcard() { let foo = Foo { x: 3, y: false }; let Foo { x, y: _ } = foo; // only one new binding since y was bound to a wildcard // x: u64 = 3 } fun example_destroy_foo_assignment() { let x: u64; let y: bool; Foo { x, y } = Foo { x: 3, y: false }; // mutating existing variables x and y // x = 3, y = false } fun example_foo_ref() { let foo = Foo { x: 3, y: false }; let Foo { x, y } = &foo; // two new bindings // x: &u64 // y: &bool } fun example_foo_ref_mut() { let foo = Foo { x: 3, y: false }; let Foo { x, y } = &mut foo; // two new bindings // x: &mut u64 // y: &mut bool } fun example_destroy_bar() { let bar = Bar(Foo { x: 3, y: false }); let Bar(Foo { x, y }) = bar; // ^ nested pattern // two new bindings // x: u64 = 3 // y: bool = false } fun example_destroy_baz() { let baz = Baz {}; let Baz {} = baz; } fun example_destroy_qux() { let qux = Qux(); let Qux() = qux; } ``` ### Accessing Struct Fields Fields of a struct can be accessed using the dot operator `.`. For structs with named fields, the fields can be accessed by their name: ```move public struct Foo { x: u64, y: bool } let foo = Foo { x: 3, y: true }; let x = foo.x; // x == 3 let y = foo.y; // y == true ``` For positional structs, fields can be accessed by their position in the struct definition: ```move public struct PosFoo(u64, bool) let pos_foo = PosFoo(3, true); let x = pos_foo.0; // x == 3 let y = pos_foo.1; // y == true ``` Accessing struct fields without borrowing or copying them is subject to the field's ability constraints. For more details see the sections on [borrowing structs and fields](#borrowing-structs-and-fields) and [reading and writing fields](#reading-and-writing-fields) for more information. ### Borrowing Structs and Fields The `&` and `&mut` operator can be used to create references to structs or fields. These examples include some optional type annotations (e.g., `: &Foo`) to demonstrate the type of operations. ```move let foo = Foo { x: 3, y: true }; let foo_ref: &Foo = &foo; let y: bool = foo_ref.y; // reading a field via a reference to the struct let x_ref: &u64 = &foo.x; // borrowing a field by extending a reference to the struct let x_ref_mut: &mut u64 = &mut foo.x; *x_ref_mut = 42; // modifying a field via a mutable reference ``` It is possible to borrow inner fields of nested structs: ```move let foo = Foo { x: 3, y: true }; let bar = Bar(foo); let x_ref = &bar.0.x; ``` You can also borrow a field via a reference to a struct: ```move let foo = Foo { x: 3, y: true }; let foo_ref = &foo; let x_ref = &foo_ref.x; // this has the same effect as let x_ref = &foo.x ``` ### Reading and Writing Fields If you need to read and copy a field's value, you can then dereference the borrowed field: ```move let foo = Foo { x: 3, y: true }; let bar = Bar(copy foo); let x: u64 = *&foo.x; let y: bool = *&foo.y; let foo2: Foo = *&bar.0; ``` More canonically, the dot operator can be used to read fields of a struct without any borrowing. As is true with [dereferencing](./primitive-types/references#reading-and-writing-through-references), the field type must have the `copy` [ability](./abilities). ```move let foo = Foo { x: 3, y: true }; let x = foo.x; // x == 3 let y = foo.y; // y == true ``` Dot operators can be chained to access nested fields: ```move let bar = Bar(Foo { x: 3, y: true }); let x = baz.0.x; // x = 3; ``` However, this is not permitted for fields that contain non-primitive types, such a vector or another struct: ```move let foo = Foo { x: 3, y: true }; let bar = Bar(foo); let foo2: Foo = *&bar.0; let foo3: Foo = bar.0; // error! must add an explicit copy with *& ``` We can mutably borrow a field to a struct to assign it a new value: ```move let mut foo = Foo { x: 3, y: true }; *&mut foo.x = 42; // foo = Foo { x: 42, y: true } *&mut foo.y = !foo.y; // foo = Foo { x: 42, y: false } let mut bar = Bar(foo); // bar = Bar(Foo { x: 42, y: false }) *&mut bar.0.x = 52; // bar = Bar(Foo { x: 52, y: false }) *&mut bar.0 = Foo { x: 62, y: true }; // bar = Bar(Foo { x: 62, y: true }) ``` Similar to dereferencing, we can instead directly use the dot operator to modify a field. And in both cases, the field type must have the `drop` [ability](./abilities). ```move let mut foo = Foo { x: 3, y: true }; foo.x = 42; // foo = Foo { x: 42, y: true } foo.y = !foo.y; // foo = Foo { x: 42, y: false } let mut bar = Bar(foo); // bar = Bar(Foo { x: 42, y: false }) bar.0.x = 52; // bar = Bar(Foo { x: 52, y: false }) bar.0 = Foo { x: 62, y: true }; // bar = Bar(Foo { x: 62, y: true }) ``` The dot syntax for assignment also works via a reference to a struct: ```move let mut foo = Foo { x: 3, y: true }; let foo_ref = &mut foo; foo_ref.x = foo_ref.x + 1; ``` ## Privileged Struct Operations Most struct operations on a struct type `T` can only be performed inside the module that declares `T`: - Struct types can only be created ("packed"), destroyed ("unpacked") inside the module that defines the struct. - The fields of a struct are only accessible inside the module that defines the struct. Following these rules, if you want to modify your struct outside the module, you will need to provide public APIs for them. The end of the chapter contains some examples of this. However as stated [in the visibility section above](#visibility), struct _types_ are always visible to another module ```move module a::m { public struct Foo has drop { x: u64 } public fun new_foo(): Foo { Foo { x: 42 } } } module a::n { use a::m::Foo; public struct Wrapper has drop { foo: Foo // ^ valid the type is public } fun f1(foo: Foo) { let x = foo.x; // ^ ERROR! cannot access fields of `Foo` outside of `a::m` } fun f2() { let foo_wrapper = Wrapper { foo: a::m::new_foo() }; // ^ valid the function is public } } ``` ## Ownership As mentioned above in [Defining Structs](#defining-structs), structs are by default linear and ephemeral. This means they cannot be copied or dropped. This property can be very useful when modeling real world assets like money, as you do not want money to be duplicated or get lost in circulation. ```move module a::m; public struct Foo { x: u64 } public fun copying() { let foo = Foo { x: 100 }; let foo_copy = copy foo; // ERROR! 'copy'-ing requires the 'copy' ability let foo_ref = &foo; let another_copy = *foo_ref // ERROR! dereference requires the 'copy' ability } public fun destroying_1() { let foo = Foo { x: 100 }; // error! when the function returns, foo still contains a value. // This destruction requires the 'drop' ability } public fun destroying_2(f: &mut Foo) { *f = Foo { x: 100 } // error! // destroying the old value via a write requires the 'drop' ability } ``` To fix the example `fun destroying_1`, you would need to manually "unpack" the value: ```move module a::m; public struct Foo { x: u64 } public fun destroying_1_fixed() { let foo = Foo { x: 100 }; let Foo { x: _ } = foo; } ``` Recall that you are only able to deconstruct a struct within the module in which it is defined. This can be leveraged to enforce certain invariants in a system, for example, conservation of money. If on the other hand, your struct does not represent something valuable, you can add the abilities `copy` and `drop` to get a struct value that might feel more familiar from other programming languages: ```move module a::m; public struct Foo has copy, drop { x: u64 } public fun run() { let foo = Foo { x: 100 }; let foo_copy = foo; // ^ this code copies foo, // whereas `let x = move foo` would move foo let x = foo.x; // x = 100 let x_copy = foo_copy.x; // x = 100 // both foo and foo_copy are implicitly discarded when the function returns } ``` ## Storage Structs can be used to define storage schemas, but the details are different per deployment of Move. See the documentation for the [`key` ability](./abilities#key) and [Sui objects](./abilities/object) for more details. --- # Enumerations An _enum_ is a user-defined data structure containing one or more _variants_. Each variant can optionally contain typed fields. The number, and types of these fields can differ for each variant in the enumeration. Fields in enums can store any non-reference, non-tuple type, including other structs or enums. As a simple example, consider the following enum definition in Move: ```move public enum Action { Stop, Pause { duration: u32 }, MoveTo { x: u64, y: u64 }, Jump(u64), } ``` This declares an enum `Action` that represents different actions that can be taken by a game -- you can `Stop`, `Pause` for a given duration, `MoveTo` a specific location, or `Jump` to a specific height. Similar to structs, enums can have [abilities](./abilities) that control what operations can be performed on them. It is important to note however that enums cannot have the `key` ability since they cannot be top-level objects. ## Defining Enums Enums must be defined in a module, an enum must contain at least one variant, and each variant of an enum can either have no fields, positional fields, or named fields. Here are some examples of each: ```move module a::m; public enum Foo has drop { VariantWithNoFields, // ^ note: it is fine to have a trailing comma after variant declarations } public enum Bar has copy, drop { VariantWithPositionalFields(u64, bool), } public enum Baz has drop { VariantWithNamedFields { x: u64, y: bool, z: Bar }, } ``` Enums cannot be recursive in any of their variants, so the following definitions of an enum are not allowed because they would be recursive in at least one variant. Incorrect: ```move module a::m; public enum Foo { Recursive(Foo), // ^ error: recursive enum variant } public enum List { Nil, Cons { head: u64, tail: List }, // ^ error: recursive enum variant } public enum BTree { Leaf(T), Node { left: BTree, right: BTree }, // ^ error: recursive enum variant } // Mutually recursive enums are also not allowed public enum MutuallyRecursiveA { Base, Other(MutuallyRecursiveB), // ^^^^^^^^^^^^^^^^^^ error: recursive enum variant } public enum MutuallyRecursiveB { Base, Other(MutuallyRecursiveA), // ^^^^^^^^^^^^^^^^^^ error: recursive enum variant } ``` ## Visibility All enums are declared as `public`. This means that the type of the enum can be referred to from any other module. However, the variants of the enum, the fields within each variant, and the ability to create or destroy variants of the enum are internal to the module that defines the enum. ### Abilities Just like with structs, by default an enum declaration is linear and ephemeral. To use an enum value in a non-linear or non-ephemeral way -- i.e., copied, dropped, or stored in an [object](./abilities/object) -- you need to grant it additional [abilities](./abilities) by annotating them with `has `: ```move module a::m; public enum Foo has copy, drop { VariantWithNoFields, } ``` The ability declaration can occur either before or after the enum's variants, however only one or the other can be used, and not both. If declared after the variants, the ability declaration must be terminated with a semicolon: ```move module a::m; public enum PreNamedAbilities has copy, drop { Variant } public enum PostNamedAbilities { Variant } has copy, drop; public enum PostNamedAbilitiesInvalid { Variant } has copy, drop // ^ ERROR! missing semicolon public enum NamedInvalidAbilities has copy { Variant } has drop; // ^ ERROR! duplicate ability declaration ``` For more details, see the section on [annotating abilities](./abilities#annotating-structs-and-enums). ## Naming Enums and variants within enums must start with a capital letter `A` to `Z`. After the first letter, enum names can contain underscores `_`, lowercase letters `a` to `z`, uppercase letters `A` to `Z`, or digits `0` to `9`. ```move public enum Foo { Variant } public enum BAR { Variant } public enum B_a_z_4_2 { V_a_riant_0 } ``` This naming restriction of starting with `A` to `Z` is in place to give room for future language features. ## Using Enums ### Creating Enum Variants Values of an enum type can be created (or "packed") by indicating a variant of the enum, followed by a value for each field in the variant. The variant name must always be qualified by the enum's name. Similarly to structs, for a variant with named fields, the order of the fields does not matter but the field names need to be provided. For a variant with positional fields, the order of the fields matters and the order of the fields must match the order in the variant declaration. It must also be created using `()` instead of `{}`. If the variant has no fields, the variant name is sufficient and no `()` or `{}` needs to be used. ```move module a::m; public enum Action has drop { Stop, Pause { duration: u32 }, MoveTo { x: u64, y: u64 }, Jump(u64), } public enum Other has drop { Stop(u64), } fun example() { // Note: The `Stop` variant of `Action` doesn't have fields so no parentheses or curlies are needed. let stop = Action::Stop; let pause = Action::Pause { duration: 10 }; let move_to = Action::MoveTo { x: 10, y: 20 }; let jump = Action::Jump(10); // Note: The `Stop` variant of `Other` does have positional fields so we need to supply them. let other_stop = Other::Stop(10); } ``` For variants with named fields you can also use the shorthand syntax that you might be familiar with from structs to create the variant: ```move let duration = 10; let pause = Action::Pause { duration: duration }; // is equivalent to let pause = Action::Pause { duration }; ``` ### Pattern Matching Enum Variants and Destructuring Since enum values can take on different shapes, dot access to fields of variants is not allowed like it is for struct fields. Instead, to access fields within a variant -- either by value, or immutable or mutable reference -- you must use pattern matching. You can pattern match on Move values by value, immutable reference, and mutable reference. When pattern matching by value, the value is moved into the match arm. When pattern matching by reference, the value is borrowed into the match arm (either immutably or mutably). We'll go through a brief description of pattern matching using `match` here, but for more information on pattern matching using `match` in Move see the [Pattern Matching](./control-flow/pattern-matching) section. A `match` statement is used to pattern match on a Move value and consists of a number of _match arms_. Each match arm consists of a pattern, an arrow `=>`, and an expression, followed by a comma `,`. The pattern can be a struct, enum variant, binding (`x`, `y`), wildcard (`_` or `..`), constant (`ConstValue`), or literal value (`true`, `42`, and so on). The value is matched against each pattern from the top-down, and will match the first pattern that structurally matches the value. Once the value is matched, the expression on the right hand side of the `=>` is executed. Additionally, match arms can have optional _guards_ that are checked after the pattern matches but _before_ the expression is executed. Guards are specified by the `if` keyword followed by an expression that must evaluate to a boolean value before the `=>`. ```move module a::m; public enum Action has drop { Stop, Pause { duration: u32 }, MoveTo { x: u64, y: u64 }, Jump(u64), } public struct GameState { // Fields containing a game state character_x: u64, character_y: u64, character_height: u64, // ... } fun perform_action(stat: &mut GameState, action: Action) { match (action) { // Handle the `Stop` variant Action::Stop => state.stop(), // Handle the `Pause` variant // If the duration is 0, do nothing Action::Pause { duration: 0 } => (), Action::Pause { duration } => state.pause(duration), // Handle the `MoveTo` variant Action::MoveTo { x, y } => state.move_to(x, y), // Handle the `Jump` variant // if the game disallows jumps then do nothing Action::Jump(_) if (state.jumps_not_allowed()) => (), // otherwise, jump to the specified height Action::Jump(height) => state.jump(height), } } ``` To see how to pattern match on an enum to update values within it mutably, let's take the following example of a simple enum that has two variants, each with a single field. We can then write two functions, one that only increments the value of the first variant, and another that only increments the value of the second variant: ```move module a::m; public enum SimpleEnum { Variant1(u64), Variant2(u64), } public fun incr_enum_variant1(simple_enum: &mut SimpleEnum) { match (simple_enum) { SimpleEnum::Variant1(mut value) => *value += 1, _ => (), } } public fun incr_enum_variant2(simple_enum: &mut SimpleEnum) { match (simple_enum) { SimpleEnum::Variant2(mut value) => *value += 1, _ => (), } } ``` Now, if we have a value of `SimpleEnum` we can use the functions to increment the value of this variant: ```move let mut x = SimpleEnum::Variant1(10); incr_enum_variant1(&mut x); assert!(x == SimpleEnum::Variant1(11)); // Doesn't increment since it increments a different variant incr_enum_variant2(&mut x); assert!(x == SimpleEnum::Variant1(11)); ``` When pattern matching on a Move value that does not have the `drop` ability, the value must be consumed or destructured in each match arm. If the value is not consumed or destructured in a match arm, the compiler will raise an error. This is to ensure that all possible values are handled in the match statement. As an example, consider the following code: ```move module a::m; public enum X { Variant { x: u64 } } public fun bad(x: X) { match (x) { _ => (), // ^ ERROR! value of type `X` is not consumed or destructured in this match arm } } ``` To properly handle this, you will need to destructure `X` and all its variants in the match's arm(s): ```move module a::m; public enum X { Variant { x: u64 } } public fun good(x: X) { match (x) { // OK! Compiles since the value is destructured X::Variant { x: _ } => (), } } ``` ### Overwriting to Enum Values As long as the enum has the `drop` ability, you can overwrite the value of an enum with a new value of the same type just as you might with other values in Move. ```move module a::m; public enum X has drop { A(u64), B(u64), } public fun overwrite_enum(x: &mut X) { *x = X::A(10); } ``` ```move let mut x = X::B(20); overwrite_enum(&mut x); assert!(x == X::A(10)); ``` --- # Constants Constants are a way of giving a name to shared, static values inside of a `module`. The constant's value must be known at compilation. The constant's value is stored in the compiled module. And each time the constant is used, a new copy of that value is made. ## Declaration Constant declarations begin with the `const` keyword, followed by a name, a type, and a value. ```text const : = ; ``` For example ```move module a::example; const MY_ADDRESS: address = @a; public fun permissioned(addr: address) { assert!(addr == MY_ADDRESS, 0); } ``` ## Naming Constants must start with a capital letter `A` to `Z`. After the first letter, constant names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. ```move const FLAG: bool = false; const EMyErrorCode: u64 = 0; const ADDRESS_42: address = @0x42; ``` Even though you can use letters `a` to `z` in a constant. The [general style guidelines](./coding-conventions) are to use just uppercase letters `A` to `Z`, with underscores `_` between each word. For error codes, we use `E` as a prefix and then upper camel case (also known as Pascal case) for the rest of the name, as seen in `EMyErrorCode`. The current naming restriction of starting with `A` to `Z` is in place to give room for future language features. ## Visibility `public` or `public(package)` constants are not currently supported. `const` values can be used only in the declaring module. However, as a convenience, they can be used across modules in [unit tests attributes](./unit-testing). ## Valid Expressions Currently, constants are limited to the primitive types `bool`, `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, `address`, and `vector`, where `T` is the valid type for a constant. ### Values Commonly, `const`s are assigned a simple value, or literal, of their type. For example ```move const MY_BOOL: bool = false; const MY_ADDRESS: address = @0x70DD; const BYTES: vector = b"hello world"; const HEX_BYTES: vector = x"DEADBEEF"; ``` ### Complex Expressions In addition to literals, constants can include more complex expressions, as long as the compiler is able to reduce the expression to a value at compile time. Currently, equality operations, all boolean operations, all bitwise operations, and all arithmetic operations can be used. ```move const RULE: bool = true && false; const CAP: u64 = 10 * 100 + 1; const SHIFTY: u8 = { (1 << 1) * (1 << 2) * (1 << 3) * (1 << 4) }; const HALF_MAX: u128 = 340282366920938463463374607431768211455 / 2; const REM: u256 = 57896044618658097711785492504343953926634992332820282019728792003956564819968 % 654321; const EQUAL: bool = 1 == 1; ``` If the operation would result in a runtime exception, the compiler will give an error that it is unable to generate the constant's value ```move const DIV_BY_ZERO: u64 = 1 / 0; // ERROR! const SHIFT_BY_A_LOT: u64 = 1 << 100; // ERROR! const NEGATIVE_U64: u64 = 0 - 1; // ERROR! ``` Additionally, constants can refer to other constants within the same module. ```move const BASE: u8 = 4; const SQUARE: u8 = BASE * BASE; ``` Note though, that any cycle in the constant definitions results in an error. ```move const A: u16 = B + 1; const B: u16 = A + 1; // ERROR! ``` --- # Generics Generics can be used to define functions and structs over different input data types. This language feature is sometimes referred to as parametric polymorphism. In Move, we will often use the term generics interchangeably with _type parameters_ and _type arguments_. Generics are commonly used in library code, such as in [vector](./primitive-types/vector), to declare code that works over any possible type (that satisfies the specified constraints). This sort of parameterization allows you to reuse the same implementation across multiple types and situations. ## Declaring Type Parameters Both functions and structs can take a list of type parameters in their signatures, enclosed by a pair of angle brackets `<...>`. ### Generic Functions Type parameters for functions are placed after the function name and before the (value) parameter list. The following code defines a generic identity function that takes a value of any type and returns that value unchanged. ```move fun id(x: T): T { // this type annotation is unnecessary but valid (x: T) } ``` Once defined, the type parameter `T` can be used in parameter types, return types, and inside the function body. ### Generic Structs Type parameters for structs are placed after the struct name, and can be used to name the types of the fields. ```move public struct Foo has copy, drop { x: T } public struct Bar has copy, drop { x: T1, y: vector, } ``` Note that [type parameters do not have to be used](#unused-type-parameters) ## Type Arguments ### Calling Generic Functions When calling a generic function, one can specify the type arguments for the function's type parameters in a list enclosed by a pair of angle brackets. ```move fun foo() { let x = id(true); } ``` If you do not specify the type arguments, Move's [type inference](#type-inference) will supply them for you. ### Using Generic Structs Similarly, one can attach a list of type arguments for the struct's type parameters when constructing or destructing values of generic types. ```move fun foo() { // type arguments on construction let foo = Foo { x: true }; let bar = Bar { x: 0, y: vector[] }; // type arguments on destruction let Foo { x } = foo; let Bar { x, y } = bar; } ``` In any case if you do not specify the type arguments, Move's [type inference](#type-inference) will supply them for you. ### Type Argument Mismatch If you specify the type arguments and they conflict with the actual values supplied, an error will be given: ```move fun foo() { let x = id(true); // ERROR! true is not a u64 } ``` and similarly: ```move fun foo() { let foo = Foo { x: 0 }; // ERROR! 0 is not a bool let Foo
{ x } = foo; // ERROR! bool is incompatible with address } ``` ## Type Inference In most cases, the Move compiler will be able to infer the type arguments so you don't have to write them down explicitly. Here's what the examples above would look like if we omit the type arguments: ```move fun foo() { let x = id(true); // ^ is inferred let foo = Foo { x: true }; // ^ is inferred let Foo { x } = foo; // ^ is inferred } ``` Note: when the compiler is unable to infer the types, you'll need annotate them manually. A common scenario is to call a function with type parameters appearing only at return positions. ```move module a::m; fun foo() { let v = vector[]; // ERROR! // ^ The compiler cannot figure out the element type, since it is never used let v = vector[]; // ^~~~~ Must annotate manually in this case. } ``` Note that these cases are a bit contrived since the `vector[]` is never used, ad as such, Move's type inference cannot infer the type. However, the compiler will be able to infer the type if that value is used later in that function: ```move module a::m; fun foo() { let v = vector[]; // ^ is inferred vector::push_back(&mut v, 42); // ^ is inferred } ``` ### `_` Type In some cases, you might want to explicitly annotate some of the type arguments, but let the compiler infer the others. The `_` type serves as such a placeholder for the compiler to infer the type. ```move let bar = Bar { x: 0, y: vector[b"hello"] }; // ^ vector is inferred ``` The placeholder `_` may only appear in expressions and macro function definitions, not signatures. This means you cannot use `_` as part of the definition of a function parameter, function return type, constant definition type, and datatype field. ## Integers In Move, the integer types `u8`, `u16`, `u32`, `u64`, `u128`, and `u256` are all distinct types. However, each one of these types can be created with the same numerical value syntax. In other words, if a type suffix is not provided, the compiler will infer the integer type based on the usage of the value. ```move let x8: u8 = 0; let x16: u16 = 0; let x32: u32 = 0; let x64: u64 = 0; let x128: u128 = 0; let x256: u256 = 0; ``` If the value is not used in a context that requires a specific integer type, `u64` is taken as a default. ```move let x = 0; // ^ u64 is used by default ``` If the value however is too large for the inferred type, an error will be given ```move let i: u8 = 256; // ERROR! // ^^^ too large for u8 let x = 340282366920938463463374607431768211454; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ too large for u64 ``` In cases where the number is too large, you might need to annotate it explicitly ```move let x = 340282366920938463463374607431768211454u128; // ^^^^ valid! ``` ## Unused Type Parameters For a struct definition, an unused type parameter is one that does not appear in any field defined in the struct, but is checked statically at compile time. Move allows unused type parameters so the following struct definition is valid: ```move public struct Foo { foo: u64 } ``` This can be convenient when modeling certain concepts. Here is an example: ```move module a::m; // Currency Specifiers public struct A {} public struct B {} // A generic coin type that can be instantiated using a currency // specifier type. // e.g. Coin, Coin etc. public struct Coin has store { value: u64 } // Write code generically about all currencies public fun mint_generic(value: u64): Coin { Coin { value } } // Write code concretely about one currency public fun mint_a(value: u64): Coin { mint_generic(value) } public fun mint_b(value: u64): Coin { mint_generic(value) } ``` In this example, `Coin` is generic on the `Currency` type parameter, which specifies the currency of the coin and allows code to be written either generically on any currency or concretely on a specific currency. This generality applies even when the `Currency` type parameter does not appear in any of the fields defined in `Coin`. ### Phantom Type Parameters In the example above, although `struct Coin` asks for the `store` ability, neither `Coin` nor `Coin` will have the `store` ability. This is because of the rules for [Conditional Abilities and Generic Types](./abilities#conditional-abilities-and-generic-types) and the fact that `A` and `B` don't have the `store` ability, despite the fact that they are not even used in the body of `struct Coin`. This might cause some unpleasant consequences. For example, we are unable to put `Coin` into a wallet in storage. One possible solution would be to add spurious ability annotations to `A` and `B` (i.e., `public struct Currency1 has store {}`). But, this might lead to bugs or security vulnerabilities because it weakens the types with unnecessary ability declarations. For example, we would never expect a value in the storage to have a field in type `A`, but this would be possible with the spurious `store` ability. Moreover, the spurious annotations would be infectious, requiring many functions generic on the unused type parameter to also include the necessary constraints. Phantom type parameters solve this problem. Unused type parameters can be marked as _phantom_ type parameters, which do not participate in the ability derivation for structs. In this way, arguments to phantom type parameters are not considered when deriving the abilities for generic types, thus avoiding the need for spurious ability annotations. For this relaxed rule to be sound, Move's type system guarantees that a parameter declared as `phantom` is either not used at all in the struct definition, or it is only used as an argument to type parameters also declared as `phantom`. #### Declaration In a struct definition a type parameter can be declared as phantom by adding the `phantom` keyword before its declaration. ```move public struct Coin has store { value: u64 } ``` If a type parameter is declared as phantom we say it is a phantom type parameter. When defining a struct, Move's type checker ensures that every phantom type parameter is either not used inside the struct definition or it is only used as an argument to a phantom type parameter. ```move public struct S1 { f: u64 } // ^^^^^^^ valid, T1 does not appear inside the struct definition public struct S2 { f: S1 } // ^^^^^^^ valid, T1 appears in phantom position ``` The following code shows examples of violations of the rule: ```move public struct S1 { f: T } // ^^^^^^^ ERROR! ^ Not a phantom position public struct S2 { f: T } public struct S3 { f: S2 } // ^^^^^^^ ERROR! ^ Not a phantom position ``` More formally, if a type is used as an argument to a phantom type parameter we say the type appears in _phantom position_. With this definition in place, the rule for the correct use of phantom parameters can be specified as follows: **A phantom type parameter can only appear in phantom position**. Note that specifying `phantom` is not required, but the compiler will warn if a type parameter could be `phantom` but was not marked as such. #### Instantiation When instantiating a struct, the arguments to phantom parameters are excluded when deriving the struct abilities. For example, consider the following code: ```move public struct S has copy { f: T1 } public struct NoCopy {} public struct HasCopy has copy {} ``` Consider now the type `S`. Since `S` is defined with `copy` and all non-phantom arguments have `copy` then `S` also has `copy`. #### Phantom Type Parameters with Ability Constraints Ability constraints and phantom type parameters are orthogonal features in the sense that phantom parameters can be declared with ability constraints. ```move public struct S {} ``` When instantiating a phantom type parameter with an ability constraint, the type argument has to satisfy that constraint, even though the parameter is phantom. The usual restrictions apply and `T` can only be instantiated with arguments having `copy`. ## Constraints In the examples above, we have demonstrated how one can use type parameters to define "unknown" types that can be plugged in by callers at a later time. This however means the type system has little information about the type and has to perform checks in a very conservative way. In some sense, the type system must assume the worst case scenario for an unconstrained generic--a type with no [abilities](./abilities). Constraints offer a way to specify what properties these unknown types have so the type system can allow operations that would otherwise be unsafe. ### Declaring Constraints Constraints can be imposed on type parameters using the following syntax. ```move // T is the name of the type parameter T: (+ )* ``` The `` can be any of the four [abilities](./abilities), and a type parameter can be constrained with multiple abilities at once. So all of the following would be valid type parameter declarations: ```move T: copy T: copy + drop T: copy + drop + store + key ``` ### Verifying Constraints Constraints are checked at instantiation sites ```move public struct Foo { x: T } public struct Bar { x: Foo } // ^^ valid, u8 has `copy` public struct Baz { x: Foo } // ^ ERROR! T does not have 'copy' ``` And similarly for functions ```move fun unsafe_consume(x: T) { // ERROR! x does not have 'drop' } fun consume(x: T) { // valid, x will be dropped automatically } public struct NoAbilities {} fun foo() { let r = NoAbilities {}; consume(NoAbilities); // ^^^^^^^^^^^ ERROR! NoAbilities does not have 'drop' } ``` And some similar examples, but with `copy` ```move fun unsafe_double(x: T) { (copy x, x) // ERROR! T does not have 'copy' } fun double(x: T) { (copy x, x) // valid, T has 'copy' } public struct NoAbilities {} fun foo(): (NoAbilities, NoAbilities) { let r = NoAbilities {}; double(r) // ^ ERROR! NoAbilities does not have 'copy' } ``` For more information, see the abilities section on [conditional abilities and generic types](./abilities#conditional-abilities-and-generic-types). ## Limitations on Recursions ### Recursive Structs Generic structs can not contain fields of the same type, either directly or indirectly, even with different type arguments. All of the following struct definitions are invalid: ```move public struct Foo { x: Foo // ERROR! 'Foo' containing 'Foo' } public struct Bar { x: Bar // ERROR! 'Bar' containing 'Bar' } // ERROR! 'A' and 'B' forming a cycle, which is not allowed either. public struct A { x: B } public struct B { x: A y: A } ``` ### Advanced Topic: Type-level Recursions Move allows generic functions to be called recursively. However, when used in combination with generic structs, this could create an infinite number of types in certain cases, and allowing this means adding unnecessary complexity to the compiler, vm and other language components. Therefore, such recursions are forbidden. This restriction might be relaxed in the future, but for now, the following examples should give you an idea of what is allowed and what is not. ```move module a::m; public struct A {} // Finitely many types -- allowed. // foo -> foo -> foo -> ... is valid fun foo() { foo(); } // Finitely many types -- allowed. // foo -> foo> -> foo> -> ... is valid fun foo() { foo>(); } ``` Not allowed: ```move module a::m; public struct A {} // Infinitely many types -- NOT allowed. // error! // foo -> foo> -> foo>> -> ... fun foo() { foo>(); } ``` And similarly, not allowed: ```move module a::n; public struct A {} // Infinitely many types -- NOT allowed. // error! // foo -> bar -> foo> // -> bar, T2> -> foo, A> // -> bar, A> -> foo, A>> // -> ... fun foo() { bar(); } fun bar { foo>(); } ``` Note, the check for type level recursions is based on a conservative analysis on the call sites and does NOT take control flow or runtime values into account. ```move module a::m; public struct A {} // Infinitely many types -- NOT allowed. // error! fun foo(n: u64) { if (n > 0) foo>(n - 1); } ``` The function in the example above will technically terminate for any given input and therefore only creating finitely many types, but it is still considered invalid by Move's type system. --- # Abilities Abilities are a typing feature in Move that control what actions are permissible for values of a given type. This system grants fine grained control over the "linear" typing behavior of values, as well as if and how values are used in storage (as defined by the specific deployment of Move, e.g. the notion of storage for the blockchain). This is implemented by gating access to certain bytecode instructions so that for a value to be used with the bytecode instruction, it must have the ability required (if one is required at all—not every instruction is gated by an ability). For Sui, `key` is used to signify an [object](./abilities/object). Objects are the basic unit of storage where each object has a unique, 32-byte ID. `store` is then used to both indicate what data can be stored inside of an object, and is also used to indicate what types can be transferred outside of their defining module. ## The Four Abilities The four abilities are: - [`copy`](#copy) - Allows values of types with this ability to be copied. - [`drop`](#drop) - Allows values of types with this ability to be popped/dropped. - [`store`](#store) - Allows values of types with this ability to exist inside a value in storage. - For Sui, `store` controls what data can be stored inside of an [object](./abilities/object). `store` also controls what types can be transferred outside of their defining module. - [`key`](#key) - Allows the type to serve as a "key" for storage. Ostensibly this means the value can be a top-level value in storage; in other words, it does not need to be contained in another value to be in storage. - For Sui, `key` is used to signify an [object](./abilities/object). ### `copy` The `copy` ability allows values of types with that ability to be copied. It gates the ability to copy values out of local variables with the [`copy`](./variables#move-and-copy) operator and to copy values via references with [dereference `*e`](./primitive-types/references#reading-and-writing-through-references). If a value has `copy`, all values contained inside of that value have `copy`. ### `drop` The `drop` ability allows values of types with that ability to be dropped. By dropped, we mean that value is not transferred and is effectively destroyed as the Move program executes. As such, this ability gates the ability to ignore values in a multitude of locations, including: - not using the value in a local variable or parameter - not using the value in a [sequence via `;`](./variables#expression-blocks) - overwriting values in variables in [assignments](./variables#assignments) - overwriting values via references when [writing `*e1 = e2`](./primitive-types/references#reading-and-writing-through-references). If a value has `drop`, all values contained inside of that value have `drop`. ### `store` The `store` ability allows values of types with this ability to exist inside of a value in storage, _but_ not necessarily as a top-level value in storage. This is the only ability that does not directly gate an operation. Instead it gates the existence in storage when used in tandem with `key`. If a value has `store`, all values contained inside of that value have `store`. For Sui, `store` serves double duty. It controls what values can appear inside of an [object](/storage/store-ability), and what objects can be [transferred](./abilities/object#transfer-rules) outside of their defining module. ### `key` The `key` ability allows the type to serve as a key for storage operations as defined by the deployment of Move. While it is specific per Move instance, it serves to gates all storage operations, so in order for a type to be used with storage primitives, the type must have the `key` ability. If a value has `key`, all values contained inside of that value have `store`. This is the only ability with this sort of asymmetry. For Sui, `key` is used to signify an [object](./abilities/object). ## Builtin Types All primitive, builtin types have `copy`, `drop`, and `store`. - `bool`, `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, and `address` all have `copy`, `drop`, and `store`. - `vector` may have `copy`, `drop`, and `store` depending on the abilities of `T`. - See [Conditional Abilities and Generic Types](#conditional-abilities-and-generic-types) for more details. - Immutable references `&` and mutable references `&mut` both have `copy` and `drop`. - This refers to copying and dropping the reference itself, not what they refer to. - References cannot appear in global storage, hence they do not have `store`. Note that none of the primitive types have `key`, meaning none of them can be used directly with storage operations. ## Annotating Structs and Enums To declare that a `struct` or `enum` has an ability, it is declared with `has ` after the datatype name and either before or after the fields/variants. For example: ```move public struct Ignorable has drop { f: u64 } public struct Pair has copy, drop, store { x: u64, y: u64 } public struct MyVec(vector) has copy, drop, store; public enum IgnorableEnum has drop { Variant } public enum PairEnum has copy, drop, store { Variant } public enum MyVecEnum { Variant } has copy, drop, store; ``` In this case: `Ignorable*` has the `drop` ability. `Pair*` and `MyVec*` both have `copy`, `drop`, and `store`. All of these abilities have strong guarantees over these gated operations. The operation can be performed on the value only if it has that ability; even if the value is deeply nested inside of some other collection! As such: when declaring a struct’s abilities, certain requirements are placed on the fields. All fields must satisfy these constraints. These rules are necessary so that structs satisfy the reachability rules for the abilities given above. If a struct is declared with the ability... - `copy`, all fields must have `copy`. - `drop`, all fields must have `drop`. - `store`, all fields must have `store`. - `key`, all fields must have `store`. - `key` is the only ability currently that doesn’t require itself. An enum can have any of these abilities with the exception of `key`, which enums cannot have because they cannot be top-level values (objects) in storage. The same rules apply to fields of enum variants as they do for struct fields though. In particular, if an enum is declared with the ability... - `copy`, all fields of all variants must have `copy`. - `drop`, all fields of all variants must have `drop`. - `store`, all fields of all variants must have `store`. - `key`, is not allowed on enums as previously mentioned. For example: ```move // A struct without any abilities public struct NoAbilities {} public struct WantsCopy has copy { f: NoAbilities, // ERROR 'NoAbilities' does not have 'copy' } public enum WantsCopyEnum has copy { Variant1 Variant2(NoAbilities), // ERROR 'NoAbilities' does not have 'copy' } ``` and similarly: ```move // A struct without any abilities public struct NoAbilities {} public struct MyData has key { f: NoAbilities, // Error 'NoAbilities' does not have 'store' } public struct MyDataEnum has store { Variant1, Variant2(NoAbilities), // Error 'NoAbilities' does not have 'store' } ``` ## Conditional Abilities and Generic Types When abilities are annotated on a generic type, not all instances of that type are guaranteed to have that ability. Consider this struct declaration: ```move public struct Cup has copy, drop, store, key { item: T } ``` It might be very helpful if `Cup` could hold any type, regardless of its abilities. The type system can _see_ the type parameter, so it should be able to remove abilities from `Cup` if it _sees_ a type parameter that would violate the guarantees for that ability. This behavior might sound a bit confusing at first, but it might be more understandable if we think about collection types. We could consider the builtin type `vector` to have the following type declaration: ```move vector has copy, drop, store; ``` We want `vector`s to work with any type. We don't want separate `vector` types for different abilities. So what are the rules we would want? Precisely the same that we would want with the field rules above. So, it would be safe to copy a `vector` value only if the inner elements can be copied. It would be safe to ignore a `vector` value only if the inner elements can be ignored/dropped. And, it would be safe to put a `vector` in storage only if the inner elements can be in storage. To have this extra expressiveness, a type might not have all the abilities it was declared with depending on the instantiation of that type; instead, the abilities a type will have depends on both its declaration **and** its type arguments. For any type, type parameters are pessimistically assumed to be used inside of the struct, so the abilities are only granted if the type parameters meet the requirements described above for fields. Taking `Cup` from above as an example: - `Cup` has the ability `copy` only if `T` has `copy`. - It has `drop` only if `T` has `drop`. - It has `store` only if `T` has `store`. - It has `key` only if `T` has `store`. Here are examples for this conditional system for each ability: ### Example: conditional `copy` ```move public struct NoAbilities {} public struct S has copy, drop { f: bool } public struct Cup has copy, drop, store { item: T } fun example(c_x: Cup, c_s: Cup) { // Valid, 'Cup' has 'copy' because 'u64' has 'copy' let c_x2 = copy c_x; // Valid, 'Cup' has 'copy' because 'S' has 'copy' let c_s2 = copy c_s; } fun invalid(c_account: Cup, c_n: Cup) { // Invalid, 'Cup' does not have 'copy'. // Even though 'Cup' was declared with copy, the instance does not have 'copy' // because 'signer' does not have 'copy' let c_account2 = copy c_account; // Invalid, 'Cup' does not have 'copy' // because 'NoAbilities' does not have 'copy' let c_n2 = copy c_n; } ``` ### Example: conditional `drop` ```move public struct NoAbilities {} public struct S has copy, drop { f: bool } public struct Cup has copy, drop, store { item: T } fun unused() { Cup { item: true }; // Valid, 'Cup' has 'drop' Cup { item: S { f: false }}; // Valid, 'Cup' has 'drop' } fun left_in_local(c_account: Cup): u64 { let c_b = Cup { item: true }; let c_s = Cup { item: S { f: false }}; // Valid return: 'c_account', 'c_b', and 'c_s' have values // but 'Cup', 'Cup', and 'Cup' have 'drop' 0 } fun invalid_unused() { // Invalid, Cannot ignore 'Cup' because it does not have 'drop'. // Even though 'Cup' was declared with 'drop', the instance does not have 'drop' // because 'NoAbilities' does not have 'drop' Cup { item: NoAbilities {} }; } fun invalid_left_in_local(): u64 { let n = Cup { item: NoAbilities {} }; // Invalid return: 'c_n' has a value // and 'Cup' does not have 'drop' 0 } ``` ### Example: conditional `store` ```move public struct Cup has copy, drop, store { item: T } // 'MyInnerData is declared with 'store' so all fields need 'store' struct MyInnerData has store { yes: Cup, // Valid, 'Cup' has 'store' // no: Cup, Invalid, 'Cup' does not have 'store' } // 'MyData' is declared with 'key' so all fields need 'store' struct MyData has key { yes: Cup, // Valid, 'Cup' has 'store' inner: Cup, // Valid, 'Cup' has 'store' // no: Cup, Invalid, 'Cup' does not have 'store' } ``` ### Example: conditional `key` ```move public struct NoAbilities {} public struct MyData has key { f: T } fun valid(addr: address) acquires MyData { // Valid, 'MyData' has 'key' transfer(addr, MyData { f: 0 }); } fun invalid(addr: address) { // Invalid, 'MyData' does not have 'key' transfer(addr, MyData { f: NoAbilities {} }) // Invalid, 'MyData' does not have 'key' borrow(addr); // Invalid, 'MyData' does not have 'key' borrow_mut(addr); } // Mock storage operation native public fun transfer(addr: address, value: T); ``` --- # Uses and Aliases The `use` syntax can be used to create aliases to members in other modules. `use` can be used to create aliases that last either for the entire module, or for a given expression block scope. ## Syntax There are several different syntax cases for `use`. Starting with the most simple, we have the following for creating aliases to other modules ```move use
::; use
:: as ; ``` For example ```move use std::vector; use std::option as o; ``` `use std::vector;` introduces an alias `vector` for `std::vector`. This means that anywhere you would want to use the module name `std::vector` (assuming this `use` is in scope), you could use `vector` instead. `use std::vector;` is equivalent to `use std::vector as vector;` Similarly `use std::option as o;` would let you use `o` instead of `std::option` ```move use std::vector; use std::option as o; fun new_vec(): vector> { let mut v = vector[]; vector::push_back(&mut v, o::some(0)); vector::push_back(&mut v, o::none()); v } ``` If you want to import a specific module member (such as a function or struct). You can use the following syntax. ```move use
::::; use
:::: as ; ``` For example ```move use std::vector::push_back; use std::option::some as s; ``` This would let you use the function `std::vector::push_back` without full qualification. Similarly for `std::option::some` with `s`. Instead you could use `push_back` and `s` respectively. Again, `use std::vector::push_back;` is equivalent to `use std::vector::push_back as push_back;` ```move use std::vector::push_back; use std::option::some as s; fun new_vec(): vector> { let mut v = vector[]; vector::push_back(&mut v, s(0)); vector::push_back(&mut v, std::option::none()); v } ``` ### Multiple Aliases If you want to add aliases for multiple module members at once, you can do so with the following syntax ```move use
::::{, as ... }; ``` For example ```move use std::vector::push_back; use std::option::{some as s, none as n}; fun new_vec(): vector> { let mut v = vector[]; push_back(&mut v, s(0)); push_back(&mut v, n()); v } ``` ### Self aliases If you need to add an alias to the Module itself in addition to module members, you can do that in a single `use` using `Self`. `Self` is a member of sorts that refers to the module. ```move use std::option::{Self, some, none}; ``` For clarity, all of the following are equivalent: ```move use std::option; use std::option as option; use std::option::Self; use std::option::Self as option; use std::option::{Self}; use std::option::{Self as option}; ``` ### Multiple Aliases for the Same Definition If needed, you can have as many aliases for any item as you like ```move use std::vector::push_back; use std::option::{Option, some, none}; fun new_vec(): vector> { let mut v = vector[]; push_back(&mut v, some(0)); push_back(&mut v, none()); v } ``` ### Nested imports In Move, you can also import multiple names with the same `use` declaration. This brings all provided names into scope: ```move use std::{ vector::{Self as vec, push_back}, string::{String, Self as str} }; fun example(s: &mut String) { let mut v = vec::empty(); push_back(&mut v, 0); push_back(&mut v, 10); str::append_utf8(s, v); } ``` ## Inside a `module` Inside of a `module` all `use` declarations are usable regardless of the order of declaration. ```move module a::example; use std::vector; fun new_vec(): vector> { let mut v = vector[]; vector::push_back(&mut v, 0); vector::push_back(&mut v, 10); v } use std::option::{Option, some, none}; ``` The aliases declared by `use` in the module usable within that module. Additionally, the aliases introduced cannot conflict with other module members. See [Uniqueness](#uniqueness) for more details ## Inside an expression You can add `use` declarations to the beginning of any expression block ```move module a::example; fun new_vec(): vector> { use std::vector::push_back; use std::option::{Option, some, none}; let mut v = vector[]; push_back(&mut v, some(0)); push_back(&mut v, none()); v } ``` As with `let`, the aliases introduced by `use` in an expression block are removed at the end of that block. ```move module a::example; fun new_vec(): vector> { let result = { use std::vector::push_back; use std::option::{Option, some, none}; let mut v = vector[]; push_back(&mut v, some(0)); push_back(&mut v, none()); v }; result } ``` Attempting to use the alias after the block ends will result in an error ```move fun new_vec(): vector> { let mut result = { use std::vector::push_back; use std::option::{Option, some, none}; let mut v = vector[]; push_back(&mut v, some(0)); v }; push_back(&mut result, std::option::none()); // ^^^^^^ ERROR! unbound function 'push_back' result } ``` Any `use` must be the first item in the block. If the `use` comes after any expression or `let`, it will result in a parsing error ```move { let mut v = vector[]; use std::vector; // ERROR! } ``` This allows you to shorten your import blocks in many situations. Note that these imports, as the previous ones, are all subject to the naming and uniqueness rules described in the following sections. ## Naming rules Aliases must follow the same rules as other module members. This means that aliases to structs (and constants) must start with `A` to `Z` ```move module a::data { public struct S {} const FLAG: bool = false; public fun foo() {} } module a::example { use a::data::{ S as s, // ERROR! FLAG as fLAG, // ERROR! foo as FOO, // valid foo as bar, // valid }; } ``` ## Uniqueness Inside a given scope, all aliases introduced by `use` declarations must be unique. For a module, this means aliases introduced by `use` cannot overlap ```move module a::example; use std::option::{none as foo, some as foo}; // ERROR! // ^^^ duplicate 'foo' use std::option::none as bar; use std::option::some as bar; // ERROR! // ^^^ duplicate 'bar' ``` And, they cannot overlap with any of the module's other members ```move module a::data { public struct S {} } module example { use a::data::S; public struct S { value: u64 } // ERROR! // ^ conflicts with alias 'S' above } ``` Inside of an expression block, they cannot overlap with each other, but they can [shadow](#shadowing) other aliases or names from an outer scope ## Shadowing `use` aliases inside of an expression block can shadow names (module members or aliases) from the outer scope. As with shadowing of locals, the shadowing ends at the end of the expression block; ```move module a::example; public struct WrappedVector { vec: vector } public fun empty(): WrappedVector { WrappedVector { vec: std::vector::empty() } } public fun push_back(v: &mut WrappedVector, value: u64) { std::vector::push_back(&mut v.vec, value); } fun example1(): WrappedVector { use std::vector::push_back; // 'push_back' now refers to std::vector::push_back let mut vec = vector[]; push_back(&mut vec, 0); push_back(&mut vec, 1); push_back(&mut vec, 10); WrappedVector { vec } } fun example2(): WrappedVector { let vec = { use std::vector::push_back; // 'push_back' now refers to std::vector::push_back let mut v = vector[]; push_back(&mut v, 0); push_back(&mut v, 1); v }; // 'push_back' now refers to Self::push_back let mut res = WrappedVector { vec }; push_back(&mut res, 10); res } ``` ## Unused Use or Alias An unused `use` will result in a warning ```move module a::example; use std::option::{some, none}; // Warning! // ^^^^ unused alias 'none' public fun example(): std::option::Option { some(0) } ``` --- # Methods As a syntactic convenience, some functions in Move can be called as "methods" on a value. This is done by using the `.` operator to call the function, where the value on the left-hand side of the `.` is the first argument to the function (sometimes called the receiver). The type of that value statically determines which function is called. This is an important difference from some other languages, where this syntax might indicate a dynamic call, where the function to be called is determined at runtime. In Move, all function calls are statically determined. In short, this syntax exists to make it easier to call functions without having to create an alias with `use`, and without having to explicitly borrow the first argument to the function. Additionally, this can make code more readable, as it reduces the amount of boilerplate needed to call a function and makes it easier to chain function calls. ## Syntax The syntax for calling a method is as follows: ```text . <[type_arguments],*> ( ) ``` For example ```move coin.value(); *nums.borrow_mut(i) = 5; ``` ## Method Resolution When a method is called, the compiler will statically determine which function is called based on the type of the receiver (the argument on the left-hand side of the `.`). The compiler maintains a mapping from type and method name to the module and function name that should be called. This mapping is created form the `use fun` aliases that are currently in scope, and from the appropriate functions in the receiver type's defining module. In all cases, the receiver type is the first argument to the function, whether by-value or by-reference. In this section, when we say a method "resolves" to a function, we mean that the compiler will statically replace the method with a normal [function](./functions) call. For example if we have `x.foo(e)` with `foo` resolving to `a::m::foo`, the compiler will replace `x.foo(e)` with `a::m::foo(x, e)`, potentially [automatically borrowing](#automatic-borrowing) `x`. ### Functions in the Defining Module In a type’s defining module, the compiler will automatically create a method alias for any function declaration for its types when the type is the first argument in the function. For example, ```move module a::m; public struct X() has copy, drop, store; public fun foo(x: &X) { ... } public fun bar(flag: bool, x: &X) { ... } ``` The function `foo` can be called as a method on a value of type `X`. However, not the first argument (and one is not created for `bool` since `bool` is not defined in that module). For example, ```move fun example(x: a::m::X) { x.foo(); // valid // x.bar(true); ERROR! } ``` ### `use fun` Aliases Like a traditional [`use`](uses), a `use fun` statement creates an alias local to its current scope. This could be for the current module or the current expression block. However, the alias is associated to a type. The syntax for a `use fun` statement is as follows: ```move use fun as .; ``` This creates an alias for the ``, which the `` can receive as ``. For example ```move module a::cup; public struct Cup(T) has copy, drop, store; public fun cup_borrow(c: &Cup): &T { &c.0 } public fun cup_value(c: Cup): T { let Cup(t) = c; t } public fun cup_swap(c: &mut Cup, t: T) { c.0 = t; } ``` We can now create `use fun` aliases to these functions ```move module b::example; use fun a::cup::cup_borrow as Cup.borrow; use fun a::cup::cup_value as Cup.value; use fun a::cup::cup_swap as Cup.set; fun example(c: &mut Cup) { let _ = c.borrow(); // resolves to a::cup::cup_borrow let v = c.value(); // resolves to a::cup::cup_value c.set(v * 2); // resolves to a::cup::cup_swap } ``` Note that the `` in the `use fun` does not have to be a fully resolved path, and an alias can be used instead, so the declarations in the above example could equivalently be written as ```move use a::cup::{Self, cup_swap}; use fun cup::cup_borrow as Cup.borrow; use fun cup::cup_value as Cup.value; use fun cup_swap as Cup.set; ``` While these examples are cute for renaming the functions in the current module, the feature is perhaps more useful for declaring methods on types from other modules. For example, if we wanted to add a new utility to `Cup`, we could do so with a `use fun` alias and still use method syntax ```move module b::example; fun double(c: &Cup): Cup { let v = c.value(); Cup::new(v * 2) } ``` Normally, we would be stuck having to call it as `double(&c)` because `b::example` did not define `Cup`, but instead we can use a `use fun` alias ```move fun double_double(c: Cup): (Cup, Cup) { use fun b::example::double as Cup.dub; (c.dub(), c.dub()) // resolves to b::example::double in both calls } ``` While `use fun` can be made in any scope, the target `` of the `use fun` must have a first argument that is the same as the ``. ```move public struct X() has copy, drop, store; fun new(): X { X() } fun flag(flag: bool): u8 { if (flag) 1 else 0 } use fun new as X.new; // ERROR! use fun flag as X.flag; // ERROR! // Neither `new` nor `flag` has first argument of type `X` ``` But any first argument of the `` can be used, including references and mutable references ```move public struct X() has copy, drop, store; public fun by_val(_: X) {} public fun by_ref(_: &X) {} public fun by_mut(_: &mut X) {} // All 3 valid, in any scope use fun by_val as X.v; use fun by_ref as X.r; use fun by_mut as X.m; ``` Note for generics, the methods are associated for _all_ instances of the generic type. You cannot overload the method to resolve to different functions depending on the instantiation. ```move public struct Cup(T) has copy, drop, store; public fun value(c: &Cup): T { c.0 } use fun value as Cup.flag; // ERROR! use fun value as Cup.num; // ERROR! // In both cases, `use fun` aliases cannot be generic, they must work for all instances of the type ``` ### `public use fun` Aliases Unlike a traditional [`use`](uses), the `use fun` statement can be made `public`, which allows it to be used outside of its declared scope. A `use fun` can be made `public` if it is declared in the module that defines the receivers type, much like the method aliases that are [automatically created](#functions-in-the-defining-module) for functions in the defining module. Or conversely, one can think that an implicit `public use fun` is created automatically for every function in the defining module that has a first argument of the receiver type (if it is defined in that module). Both of these views are equivalent. ```move module a::cup; public struct Cup(T) has copy, drop, store; public use fun cup_borrow as Cup.borrow; public fun cup_borrow(c: &Cup): &T { &c.0 } ``` In this example, a public method alias is created for `a::cup::Cup.borrow` and `a::cup::Cup.cup_borrow`. Both resolve to `a::cup::cup_borrow`. And both are "public" in the sense that they can be used outside of `a::cup`, without an additional `use` or `use fun`. ```move module b::example; fun example(c: a::cup::Cup) { c.borrow(); // resolves to a::cup::cup_borrow c.cup_borrow(); // resolves to a::cup::cup_borrow } ``` The `public use fun` declarations thus serve as a way of renaming a function if you want to give it a cleaner name for use with method syntax. This is especially helpful if you have a module with multiple types, and similarly named functions for each type. ```move module a::shapes; public struct Rectangle { base: u64, height: u64 } public struct Box { base: u64, height: u64, depth: u64 } // Rectangle and Box can have methods with the same name public use fun rectangle_base as Rectangle.base; public fun rectangle_base(rectangle: &Rectangle): u64 { rectangle.base } public use fun box_base as Box.base; public fun box_base(box: &Box): u64 { box.base } ``` Another use for `public use fun` is adding methods to types from other modules. This can be helpful in conjunction with functions spread out across a single package. ```move module a::cup { public struct Cup(T) has copy, drop, store; public fun new(t: T): Cup { Cup(t) } public fun borrow(c: &Cup): &T { &c.0 } // `public use fun` to a function defined in another module public use fun a::utils::split as Cup.split; } module a::utils { use a::m::{Self, Cup}; public fun split(c: Cup): (Cup, Cup) { let Cup(t) = c; let half = t / 2; let rem = if (t > 0) t - half else 0; (cup::new(half), cup::new(rem)) } } ``` And note that this `public use fun` does not create a circular dependency, as the `use fun` is not present after the module is compiled--all methods are resolved statically. ### Interactions with `use` Aliases A small detail to note is that method aliases respect normal `use` aliases. ```move module a::cup { public struct Cup(T) has copy, drop, store; public fun cup_borrow(c: &Cup): &T { &c.0 } } module b::other { use a::cup::{Cup, cup_borrow as borrow}; fun example(c: &Cup) { c.borrow(); // resolves to a::cup::cup_borrow } } ``` A helpful way to think about this is that `use` creates an implicit `use fun` alias for the function whenever it can. In this case the `use a::cup::cup_borrow as borrow` creates an implicit `use fun a::cup::cup_borrow as Cup.borrow` because it would be a valid `use fun` alias. Both views are equivalent. This line of reasoning can inform how specific methods will resolve with shadowing. See the cases in [Scoping](#scoping) for more details. ### Scoping If not `public`, a `use fun` alias is local to its scope, much like a normal [`use`](uses). For example ```move module a::m { public struct X() has copy, drop, store; public fun foo(_: &X) {} public fun bar(_: &X) {} } module b::other { use a::m::X; use fun a::m::foo as X.f; fun example(x: &X) { x.f(); // resolves to a::m::foo { use a::m::bar as f; x.f(); // resolves to a::m::bar }; x.f(); // still resolves to a::m::foo { use fun a::m::bar as X.f; x.f(); // resolves to a::m::bar } } ``` ## Automatic Borrowing When resolving a method, the compiler will automatically borrow the receiver if the function expects a reference. For example ```move module a::m; public struct X() has copy, drop; public fun by_val(_: X) {} public fun by_ref(_: &X) {} public fun by_mut(_: &mut X) {} fun example(mut x: X) { x.by_ref(); // resolves to a::m::by_ref(&x) x.by_mut(); // resolves to a::m::by_mut(&mut x) } ``` In these examples, `x` was automatically borrowed to `&x` and `&mut x` respectively. This will also work through field access ```move module a::m; public struct X() has copy, drop; public fun by_val(_: X) {} public fun by_ref(_: &X) {} public fun by_mut(_: &mut X) {} public struct Y has drop { x: X } fun example(mut y: Y) { y.x.by_ref(); // resolves to a::m::by_ref(&y.x) y.x.by_mut(); // resolves to a::m::by_mut(&mut y.x) } ``` Note that in both examples, the local variable had to be labeled as [`mut`](./variables) to allow for the `&mut` borrow. Without this, there would be an error saying that `x` (or `y` in the second example) is not mutable. Keep in mind that without a reference, normal rules for variable and field access come into play. Meaning a value might be moved or copied if it is not borrowed. ```move module a::m; public struct X() has copy, drop; public fun by_val(_: X) {} public fun by_ref(_: &X) {} public fun by_mut(_: &mut X) {} public struct Y has drop { x: X } public fun drop_y(y: Y) { y } fun example(y: Y) { y.x.by_val(); // copies `y.x` since `by_val` is by-value and `X` has `copy` y.drop_y(); // moves `y` since `drop_y` is by-value and `Y` does _not_ have `copy` } ``` ## Chaining Method calls can be chained, because any expression can be the receiver of the method. ```move module a::shapes { public struct Point has copy, drop, store { x: u64, y: u64 } public struct Line has copy, drop, store { start: Point, end: Point } public fun x(p: &Point): u64 { p.x } public fun y(p: &Point): u64 { p.y } public fun start(l: &Line): &Point { &l.start } public fun end(l: &Line): &Point { &l.end } } module b::example { use a::shapes::Line; public fun x_values(l: Line): (u64, u64) { (l.start().x(), l.end().x()) } } ``` In this example for `l.start().x()`, the compiler first resolves `l.start()` to `a::shapes::start(&l)`. Then `.x()` is resolved to `a::shapes::x(a::shapes::start(&l))`. Similarly for `l.end().x()`. Keep in mind, this feature is not "special"--the left-hand side of the `.` can be any expression, and the compiler will resolve the method call as normal. We simply draw attention to this sort of "chaining" because it is a common practice to increase readability. --- # Index Syntax Move provides syntax attributes to allow you to define operations that look and feel like native Move code, lowering these operations into your user-provided definitions. Our first syntax method, `index`, allows you to define a group of operations that can be used as custom index accessors for your datatypes, such as accessing a matrix element as `m[i,j]`, by annotating functions that should be used for these index operations. Moreover, these definitions are bespoke per-type and available implicitly for any programmer using your type. ## Overview and Summary To start, consider a `Matrix` type that uses a vector of vectors to represent its values. You can write a small library using `index` syntax annotations on the `borrow` and `borrow_mut` functions as follows: ```move module matrix::matrix; public struct Matrix { v: vector> } #[syntax(index)] public fun borrow(s: &Matrix, i: u64, j: u64): &T { vector::borrow(vector::borrow(&s.v, i), j) } #[syntax(index)] public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut T { vector::borrow_mut(vector::borrow_mut(&mut s.v, i), j) } public fun make_matrix(v: vector>): Matrix { Matrix { v } } ``` Now anyone using this `Matrix` type has access to index syntax for it: ```move let mut m = matrix::make_matrix(vector[ vector[1, 0, 0], vector[0, 1, 0], vector[0, 0, 1], ]);x let mut i = 0; while (i < 3) { let mut j = 0; while (j < 3) { if (i == j) { assert!(m[i, j] == 1, 1); } else { assert!(m[i, j] == 0, 0); }; *(&mut m[i,j]) = 2; j = j + 1; }; i = i + 1; } ``` ## Usage As the example indicates, if you define a datatype and an associated index syntax method, anyone can invoke that method by writing index syntax on a value of that type: ```move let mat = matrix::make_matrix(...); let m_0_0 = mat[0, 0]; ``` During compilation, the compiler translates these into the appropriate function invocations based on the position and mutable usage of the expression: ```move let mut mat = matrix::make_matrix(...); let m_0_0 = mat[0, 0]; // translates to `copy matrix::borrow(&mat, 0, 0)` let m_0_0 = &mat[0, 0]; // translates to `matrix::borrow(&mat, 0, 0)` let m_0_0 = &mut mat[0, 0]; // translates to `matrix::borrow_mut(&mut mat, 0, 0)` ``` You can also intermix index expressions with field accesses: ```move public struct V { v: vector } public struct Vs { vs: vector } fun borrow_first(input: &Vs): &u64 { &input.vs[0].v[0] // translates to `vector::borrow(&vector::borrow(&input.vs, 0).v, 0)` } ``` ### Index Functions Take Flexible Arguments Note that, aside from the definition and type limitations described in the rest of this chapter, Move places no restrictions on the values your index syntax method takes as parameters. This allows you to implement intricate programmatic behavior when defining index syntax, such as a data structure that takes a default value if the index is out of bounds: ```move #[syntax(index)] public fun borrow_or_set( input: &mut MTable, key: Key, default: Value ): &mut Value { if (contains(input, key)) { borrow(input, key) } else { insert(input, key, default); borrow(input, key) } } ``` Now, when you index into `MTable`, you must also provide a default value: ```move let string_key: String = ...; let mut table: MTable = m_table::make_table(); let entry: &mut u64 = &mut table[string_key, 0]; ``` This sort of extensible power allows you to write precise index interfaces for your types, concretely enforcing bespoke behavior. ## Defining Index Syntax Functions This powerful syntax form allows all of your user-defined datatypes to behave in this way, assuming your definitions adhere to the following rules: 1. The `#[syntax(index)]` attribute is added to the designated functions defined in the same module as the subject type. 1. The designated functions have `public` visibility. 1. The functions take a reference type as its subject type (its first argument) and returns a matching references type (`mut` if the subject was `mut`). 1. Each type has only a single mutable and single immutable definition. 1. Immutable and mutable versions have type agreement: - The subject types match, differing only in mutability. - The return types match the mutability of their subject types. - Type parameters, if present, have identical constraints between both versions. - All parameters beyond the subject type are identical. The following content and additional examples describe these rules in greater detail. ### Declaration To declare an index syntax method, add the `#[syntax(index)]` attribute above the relevant function definition in the same module as the subject type's definition. This signals to the compiler that the function is an index accessor for the specified type. #### Immutable Accessor The immutable index syntax method is defined for read-only access. It takes an immutable reference of the subject type and returns an immutable reference to the element type. The `borrow` function defined in `std::vector` is an example of this: ```move #[syntax(index)] public native fun borrow(v: &vector, i: u64): ∈ ``` #### Mutable Accessor The mutable index syntax method is the dual of the immutable one, allowing for both read and write operations. It takes a mutable reference of the subject type and returns a mutable reference to the element type. The `borrow_mut` function defined in `std::vector` is an example of this: ```move #[syntax(index)] public native fun borrow_mut(v: &mut vector, i: u64): &mut Element; ``` #### Visibility To ensure that indexing functions are available anywhere the type is used, all index syntax methods must have public visibility. This ensures ergonomic usage of indexing across modules and packages in Move. #### No Duplicates In addition to the above requirements, we restrict each subject base type to defining a single index syntax method for immutable references and a single index syntax method for mutable references. For example, you cannot define a specialized version for a polymorphic type: ```move #[syntax(index)] public fun borrow_matrix_u64(s: &Matrix, i: u64, j: u64): &u64 { ... } #[syntax(index)] public fun borrow_matrix(s: &Matrix, i: u64, j: u64): &T { ... } // ERROR! Matrix already has a definition // for its immutable index syntax method ``` This ensures that you can always tell which method is being invoked, without the need to inspect type instantiation. ### Type Constraints By default, an index syntax method has the following type constraints: **Its subject type (first argument) must be a reference to a single type defined in the same module as the marked function.** This means that you cannot define index syntax methods for tuples, type parameters, or values: ```move #[syntax(index)] public fun borrow_fst(x: &(u64, u64), ...): &u64 { ... } // ERROR because the subject type is a tuple #[syntax(index)] public fun borrow_tyarg(x: &T, ...): &T { ... } // ERROR because the subject type is a type parameter #[syntax(index)] public fun borrow_value(x: Matrix, ...): &u64 { ... } // ERROR because x is not a reference ``` **The subject type must match mutability with the return type.** This restriction allows you to clarify the expected behavior when borrowing an indexed expression as `&vec[i]` versus `&mut vec[i]`. The Move compiler uses the mutability marker to determine which borrow form to call to produce a reference of the appropriate mutability. As a result, we disallow index syntax methods whose subject and return mutability differ: ```move #[syntax(index)] public fun borrow_imm(x: &mut Matrix, ...): &u64 { ... } // ERROR! incompatible mutability // expected a mutable reference '&mut' return type ``` ### Type Compatibility When defining an immutable and mutable index syntax method pair, they are subject to a number of compatibility constraints: 1. They must take the same number of type parameters, those type parameters must have the same constraints. 1. Type parameters must be used the same _by position_, not name. 1. Their subject types must match exactly except for the mutability. 1. Their return types must match exactly except for the mutability. 1. All other parameter types must match exactly. These constraints are to ensure that index syntax behaves identically regardless of being in a mutable or immutable position. To illustrate some of these errors, recall the previous `Matrix` definition: ```move #[syntax(index)] public fun borrow(s: &Matrix, i: u64, j: u64): &T { vector::borrow(vector::borrow(&s.v, i), j) } ``` All of the following are type-incompatible definitions of the mutable version: ```move #[syntax(index)] public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut T { ... } // ERROR! `T` has `drop` here, but no in the immutable version #[syntax(index)] public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut u64 { ... } // ERROR! This takes a different number of type parameters #[syntax(index)] public fun borrow_mut(s: &mut Matrix, i: u64, j: u64): &mut U { ... } // ERROR! This takes a different number of type parameters #[syntax(index)] public fun borrow_mut(s: &mut Matrix, i_j: (u64, u64)): &mut U { ... } // ERROR! This takes a different number of arguments #[syntax(index)] public fun borrow_mut(s: &mut Matrix, i: u64, j: u32): &mut U { ... } // ERROR! `j` is a different type ``` Again, the goal here is to make the usage across the immutable and mutable versions consistent. This allows index syntax methods to work without changing out the behavior or constraints based on mutable versus immutable usage, ultimately ensuring a consistent interface to program against. --- # Packages Packages allow Move programmers to more easily re-use code and share it across projects. The Move package system allows programmers to easily: - Define a package containing Move code; - Parameterize a package by [named addresses](./primitive-types/address); - Import and use packages in other Move code and instantiate named addresses; - Build packages and generate associated compilation artifacts from packages; and - Work with a common interface around compiled Move artifacts. ## Package Layout and Manifest Syntax A Move package source directory contains a `Move.toml` package manifest file, a generated `Move.lock` file, and a set of subdirectories: ```plaintext a_move_package ├── Move.toml (required) ├── Move.lock (generated) ├── sources (required) ├── doc_templates (optional) ├── examples (optional, test & dev mode) └── tests (optional, test mode) ``` The directories and files labeled "required" must be present for a directory to be considered a Move package and built. Optional directories may be present, and if so, they will be included in the compilation process depending on the mode used to build the package. For instance, when built in "dev" or "test" modes, the `tests` and `examples` directories will also be included. Going through each of these in turn: 1. The [`Move.toml`](#movetoml) file is the package manifest and is required for a directory to be considered a Move package. This file contains metadata about the package, such as name, dependencies, and so on. 1. The [`Move.lock`](#movelock) file is generated by the Move CLI and contains the fixed build versions of the package and its dependencies. It is used to ensure consistent versions are used across different builds and that changes in dependencies are apparent as a change in this file. 1. The `sources` directory is required and contains the Move modules that make up the package. Modules in this directory will always be included in the compilation process. 1. The `doc_templates` directory can contain documentation templates that will be used when generating documentation for the package. 1. The `examples` directory can hold additional code to be used only for development and/or tutorials, this will not be included when compiled outside of `test` or `dev` modes. 1. The `tests` directory can contain Move modules that are only included when compiled in `test` mode or when [Move unit tests](./unit-testing) are run. ### Move.toml The Move package manifest is defined within the `Move.toml` file and has the following syntax. Optional fields are marked with `*`, `+` denotes one or more elements: ```toml [package] name = edition* = # e.g., "2024.alpha" to use the Move 2024 edition, # currently in alpha. Will default to the latest stable edition if not specified. license* = # e.g., "MIT", "GPL", "Apache 2.0" authors* = [,+] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] # Additional fields may be added to this section by external tools. E.g., on Sui the following sections are added: published-at* = "" # The address that the package is published at. Should be set after the first publication. [dependencies] # (Optional section) Paths to dependencies # One or more lines declaring dependencies in the following format # ##### Local Dependencies ##### # For local dependencies use `local = path`. Path is relative to the package root # Local = { local = "../path/to" } # To resolve a version conflict and force a specific version for dependency # override you can use `override = true` # Override = { local = "../conflicting/version", override = true } # To instantiate address values in a dependency, use `addr_subst` = { local = , override* = , addr_subst* = { ( = ( | ""))+ } } # ##### Git Dependencies ##### # For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`. # Revision must be supplied, it can be a branch, a tag, or a commit hash. # If no `subdir` is specified, the root of the repository is used. # MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" } = { git = , subdir=, rev=, override* = , addr_subst* = { ( = ( | ""))+ } } [addresses] # (Optional section) Declares named addresses in this package # One or more lines declaring named addresses in the following format # Addresses that match the name of the package must be set to `"0x0"` or they will be unable to be published. = "_" | "" # e.g., std = "_" or my_addr = "0xC0FFEECAFE" # Named addresses will be accessible in Move as `@name`. They're also exported: # for example, `std = "0x1"` is exported by the Standard Library. # alice = "0xA11CE" [dev-dependencies] # (Optional section) Same as [dependencies] section, but only included in "dev" and "test" modes # The dev-dependencies section allows overriding dependencies for `--test` and # `--dev` modes. You can e.g., introduce test-only dependencies here. # Local = { local = "../path/to/dev-build" } = { local = , override* = , addr_subst* = { ( = ( | ""))+ } } = { git = , subdir=, rev=, override* = , addr_subst* = { ( = ( | ""))+ } } [dev-addresses] # (Optional section) Same as [addresses] section, but only included in "dev" and "test" modes # The dev-addresses section allows overwriting named addresses for the `--test` # and `--dev` modes. = "" # e.g., alice = "0xB0B" ``` An example of a minimal package manifest: ```toml [package] name = "AName" ``` An example of a more standard package manifest that also includes the Move standard library and instantiates the named address `std` from the `LocalDep` package with the address value `0x1`: ```toml [package] name = "AName" license = "Apache 2.0" [addresses] address_to_be_filled_in = "_" specified_address = "0xB0B" [dependencies] # Local dependency LocalDep = { local = "projects/move-awesomeness", addr_subst = { "std" = "0x1" } } # Git dependency MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "framework/mainnet" } [dev-addresses] # For use when developing this module address_to_be_filled_in = "0x101010101" ``` Most of the sections in the package manifest are self explanatory, but named addresses can be a bit difficult to understand so we examine them in more detail in [Named Addresses During Compilation](#named-addresses-during-compilation). ## Named Addresses During Compilation Recall that Move has [named addresses](./primitive-types/address) and that named addresses cannot be declared in Move. Instead they are declared at the package level: in the manifest file (`Move.toml`) for a Move package you declare named addresses in the package, instantiate other named addresses, and rename named addresses from other packages within the Move package system. Let's go through each of these actions, and how they are performed in the package's manifest one-by-one: ### Declaring Named Addresses Let's say we have a Move module in `example_pkg/sources/A.move` as follows: ```move module named_addr::a { public fun x(): address { @named_addr } } ``` We could in `example_pkg/Move.toml` declare the named address `named_addr` in two different ways. The first: ```toml [package] name = "example_pkg" ... [addresses] named_addr = "_" ``` Declares `named_addr` as a named address in the package `example_pkg` and that _this address can be any valid address value_. In particular, an importing package can pick the value of the named address `named_addr` to be any address it wishes. Intuitively you can think of this as parameterizing the package `example_pkg` by the named address `named_addr`, and the package can then be instantiated later on by an importing package. `named_addr` can also be declared as: ```toml [package] name = "example_pkg" ... [addresses] named_addr = "0xCAFE" ``` which states that the named address `named_addr` is exactly `0xCAFE` and cannot be changed. This is useful so other importing packages can use this named address without needing to worry about the exact value assigned to it. With these two different declaration methods, there are two ways that information about named addresses can flow in the package graph: - The former ("unassigned named addresses") allows named address values to flow from the importation site to the declaration site. - The latter ("assigned named addresses") allows named address values to flow from the declaration site upwards in the package graph to usage sites. With these two methods for flowing named address information throughout the package graph the rules around scoping and renaming become important to understand. ## Scope and Renaming of Named Addresses A named address `N` in a package `P` is in scope if: 1. `P` declares a named address `N`; or 2. A package in one of `P`'s transitive dependencies declares the named address `N` and there is a dependency path in the package graph between `P` and the declaring package of `N` with no renaming of `N`. Additionally, every named address in a package is exported. Because of this and the above scoping rules each package can be viewed as coming with a set of named addresses that will be brought into scope when the package is imported, e.g., if you import `example_pkg`, that import will also bring the `named_addr` named address into scope. Because of this, if `P` imports two packages `P1` and `P2` both of which declare a named address `N` an issue arises in `P`: which "`N`" is meant when `N` is referred to in `P`? The one from `P1` or `P2`? To prevent this ambiguity around which package a named address is coming from, we enforce that the sets of scopes introduced by all dependencies in a package are disjoint, and provide a way to _rename named addresses_ when the package that brings them into scope is imported. Renaming a named address when importing can be done as follows in our `P`, `P1`, and `P2` example above: ```toml [package] name = "P" ... [dependencies] P1 = { local = "some_path_to_P1", addr_subst = { "P1N" = "N" } } P2 = { local = "some_path_to_P2" } ``` With this renaming `N` refers to the `N` from `P2` and `P1N` will refer to `N` coming from `P1`: ```move module N::A { public fun x(): address { @P1N } } ``` It is important to note that _renaming is not local_: once a named address `N` has been renamed to `N2` in a package `P` all packages that import `P` will not see `N` but only `N2` unless `N` is reintroduced from outside of `P`. This is why rule (2) in the scoping rules at the start of this section specifies a "dependency path in the package graph between `P` and the declaring package of `N` with no renaming of `N`." ### Instantiating Named Addresses Named addresses can be instantiated multiple times across the package graph as long as it is always with the same value. It is an error if the same named address (regardless of renaming) is instantiated with differing values across the package graph. A Move package can only be compiled if all named addresses resolve to a value. This presents issues if the package wishes to expose an uninstantiated named address. This is what the `[dev-addresses]` section solves in part. This section can set values for named addresses, but cannot introduce any named addresses. Additionally, only the `[dev-addresses]` in the root package are included in `dev` mode. For example a root package with the following manifest would not compile outside of `dev` mode since `named_addr` would be uninstantiated: ```toml [package] name = "example_pkg" ... [addresses] named_addr = "_" [dev-addresses] named_addr = "0xC0FFEE" ``` ## Usage and Artifacts The Move package system comes with a command line option as part of the CLI: `sui move `. Unless a particular path is provided, all package commands will run in the current enclosing Move package. The full list of commands and flags for the Move CLI can be found by running `sui move --help`. ### Artifacts A package can be compiled using CLI commands. This will create a `build` directory containing build-related artifacts (including bytecode binaries, source maps, and documentation). The general layout of the `build` directory is as follows: ```plaintext a_move_package ├── BuildInfo.yaml ├── bytecode_modules │   ├── dependencies │   │   ├── │   │   │   └── *.mv │   │   ... │   │   └── │   │      └── *.mv │   ... │   └── *.mv ├── docs │   ├── dependencies │   │   ├── │   │   │   └── *.md │   │   ... │   │   └── │   │      └── *.md │   ... │   └── *.md ├── source_maps │   ├── dependencies │   │   ├── │   │   │   └── *.mvsm │   │   ... │   │   └── │   │      └── *.mvsm │   ... │   └── *.mvsm └── sources    ...    └── *.move    ├── dependencies    │   ├──    │   │   └── *.move    │   ...    │   └──    │      └── *.move    ...    └── *.move ``` ## Move.lock The `Move.lock` file is generated at the root of the Move package when the package is built. The `Move.lock` file contains information about your package and its build configuration, and acts as a communication layer between the Move compiler and other tools, like chain-specific command line interfaces and third-party package managers. Like the `Move.toml` file, the `Move.lock` file is a text-based TOML file. Unlike the package manifest however, the `Move.lock` file is not intended for you to edit directly. Processes on the toolchain, like the Move compiler, access and edit the file to read and append relevant information to it. You also must not move the file from the root, as it needs to be at the same level as the `Move.toml` manifest in the package. If you are using source control for your package, it's recommended practice to check in the `Move.lock` file that corresponds with your desired built or published package. This ensures that every build of your package is an exact replica of the original, and that changes to the build will be apparent as changes to the `Move.lock` file. The `Move.lock` file is a TOML file that currently contains the following fields. **Note**: other fields may be added to the lock file either in the future, or by third-party package managers as well. ### The `[move]` Section This section contains the core information needed in the lockfile: - The version of the lockfile (needed for backwards compatibility checking, and versioning lockfile changes in the future). - The hash of the `Move.toml` file that was used to generate this lock file. - The hash of the `Move.lock` file of all dependencies. If no dependencies are present, this will be an empty string. - The list of dependencies. ```toml [move] version = # Lock file version, used for backwards compatibility checking. manifest_digest = # Sha3-256 hash of the Move.toml file that was used to generate this lock file. deps_digest = # Sha3-256 hash of the Move.lock file of all dependencies. If no dependencies are present, this will be an empty string. dependencies = { (name = )* } # List of dependencies. Not present if there are no dependencies. ``` ### The `[move.package]` Sections After the Move compiler resolves each of the dependencies for the package it writes the location of the dependency to the `Move.lock` file. If a dependency failed to resolve, the compiler will not write the `Move.lock` file and the build fails. If all dependencies resolve, the `Move.lock` file contains the locations (local and remote) of all of the package's transitive dependencies. These will be stored in the `Move.lock` file in the following format: ```toml # ... [[move.package]] name = "A" source = { git = "https://github.com/b/c.git", subdir = "e/f", rev = "a1b2c3" } [[move.package]] name = "B" source = { local = "../local-dep" } ``` ### The `[move.toolchain-version]` Section As mentioned above, additional fields may be added to the lock file by external tools. For example, the Sui package manager adds toolchain version information to the lock file that can then be used for on-chain source verification: ```toml # ... [move.toolchain-version] compiler-version = # The version of the Move compiler used to build the package, e.g. "1.21.0" edition = # The edition of the Move language used to build the package, e.g. "2024.alpha" flavor = # The flavor of the Move compiler used to build the package, e.g. "sui" ``` --- # Unit Tests Unit testing for Move uses three annotations in the Move source language: - `#[test]` marks a function as a test; - `#[expected_failure]` marks that a test is expected to fail; - `#[test_only]` marks a module or module member ([`use`](./uses), [function](./functions), [struct](./structs), or [constant](./constants)) as code to be included for testing only. These annotations can be placed on any appropriate form with any visibility. Whenever a module or module member is annotated as `#[test_only]` or `#[test]`, it will not be included in the compiled bytecode unless it is compiled for testing. ## Test Annotations The `#[test]` annotation can only be placed on a function with no parameters. This annotation marks the function as a test to be run by the unit testing harness. ```move #[test] // OK fun this_is_a_test() { ... } #[test] // Will fail to compile since the test takes an argument fun this_is_not_correct(arg: u64) { ... } ``` A test can also be annotated as an `#[expected_failure]`. This annotation marks that the test is expected to raise an error. There are a number of options that can be used with the `#[expected_failure]` annotation to ensure only a failure with the specified condition is marked as passing, these options are detailed in [Expected Failures](#expected-failures). Only functions that have the `#[test]` annotation can also be annotated as an #`[expected_failure]`. Some simple examples of using the `#[expected_failure]` annotation are shown below: ```move #[test, expected_failure] public fun this_test_will_abort_and_pass() { abort 1 } #[test, expected_failure] public fun test_will_error_and_pass() { 1/0; } // Will pass since test fails with the expected abort code constant. // ENotFound is a constant defined in the module. #[test, expected_failure(abort_code = ENotFound)] public fun test_will_error_and_pass_abort_code() { abort ENotFound } // Will fail since test fails with a different error than expected. #[test, expected_failure(abort_code = my_module::ENotFound)] public fun test_will_error_and_fail() { 1/0; } #[test, expected_failure] // Can have multiple in one attribute. This test will pass. public fun this_other_test_will_abort_and_pass() { abort 1 } ``` > **Note**: `#[test]` and `#[test_only]` functions can also call > [`entry`](./functions#entry-modifier) functions, regardless of their visibility. ## Expected Failures There are a number of different ways that you can use the `#[expected_failure]` annotation to specify different types of error conditions. These are: ### 1. `#[expected_failure(abort_code = )]` This will pass if the test aborts with the specified constant value in the module that defines the constant and fail otherwise. This is the recommended way of testing for expected test failures. > **Note**: You can reference constants outside of the current module or package in > `expected_failure` annotations. ```move module pkg_addr::other_module { const ENotFound: u64 = 1; public fun will_abort() { abort ENotFound } } module pkg_addr::my_module { use pkg_addr::other_module; const ENotFound: u64 = 1; #[test, expected_failure(abort_code = ENotFound)] fun test_will_abort_and_pass() { abort ENotFound } #[test, expected_failure(abort_code = other_module::ENotFound)] fun test_will_abort_and_pass() { other_module::will_abort() } // FAIL: Will not pass since we are expecting the constant from the wrong module. #[test, expected_failure(abort_code = ENotFound)] fun test_will_abort_and_pass() { other_module::will_abort() } } ``` ### 2. `#[expected_failure(arithmetic_error, location = )]` This specifies that the test is expected to fail with an arithmetic error (e.g., integer overflow, division by zero, etc.) at the specified location. The `` must be a valid path to a module location, e.g., `Self`, or `my_package::my_module`. ```move module pkg_addr::other_module { public fun will_arith_error() { 1/0; } } module pkg_addr::my_module { use pkg_addr::other_module; #[test, expected_failure(arithmetic_error, location = Self)] fun test_will_arith_error_and_pass1() { 1/0; } #[test, expected_failure(arithmetic_error, location = pkg_addr::other_module)] fun test_will_arith_error_and_pass2() { other_module::will_arith_error() } // FAIL: Will fail since the location we expect it the fail at is different from where // the test actually failed. #[test, expected_failure(arithmetic_error, location = Self)] fun test_will_arith_error_and_fail() { other_module::will_arith_error() } } ``` ### 3. `#[expected_failure(out_of_gas, location = )]` This specifies that the test is expected to fail with an out of gas error at the specified location. The `` must be a valid path to a module location, e.g., `Self`, or `my_package::my_module`. ```move module pkg_addr::other_module { public fun will_oog() { loop {} } } module pkg_addr::my_module { use pkg_addr::other_module; #[test, expected_failure(out_of_gas, location = Self)] fun test_will_oog_and_pass1() { loop {} } #[test, expected_failure(arithmetic_error, location = pkg_addr::other_module)] fun test_will_oog_and_pass2() { other_module::will_oog() } // FAIL: Will fail since the location we expect it the fail at is different from where // the test actually failed. #[test, expected_failure(out_of_gas, location = Self)] fun test_will_oog_and_fail() { other_module::will_oog() } } ``` ### 4. `#[expected_failure(vector_error, minor_status = , location = )]` This specifies that the test is expected to fail with a vector error at the specified location with the given `minor_status` (if provided). The `` must be a valid path to a module location, e.g., `Self`, or `my_package::my_module`. The `` is an optional parameter that specifies the minor status of the vector error. If it is not specified, the test will pass if the test fails with any minor status. If it is specified, the test will only pass if the test fails with a vector error with the specified minor status. ```move module pkg_addr::other_module { public fun vector_borrow_empty() { &vector[][1]; } } module pkg_addr::my_module { #[test, expected_failure(vector_error, location = Self)] fun vector_abort_same_module() { vector::borrow(&vector[], 1); } #[test, expected_failure(vector_error, location = pkg_addr::other_module)] fun vector_abort_same_module() { other_module::vector_borrow_empty(); } // Can specify minor statues (i.e., vector-specific error codes) to expect. #[test, expected_failure(vector_error, minor_status = 1, location = Self)] fun native_abort_good_right_code() { vector::borrow(&vector[], 1); } // FAIL: correct error, but wrong location. #[test, expected_failure(vector_error, location = pkg_addr::other_module)] fun vector_abort_same_module() { other_module::vector_borrow_empty(); } // FAIL: correct error and location but the minor status differs so this test will fail. #[test, expected_failure(vector_error, minor_status = 0, location = Self)] fun vector_abort_wrong_minor_code() { vector::borrow(&vector[], 1); } } ``` ### 5. `#[expected_failure]` This will pass if the test aborts with _any_ error code. You should be **_incredibly careful_** using this to annotate expected tests failures, and always prefer one of the ways described above instead. Examples of these types of annotations are: ```move #[test, expected_failure] fun test_will_abort_and_pass1() { abort 1 } #[test, expected_failure] fun test_will_arith_error_and_pass2() { 1/0; } ``` ## Test Only Annotations A module and any of its members can be declared as test only. If an item is annotated as `#[test_only]` the item will only be included in the compiled Move bytecode when compiled in test mode. Additionally, when compiled outside of test mode, any non-test `use`s of a `#[test_only]` module will raise an error during compilation. > **Note**: functions that are annotated with `#[test_only]` will only be available to be called > from test code, but they themselves are not tests and will not be run as tests by the unit testing > framework. ```move #[test_only] // test only attributes can be attached to modules module abc { ... } #[test_only] // test only attributes can be attached to constants const MY_ADDR: address = @0x1; #[test_only] // .. to uses use pkg_addr::some_other_module; #[test_only] // .. to structs public struct SomeStruct { ... } #[test_only] // .. and functions. Can only be called from test code, but this is _not_ a test! fun test_only_function(...) { ... } ``` ## Running Unit Tests Use the `sui move test` command to run unit tests for a [Move package](./packages). When running tests, every test will either `PASS`, `FAIL`, or `TIMEOUT`. If a test case fails, the location of the failure along with the function name that caused the failure will be reported if possible. You can see an example of this below. A test will be marked as timing out if it exceeds the maximum number of instructions that can be executed for any single test. This bound can be changed using the options below. Additionally, while the result of a test is always deterministic, tests are run in parallel by default, so the ordering of test results in a test run is non-deterministic unless running with only one thread, which can be configured via an option. These aforementioned options are two among many that can fine-tune testing and help debug failing tests. To see all available options, and a description of what each one does, pass the `--help` flag to the `sui move test` command: ``` $ sui move test --help ``` ## Example A simple module using some of the unit testing features is shown in the following example: First create an empty package and change directory into it: ```bash $ sui move new test_example; cd test_example ``` Next add the following module under the `sources` directory: ```move // filename: sources/my_module.move module test_example::my_module; public struct Wrapper(u64) const ECoinIsZero: u64 = 0; public fun make_sure_non_zero_coin(coin: Wrapper): Wrapper { assert!(coin.0 > 0, ECoinIsZero); coin } #[test] fun make_sure_non_zero_coin_passes() { let coin = Wrapper(1); let Wrapper(_) = make_sure_non_zero_coin(coin); } #[test, expected_failure(abort_code = ECoinIsZero)] // Or #[test, expected_failure] if we don't care about the abort code fun make_sure_zero_coin_fails() { let coin = Wrapper(0); let Wrapper(_) = make_sure_non_zero_coin(coin); } #[test_only] // test only helper function fun make_coin_zero(coin: &mut Wrapper) { coin.0 = 0; } #[test, expected_failure(abort_code = ECoinIsZero)] fun make_sure_zero_coin_fails2() { let mut coin = Wrapper(10); coin.make_coin_zero(); let Wrapper(_) = make_sure_non_zero_coin(coin); } ``` ### Running Tests You can then run these tests with the `move test` command: ```bash $ sui move test INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING test_example Running Move unit tests [ PASS ] 0x0::my_module::make_sure_non_zero_coin_passes [ PASS ] 0x0::my_module::make_sure_zero_coin_fails [ PASS ] 0x0::my_module::make_sure_zero_coin_fails2 Test result: OK. Total tests: 3; passed: 3; failed: 0 ``` ### Using Test Flags #### Passing specific tests to run You can run a specific test, or a set of tests with `sui move test `. This will only run tests whose fully qualified name contains ``. For example if we wanted to only run tests with `"non_zero"` in their name: ```bash $ sui move test non_zero INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING test_example Running Move unit tests [ PASS ] 0x0::my_module::make_sure_non_zero_coin_passes Test result: OK. Total tests: 1; passed: 1; failed: 0 ``` #### `-i ` or `--gas_used ` This bounds the amount of gas that can be consumed for any one test to ``: ```bash $ sui move test -i 0 INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING test_example Running Move unit tests [ TIMEOUT ] 0x0::my_module::make_sure_non_zero_coin_passes [ FAIL ] 0x0::my_module::make_sure_zero_coin_fails [ FAIL ] 0x0::my_module::make_sure_zero_coin_fails2 Test failures: Failures in 0x0::my_module: ┌── make_sure_non_zero_coin_passes ────── │ Test timed out └────────────────── ┌── make_sure_zero_coin_fails ────── │ error[E11001]: test failure │ ┌─ ./sources/my_module.move:22:27 │ │ │ 21 │ fun make_sure_zero_coin_fails() { │ │ ------------------------- In this function in 0x0::my_module │ 22 │ let coin = MyCoin(0); │ │ ^ Test did not error as expected. Expected test to abort with code 0 │ │ └────────────────── ┌── make_sure_zero_coin_fails2 ────── │ error[E11001]: test failure │ ┌─ ./sources/my_module.move:34:31 │ │ │ 33 │ fun make_sure_zero_coin_fails2() { │ │ -------------------------- In this function in 0x0::my_module │ 34 │ let mut coin = MyCoin(10); │ │ ^^ Test did not error as expected. Expected test to abort with code 0 │ │ └────────────────── Test result: FAILED. Total tests: 3; passed: 0; failed: 3 ``` #### `-s` or `--statistics` With these flags you can gather statistics about the tests run and report the runtime and gas used for each test. You can additionally add `csv` (`sui move test -s csv`) to get the gas usage in a csv output format. For example, if we wanted to see the statistics for the tests in the example above: ```bash $ sui move test -s INCLUDING DEPENDENCY Bridge INCLUDING DEPENDENCY DeepBook INCLUDING DEPENDENCY SuiSystem INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING test_example Running Move unit tests [ PASS ] 0x0::my_module::make_sure_non_zero_coin_passes [ PASS ] 0x0::my_module::make_sure_zero_coin_fails [ PASS ] 0x0::my_module::make_sure_zero_coin_fails2 Test Statistics: ┌────────────────────────────────────────────────┬────────────┬───────────────────────────┐ │ Test Name │ Time │ Gas Used │ ├────────────────────────────────────────────────┼────────────┼───────────────────────────┤ │ 0x0::my_module::make_sure_non_zero_coin_passes │ 0.001 │ 1 │ ├────────────────────────────────────────────────┼────────────┼───────────────────────────┤ │ 0x0::my_module::make_sure_zero_coin_fails │ 0.001 │ 1 │ ├────────────────────────────────────────────────┼────────────┼───────────────────────────┤ │ 0x0::my_module::make_sure_zero_coin_fails2 │ 0.001 │ 1 │ └────────────────────────────────────────────────┴────────────┴───────────────────────────┘ Test result: OK. Total tests: 3; passed: 3; failed: 0 ``` --- # Compilation Modes A mode is a named, compile-time switch that controls which declarations are included in a build. - `#[mode(name1, name2, ...)]` filters declarations by enabled mode names. - `#[test_only] ≡ #[mode(test)]`. - Unannotated declarations are always included. - Annotated declarations are included if and only if any listed name is enabled. - Enabling any mode (including test) mean the build is not publishable. - Modes affect compile-time inclusion only; other than publishability, they are erased at the bytecode level. ## Mode Basics Modes are expressed with the attributes: ```move #[mode(name1, name2, ...)] #[test_only] // (shorthand for #[mode(test)]) ``` Code compiled with any mode enabled (including test) is not publishable. This section defines the syntax, inclusion rules, scope, and tool interactions for modes. (For an introductory tutorial with examples, see the guide page.) ### Mode Annotations The `#[mode(...)]` may be placed on modules and module members (functions, structs, constants, etc.). ```move #[mode(name1, name2, ...)] module :: { ... } module :: { #[mode(name1, name2, ...)] } ``` > **Note**: `#[test_only]` is exactly equivalent to `#[mode(test)]`. ## Mode Names Each name is a nonempty identifier. Mode names are compared case-sensitively. ## Inclusion model Let `M` be the set of enabled modes for a build. Let `S(m)` be the set of modes listed on declaration `m`, where `#[test_only]` contributes `{test}`, and unannotated declarations have `S(x) = ∅`. A declaration x is included in the compilation unit if and only if one of the following is true: * `S(x) = ∅` (unannotated) * `S(x) ∩ M ≠ ∅:` (annotation included) That is: unannotated declarations are always included; annotated declarations are included if and only if at least one of their listed names is enabled in the build, and otherwise they are excluded. ### Module scope If a module is excluded, all of its members are excluded implicitly. If a module is included, an annotated member may still be excluded if its own `S(m)` does not intersect `M`. ### Multiple modes on one attribute The list in `#[mode(a, b, c)]` is disjunctive (logical OR): inclusion occurs if any listed name matches. ## Name resolution & duplicates Modes are a compile-time filter only. They do not introduce runtime conditionals and have no representation in bytecode. All verification is performed on the included subset of the source. Standard name resolution rules apply when duplicates are present. That means two modes may not enable different modules or members with the same name in the same build. Similarly, a mode-annotated definition may not override an unannotated declaration with the same name. To provide mode-specific alternatives, place them in separate modules gated by modes, or use distinct names and select them in tests or drivers. ## Usage Tooling & flags For building and testing, `move build --mode ` adds `` to `M`. Multiple modes may be enabled by passing `--mode` repeatedly; `M ` is the union of all names passed, e.g., `move build --mode test --mode debug`. This will enable all modules and members annotated with either `#[mode(test)]` or `#[mode(debug)]`. Note that `move test` implicitly supplies `--mode test`. ## Publishability Any build that enables at least one mode (including test) produces non-publishable outputs. To create publishable artifacts, no modes may be enabled. --- # Module Extensions **Module Extensions** let a package add new declarations to an existing module **as if** they were defined inside that module. Extensions are opt-in via a mode attribute and never modify or remove existing items. ### Example Imagine you have an off-the-shelf module that you want to test in your package, but it lacks some internal accessors or testing operations that would allow you to write full tests over it. As a toy example, consider a simple counter module defined as a library: ```move module counter::counter { public struct Counter has drop { value: u64 } public fun new(): Counter { Counter { value: 0 } } public fun incr(mut c: Counter): Counter { c.value = c.value + 1; c } public fun destroy(c: Counter): u64 { let Counter { value } = c; value } } ``` You might use this module in your own package to implement a step counter: ```move module app::step_counter { use counter::counter::{Counter, new, incr, destroy}; enum Step { Once, Many(u64) } public fun step(c: Counter, s: Step): Counter { match s { Step::Once => incr(c), Step::Many(n) => { let mut c = c; let mut i = 0; while (i < n) { c = incr(c); i = i + 1; } c } } } } ``` Suppose you wanted to write additional tests for this counter behavior, including ensuring invariants and the ability to peek at the current value without consuming the counter. Extensions allow you to add this behavior as test definitions in your own package without forking and updating the downstream dependency. ```move #[test_only] extend module counter::counter { /// Peek at the current value without consuming the counter. public fun peek(c: &Counter): u64 { c.value } } #[test_only] extend module app::step_counter { use counter::counter::{Counter, new, incr, peek}; // Local test helper to keep assertions tidy. fun expect_value(c: &Counter, want: u64) { assert!(c.peek() == want, 0); } /// Equivalence: Once == Many(1). #[test] fun once_equals_many1() { let c1a = step(new(), Step::Once); let c1b = step(new(), Step::Many(1)); expect_value(&c1a, 1); expect_value(&c1b, 1); } } ``` In this usage, you extend both the `counter::counter` module (to add helpers and tests) and the `app::step_counter` module (to add tests for the step logic). All of this code lives in your package, and it only affects test builds. The publishable code remains unchanged. > **Note**: Extensions can only add new items; they cannot modify or remove existing items. In > > addition, only extensions defined in the root package are applied (extensions in dependencies are > not). ## Extension Syntax Extension are defined by adding the `extend` keyword before the `module` keyword: ```move #[mode(name1, name2, ...)] // or #[test_only] extend module
:: { ( | | | )* } ``` Extensions are allowed for single-file module forms: ```move #[mode(test)] extend module p
::; ( | | | )* ``` In both cases: * The extension must define a mode attribute. * `
::` is the package and module name. * The module elements are as in a standard [module](modules). * The extension block is compiled into the target module under the enabled modes. * `
::` must resolve to an existing module in the current build. ## Applying Extensions Let `M` be a module in the current build. Let `E1, E2, ... En` be all extensions targeting `M` such that: - `Ei` is defined in the root package of the current build (others are ignored). - `Ei` targets `M` - `Ei` has an active mode attribute. During expansion, the effective contents of `M` are transformed into: ``` module M { ... original contents of M ... ... contents of E1 ... ... contents of E2 ... ... ... contents of En ... } ``` Name resolution, visibility, edition rules, type checking, etc., are applied to the resultant module as a whole. This means each declaration in an extension is treated as if it were written directly in the target module, and subject to the same visibility, edition features, duplicate definition errors, name conflicts, etc. This means that extensions may not modify or override existing declarations, and may not shadow existing `use` definitions, etc. New use definitions may be added, but their compilation is still subject to decidable dependency ordering, as described in the [`use`](uses) section. > **Tip**: Extension code is subject of the same edition features as the target module. If the > target module is in an older edition, the extension code must also be compatible with that > edition. --- # Coding Conventions See [Move Best Practices](https://docs.sui.io/guides/developer/move-best-practices) and [Code Quality Checklist](/guides/code-quality-checklist). --- # DEPRECATED: Friends NOTE: this feature has been superseded by [`public(package)`](./functions#visibility). The `friend` syntax was used to declare modules that are trusted by the current module. A trusted module is allowed to call any function defined in the current module that have the `public(friend)` visibility. For details on function visibilities, refer to the _Visibility_ section in [Functions](./functions). ## Friend declaration A module can declare other modules as friends via friend declaration statements, in the format of - `friend ` — friend declaration using fully qualified module name like the example below, or ```move module 0x42::a { friend 0x42::b; } ``` - `friend ` — friend declaration using a module name alias, where the module alias is introduced via the `use` statement. ```move module 0x42::a { use 0x42::b; friend b; } ``` A module may have multiple friend declarations, and the union of all the friend modules forms the friend list. In the example below, both `0x42::B` and `0x42::C` are considered as friends of `0x42::A`. ```move module 0x42::a; friend 0x42::b; friend 0x42::c; ``` Unlike `use` statements, `friend` can only be declared in the module scope and not in the expression block scope. `friend` declarations may be located anywhere a top-level construct (e.g., `use`, `function`, `struct`, etc.) is allowed. However, for readability, it is advised to place friend declarations near the beginning of the module definition. ### Friend declaration rules Friend declarations are subject to the following rules: - A module cannot declare itself as a friend. ```move module 0x42::m { friend Self; // ERROR! } // ^^^^ Cannot declare the module itself as a friend module 0x43::m { friend 0x43::M; // ERROR! } // ^^^^^^^ Cannot declare the module itself as a friend ``` - Friend modules must be known by the compiler ```move module 0x42::m { friend 0x42::nonexistent; // ERROR! } // ^^^^^^^^^^^^^^^^^ Unbound module '0x42::nonexistent' ``` - Friend modules must be within the same account address. ```move module 0x42::m {} module 0x42::n { friend 0x42::m; // ERROR! } // ^^^^^^^ Cannot declare modules out of the current address as a friend ``` - Friends relationships cannot create cyclic module dependencies. Cycles are not allowed in the friend relationships, e.g., the relation `0x2::a` friends `0x2::b` friends `0x2::c` friends `0x2::a` is not allowed. More generally, declaring a friend module adds a dependency upon the current module to the friend module (because the purpose is for the friend to call functions in the current module). If that friend module is already used, either directly or transitively, a cycle of dependencies would be created. ```move module 0x2::a { use 0x2::c; friend 0x2::b; public fun a() { c::c() } } module 0x2::b { friend 0x2::c; // ERROR! // ^^^^^^ This friend relationship creates a dependency cycle: '0x2::b' is a friend of '0x2::a' uses '0x2::c' is a friend of '0x2::b' } module 0x2::c { public fun c() {} } ``` - The friend list for a module cannot contain duplicates. ```move module 0x42::a {} module 0x42::m { use 0x42::a as aliased_a; friend 0x42::A; friend aliased_a; // ERROR! // ^^^^^^^^^ Duplicate friend declaration '0x42::a'. Friend declarations in a module must be unique } ```