Introducing Move 2024
IOTA launches with the "Move 2024.beta" edition, bringing enhanced features to improve both the writing and readability of Move code. These updates refine the source language without impacting the on-chain binary representation, allowing users to enjoy a smoother experience with the latest language improvements.
The primary goal of the "Move 2024.beta" edition is to make Move easier to write and, ideally, easier to read. The minimal breaking changes introduced in the source language are designed to better prepare Move for future advancements.
This document highlights some new features to try out and shows how to use them in your existing modules.
Please, provide any feedback or report any issues you encounter via GitHub or the IOTA Builders Discord.
New features
Below are examples of how to rewrite some legacy code in the Move 2024 edition. If you find legacy code in the Move examples and would like to rewrite it based on Move 2024, refer to these examples.
Method Syntax
You can now call certain function methods using the .
syntax. For example, the following call:
vector::push_back(&mut v, coin::value(&c));
Can now be written as:
v.push_back(c.value());
Where the receiver of the method (v
and c
in this example) is automatically borrowed if necessary (as &mut v
and &c
respectively).
You can call any function defined in the same module as the receiver's type as a method if it takes the receiver as its first argument.
For functions defined outside the module, you can declare methods using public use fun
and use fun
.
Index Syntax
With method syntax, you can annotate certain functions as being #[syntax(index)]
methods. You then call these methods using v[i]
-style calls.
For example,
*&mut v[i] = v[j];
resolves to
*vector::borrow_mut(&mut v, i) = *vector::borrow(&v, j);
Macro Functions
Higher-order functions (such as map,
filter,
fold,
for_each,
etc.) are useful in many languages for concisely transforming collections. Move does not have lambdas, closures, or function pointers, making defining these operations impossible.
Macro functions will allow for Move to mimic these sorts of operations without supporting the behavior at runtime. The body of the macro mimicking the "higher-order function" will be inlined at each call site. And the call site can provide a "lambda" that will be substituted as the macro is expanded. For example:
let v2 = v.map!(|x| x + 1);
Or
v.for_each!(|x| foo(x));
The "lambdas" additionally will support control flow through break and return.
Enums
Enumerations allow you to define a single type that may hold multiple different shapes of data. Unlike structs, which always have the same fields, enums can have different fields depending on the variant of the enum. For example, in enum Option<T> { None, Some(T) }
, the variant None has no fields, and the variant Some has a single field of type T
.
Move allows destructuring enums using match expressions. Some examples of enums in Move are the following:
public enum Color {
RGB { red: u8, green: u8, blue: u8 },
HSL { hue: u16, saturation: u8, lightness: u8 },
Hex(u32)
}
public enum Option<T> {
None,
Some(T),
}
public fun is_rgb_color(color: &Color): bool {
match (color) {
Color::RGB { red: _, green: _, blue: _ } => true,
_ => false,
}
}
const EOptionIsNone: u64 = 0;
public fun unwrap_some<T>(option: Option<T>): T {
match (option) {
Option::Some(x) => x,
Option::None => abort EOptionIsNone,
}
}
Move will add support for basic high-level enums that have similar visibility rules to structs in Move today; the enumeration type is publicly visible, just like struct types, but the variants of the enumeration are not public, much like fields. However, we plan to add public variants in the future. Similarly, enumerations cannot be recursive at release, but we plan to support this in the future.