Description
Method syntax is a syntactic transformation that allows for functions to be called “on” a value rather than directly from a module. The goal of this change is to greatly improve the ease of programming in Move.
For example
let c2: Coin<SUI> = c.withdraw(10);
which would expand to
let c2: Coin<SUI> = sui::coin::withdraw(&mut c, 10);
A new syntax, use fun
, will be introduced for adding either public
or internal method aliases. This feature can be used for creating an alias to “rename” a method. Or it can be used to create a local method alias outside of the associated types defining module.
See the main issue for more on Move 2024 changes
Design
When calling e.g(arg_0, ..., arg_n)
, the compiler will statically resolve g
depending on the type of e
. The compiler will keep a method alias table (dependent on aliases defined in that scope) to resolve these functions. The compiler will then automatically borrow e
depending on the signature of the function.
So assuming e: X
and there is an alias to resolve e.g
to a::m::f
where the first argument to f
is &mut X
, the compiler will resolve e.g(arg_0, ..., arg_n)
to a::m::f(&mut e, arg_0, ..., arg_n)
The auto-borrowing and signature aspect of the expansion should be more apparent with examples. But before looking at examples, let’s look at this “method alias” system a bit more:
use fun a::m::f as X.g;
creates a method alias local to the scope (much like normaluse
aliases).- Global aliases (used by any code without importing) are declared by marking the
use fun
aspublic
, e.g.public use fun a::m::f as X.g;
. But! Only the module that definesX
can declare apublic use fun
. More on this below. - In a type’s declaring module, the compiler will automatically create a
public use fun
for any function declaration for its types when the type is the first argument in the function.- For example, in module
a::m
that defines a typeX
, the functionfun foo(x: &X) { ... }
gets an implicit aliaspublic use fun foo as X.foo;
. This is done regardless of the visibility offoo
so that these aliases are discoverable in all cases, which is particularly helpful forpublic(package)
functions.
However, the functionfun bar(flag: bool, x: &X) { ... }
would not get an implicit alias sinceX
is not the first argument (and one is not created forbool
sincebool
is not defined in that module).
- For example, in module
- Normal
use
aliases for functions also create an implicituse fun
when the functions first argument is a type declared in that module, e.g.use a::m::f as g
also creates ause fun a::m::f as X.g
assuminga::m::X
is the first argument off
(either by reference or by value)- Alternatively, you could view this as the alias
use a::m::f as g
as affecting the implicitpublic use fun a::m::f as X.g
from the modulea::m
- Alternatively, you could view this as the alias
The goals around this aliasing system is to give power and flexibility to programmers, particularly when migrating old code that might not have been written with method syntax in mind. But above all we want this feature to feel explicit and predictable. A way to think about this is that any additions to a package’s dependencies will not affect existing method calls. This is achieved by always relying on local use fun
s or use
aliases, and falling back to the defining module when none are present; the local aliases take precedence which ensures additions in dependencies do not change how method calls in existing packages resolve.
Examples
Automatic Borrowing
module a::cup {
public struct Cup<T> { value: T }
public fun borrow<T>(cup: &Cup<T>): &T { &cup.value }
public fun borrow_mut<T>(cup: &mut Cup<T>): &mut T { &mut cup.value }
public fun value<T>(cup: Cup<T>): T { let Cup { value } = cup; value }
}
module b::examples {
use a::cup::Cup;
// Examples showing the three cases for how a value is used
fun examples<T>(mut cup: Cup<T>): T {
// The type annotations are not necessary, but here for clarity.
// automatic immutable borrow
let _: &T = cup.borrow();
// automatic mutable borrow
let _: &mut T = cup.borrow_mut();
// no borrow needed
cup.value()
}
// Same example but with the method calls expanded
fun expanded_examples<T>(mut cup: Cup<T>): T {
let _: &T = a::cup::borrow(&cup);
let _: &mut T = a::cup::borrow_mut(&mut cup);
a::cup::value(cup)
}
}
Public Use Fun
module a::shapes {
public struct Rectangle { base: u64, height: u64 }
public struct Box { base: u64, height: u64, depth: u64 }
public fun rectangle(base: u64, height: u64): Rectangle {
Rectangle { base, height }
}
// 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 rectangle_height as Rectangle.height;
public fun rectangle_height(rectangle: &Rectangle): u64 {
rectangle.height
}
public fun box(base: u64, height: u64, depth: u64): Box {
Box { base, height, depth }
}
public use fun box_base as Box.base;
public fun box_base(box: &Box): u64 {
box.base
}
public use fun box_height as Box.height;
public fun box_height(box: &Box): u64 {
box.height
}
public use fun box_depth as Box.depth;
public fun box_depth(box: &Box): u64 {
box.depth
}
}
module b::examples {
use a::shapes::{Rectangle, Square};
// Example using a public use fun
fun example(rectangle: &Rectangle, box: &Box): u64 {
(rectangle.base() * rectangle.height()) +
(box.base() * box.height() * box.depth())
}
// Same example but with the method calls expanded
fun expanded_example(rectangle: &Rectangle, box: &Box): u64 {
(a::shapes::rectangle_base(rectangle) *
a::shapes::rectangle_height(rectangle)) +
(a::shapes::box_base(box) *
a::shapes::box_height(box) *
a::shapes::box_depth(box))
}
}
Use Fun
module a::shapes {
public struct Rectangle { base: u64, height: u64 }
public struct Box { base: u64, height: u64, depth: u64 }
public fun rectangle(base: u64, height: u64): Rectangle {
Rectangle { base, height }
}
public use fun rectangle_base as Rectangle.base;
public fun rectangle_base(rectangle: &Rectangle): u64 {
rectangle.base
}
public use fun rectangle_height as Rectangle.height;
public fun rectangle_height(rectangle: &Rectangle): u64 {
rectangle.height
}
public fun box(base: u64, height: u64, depth: u64): Box {
Box { base, height, depth }
}
public use fun box_base as Box.base;
public fun box_base(box: &Box): u64 {
box.base
}
public use fun box_height as Box.height;
public fun box_height(box: &Box): u64 {
box.height
}
public use fun box_depth as Box.depth;
public fun box_depth(box: &Box): u64 {
box.depth
}
}
module b::examples {
use a::shapes::{Rectangle, Box};
use fun into_box as Rectangle.into_box;
fun into_box(rectangle: &Rectangle, depth: u64): Box {
a::shapes::box(rectangle.base(), rectangle.height(), depth)
}
// Example using a local use fun
fun example(rectangle: &Rectangle): Box {
rectangle.into_box(1)
}
// Same example but with the method calls expanded
fun expanded_example(rectangle: &Rectangle): Box {
into_box(rectangle, 1)
}
}
Some Uses Create Implicit Use Funs
module a::shapes {
public struct Rectangle { base: u64, height: u64 }
public struct Box { base: u64, height: u64, depth: u64 }
public fun rectangle(base: u64, height: u64): Rectangle {
Rectangle { base, height }
}
public use fun rectangle_base as Rectangle.base;
public fun rectangle_base(rectangle: &Rectangle): u64 {
rectangle.base
}
public use fun rectangle_height as Rectangle.height;
public fun rectangle_height(rectangle: &Rectangle): u64 {
rectangle.height
}
}
module b::examples {
use a::shapes::Rectangle;
// Example using a local use fun
fun example(rectangle: &Rectangle): u64 {
use a::shapes::{rectangle_base as b, rectangle_height as h};
// implicit 'use fun a::shapes::rectangle_base as Rectangle.b'
// implicit 'use fun a::shapes::rectangle_height as Rectangle.h'
rectangle.b() * rectangle.h()
}
// Same example but with the method calls expanded
fun expanded_example(rectangle: &Rectangle): Box {
a::shapes::rectangle_base(rectangle) *
a::shapes::rectangle_height(rectangle)
}
}
Activity