Description
This year, we plan to fully release a set of new features to the language. Many of these changes are enhancements to the source language (that is they will affect the compiler without requiring any changes to the binary representation that is published on chain).
Primarily, the goal of these changes is to make Move easier to write, and hopefully easier to read. Secondarily, we will make a few breaking changes to the source language to better position the language for the future.
Do not worry though, existing code will continue to compile! So these new features and the breaking changes will be opt-in. Any existing code will continue to compile. This also means that you will be able to write your packages with the new features, even if your dependencies do not.
These features will be developed and rolled-out over the coming months (and some have already landed!).
-
To try these features out and provide feedback, you can specify
edition = "2024.alpha"
under the[package]
section in yourMove.toml
WARNING! Features released under
alpha
will be unstable! Each time you update the compiler, any code inedition = "2024.alpha"
might fail to compile, even though it previously did. We definitely want folks to try out the features here! But with the unstable nature of the code, it should not be used in scenarios where you will need to have a persistent source file; for example, you should not use this edition if you want your package to work with source verification (that is if you want to be able to match your source code with some published bytecode package) -
Once features have stabilized a bit, and all breaking changes have been added, we will release a release candidate edition, i.e.
edition = "2024.rc"
. Unlikealpha
, code written in this edition should not break when you update your compiler. While that is our goal, keep in mind it still is arc
and things might break depending on bug fixes or changes related to feedback. In short,rc
will be more stable but be wary that there might be some breakages. -
Sometime later in the year, we will finalize the edition and
edition = "2024"
will become the default for all new Move packages! At that point, any further changes or breakages will come in a future edition.
Outlined below are the planned features and changes. If you have suggestions or feedback (either for the outlined features or for new features entirely), please let us know!
Major Features
Method Syntax
Method syntax is a syntactic transformation that allows for functions to be called “on” a value rather than directly from a module.
For example
let c2: Coin<SUI> = c.withdraw(10);
which would expand to
let c2: Coin<SUI> = sui::coin::withdraw(&mut c, 10);
There will also be additional syntax for adding new methods, without having to declare a new function, using the new use fun
alias declaration. See the issue for more details.
Index Syntax
Building on Method syntax, we will add syntax for index accesses depending on the type of access. Tentatively:
&x[i]
expands tox.borrow(i)
&mut x[i]
expands tox.borrow_mut(i)
x[i]
expands to*x.borrow(i)
x[i] = v
expands tox.assign(i, v)
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 (or closures or function pointers), which makes defining these sorts of operations impossible.
Macro functions will allow for Move to mimic these sort of operations, without supporting the behavior at runtime. The body of the macro mimicking the "higher-order function" will get inlined at each call site. And the call site can provide a "lambda" that will be substituted in 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
- [move-enums] Base PR for Move Enums in VM/IR #15086
- [move-2024][move] Add enum support to move #15590
Enumerations allow you to define a single type that may hold multiple different shapes of data. Unlike struct
s which always have the same fields, enum
s can have different fields depending on the variant of the enum
. For example, enum Option<T> { None, Some(T) }
the variant None
has no fields and the variant Some
has a single field of type T
.
Move will allow 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 is adding 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. But, we have plans to add public variants in the future. Similarly, enumerations cannot be recursive at release, but we have plans on supporting this in the future.
Smaller Improvements
public(package)
Since friend
declarations can only be made within the same package, it feels a bit strange to require explicit friend
declarations.
public(package)
will replace public(friend)
, and the compiler will do the rest, eliminating the need for explicit friend
declarations.
Positional fields
For simple wrappers, it can be annoying to have to declare field names. Positional fields can make this a bit less tiresome, e.g.
struct Wrapper(u64)
Postfix has
ability declarations
With positional fields, it is a bit awkward to read has
declarations in the middle of a declaration. As an alternative, has
can now be written after the fields. For example, both will be valid:
struct Wrapper1 has copy, drop, store (u64)
struct Wrapper2(u64) has copy, drop, store;
Type inference holes _
on type annotations
With type directed programming, often you need to annotate a variable declaration or provide type arguments. But, sometimes you really only need to annotate on specific type, but the other types can be inferred. _
will be added to allow that type to still be inferred, even when other parts of the type are annotated. For example
dynamic_field::borrow_mut<address, Coin<SUI>>(&mut id, owner)
could be rewritten as
dynamic_field::borrow_mut<_, Coin<SUI>>(&mut id, owner)
where the _
would be inferred as address
break
with value
While Move is an expression based language, it is cumbersome to extract values from a loop
concisely. In the 2024 edition, break
will be able now take a value. This should help code be more concise and less nested. For example:
let mut i = 0;
let first_over_10;
loop {
if (v[i] > 10) {
first_over_10 = i;
break
};
i = i + 1;
};
first_over_10
can be rewritten as
let mut i = 0;
loop {
if (v[i] > 10) break i;
i = i + 1;
}
Named blocks with enhanced control flow operations
Move 2024 supports naming loop
, while
, and normal blocks, allowing for more-complex control
flow.
Previous code with nested while
loops (such as this simplified excerpt from deepbook) would need to
set a flag to break both loops:
let mut terminate_loop = false;
while (...loop_condition...) {
while (...inner_condition...) {
...
if (...break_condition...) {
terminate_loop = true;
}
...
if (terminate_loop) {
break;
}
}
if (terminate_loop) {
break;
}
}
Now we can directly name the outer loop and break it all at once:
let mut terminate_loop = false;
while (...loop_condition...) 'outer: {
while (...inner_condition...) {
...
if (...break_condition...) {
terminate_loop = true;
}
...
if (terminate_loop) {
break 'outer;
}
}
}
This will immediately break to the outer loop, allowing us more-precise control flow when we'd like
to escape from loops.
This feature also works with normal loop forms, including breaks with values:
let y = loop 'outer: {
let _x = loop 'inner: {
if (true) {
break 'outer 10;
} else {
break 'inner 20
}
};
};
In this toy example, y
will take on the value 10
because the first break
will break the
'outer
loop with that value.
Finally, this feature can be apply to normal blocks in Move, but instead utilizes the return
keyword. This can be useful when sequencing a block of code that may need early returns with values.
public fun auth_user(auth_one: EasyAuth, auth_two: AuthTwo): u64 {
let auth_token = 'auth: {
let maybe_auth_token = try_auth(auth_one);
if (valid_auth(maybe_auth_token)) {
return 'auth unpack_auth(maybe_auth_token);
}
// ... more complicated code involving auth_two
};
// ... code using the auth_token
}
While we do not expect programmers to use named blocks with return in everyday cases, we anticipate
that they will ease the development and usage of macros significantly.
Breaking Changes
public struct
While struct
declarations in Move can currently only be “public”, they do not currently require the visibility modifier when declared. To make room for a future where struct types have visibility other than “public”, we will be requiring public
on all struct
declarations in the 2024 edition.
let mut
Today, all local variables in Move can be assigned x = e
and mutably borrowed &mut x
. While Move has been influenced by Rust greatly (especially in regard to references and the borrow checker), we did not see the need to require variables to be annotated as mut
before being modified or mutably borrowed; our reasoning being that you could always look locally to check for assignments or mutable borrows.
In the 2024 edition however, the new “method syntax” feature will automatically borrow locals when some circumstances. For example, c.withdraw(10)
might expand to coin::withdraw(&mut c, 10)
. The point being that c
is being mutably borrowed implicitly, so you can no longer check locally for modifications to local variables when reading Move code.
To improve readability and understandability in the presence of method calls (and any other potential language features down the line), we will be requiring mut
annotations to be added to all local variables if they are assigned or mutably borrowed.
New Keywords
Move 2024 will add new keywords that were previously accepted as identifiers. The list of new keywords is:
mut
enum
type
match
To help with any migrations of existing fields, functions, or local variables with these names, new syntax has been added that lets you use a keyword as an identifier, for example
let `type` = 0; `type` + 1
In short, any keyword can be used as an identifier by escaping it in backticks, `
.
Namespace Revisions
In legacy Move, addresses, modules, module identifiers, and locals all live in separate namespaces.
This allows for users to write rather confusing code, such as:
module a::b {
public fun a(a: u64): u64 {
a
}
}
module a::a {
fun a(a: u64): u64 {
if (a == 0) {
a::a::a(1)
} else {
a * a(a - 1)
}
}
}
While nobody would write this specific program, there are some cases on-chain where we have found
name collisions. Move 2024 combines address, module, and non-function module member namespaces (leaving locals and functions to
their own namespace). The code above will now produce the following error:
error[E03006]: unexpected name in this position
┌─ tests/move_2024/naming/separate_namespaces.move:10:13
│
10 │ a::a::a(1)
│ ^ Expected an address in this position, not a module
This is because the a
name for our module shadows the a
address alias.
Similarly, use statements that both define names will now produce errors. For example, the following
use statement use a::a::{Self, a};
will produce an error:
┌─ tests/move_2024/naming/separate_namespaces.move:17:26
│
17 │ use a::a::{Self, a};
│ ---- ^ Duplicate module member or alias 'a'. Top level names in a namespace must be unique
│ │
│ Alias previously defined here
This namespace evolution is toward making it easier to work with enums and enum variants, but we
also hope that it will improve code clarity and help programmers avoid ambiguity.
Other Move Changes
While not related directly to compiler editions, we do have other new features coming to Move!
The final version of these features will not be gated by any edition, and will be available across all editions. (Though as they are developed, some of them might be gated by editions until everything is finalized)
Major Features
Auto Formatter
A long requested feature has been an auto-formatter for Move. We are still in the early stages of planning and starting this work, but it is in the works.
Linting Framework
Still in the early days, but we are working on the a linting framework for Move. Most of the lints today are geared towards best practices in Sui, but we will add other lints over time.
If you have feedback for lints or have suggestions for future lints, please open an issue!
Smaller Improvements
- [move-compiler] Added warning suppression #12084
The compiler now allows warnings to be suppressed with the#[allow(<warning name>)]
annotation. Warnings should also note how to suppress the given warning. All warnings can be suppressed with#[allow(all)]
- [move-compiler][sui-mode] Add entry function signature checks #12990
[move-compiler][sui-mode] Add init and one-time witness rules #13335
[move-compiler][sui-mode] Added struct declaration, private generics, and global storage checks #13482
The compiler will now give specific diagnostics for rules present in Sui. Previously, these messages were only present at the bytecode level. This should make these problems easier to diagnose and fix. They can be enabled by setting the flavor toflavor = "sui"
in theMove.toml
, or by using thesui move
CLI. - [move-compiler] revise CFG creation, optimize known jumps #13497
The compiler has greatly improved its code generation for the layout of blocks. This should result in some small reductions in gas costs. - [move-compiler] Make addresses in error messages more readable #14049
The compiler has improved readability of named addresses in error messages, expanding to their numerical value only in cases of ambiguity - [move compiler] Report unnecessary mutable references #14216
The compiler will now warn on unnecessary usages of mutable references&mut
. This can be suppressed for parameters with a leading_
.
Activity