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:
<expression> . <identifier> <[type_arguments],*> ( <arguments> )
For example
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 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 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,
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,
fun example(x: a::m::X) {
x.foo(); // valid
// x.bar(true); ERROR!
}
use fun
Aliases
Like a traditional use
, 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:
use fun <function> as <type>.<method alias>;
This creates an alias for the <function>
, which the <type>
can receive as <method alias>
.
For example
module a::cup {
public struct Cup<T>(T) has copy, drop, store;
public fun cup_borrow<T>(c: &Cup<T>): &T {
&c.0
}
public fun cup_value<T>(c: Cup<T>): T {
let Cup(t) = c;
t
}
public fun cup_swap<T: drop>(c: &mut Cup<T>, t: T) {
c.0 = t;
}
}
We can now create use fun
aliases to these functions
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<u64>) {
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 <function>
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
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
module b::example {
fun double(c: &Cup<u64>): Cup<u64> {
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
fun double_double(c: Cup<u64>): (Cup<u64>, Cup<u64>) {
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 <function>
of the use fun
must have a first
argument that is the same as the <type>
.
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 <type>
can be used, including references and mutable references
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.
public struct Cup<T>(T) has copy, drop, store;
public fun value<T: copy>(c: &Cup<T>): T {
c.0
}
use fun value as Cup<bool>.flag; // ERROR!
use fun value as Cup<u64>.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
, 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 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.
module a::cup {
public struct Cup<T>(T) has copy, drop, store;
public use fun cup_borrow as Cup.borrow;
public fun cup_borrow<T>(c: &Cup<T>): &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
.
module b::example {
fun example<T: drop>(c: a::cup::Cup<u64>) {
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.
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.
module a::cup {
public struct Cup<T>(T) has copy, drop, store;
public fun new<T>(t: T): Cup<T> { Cup(t) }
public fun borrow<T>(c: &Cup<T>): &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<u64>(c: Cup<u64>): (Cup<u64>, Cup<u64>) {
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.
module a::cup {
public struct Cup<T>(T) has copy, drop, store;
public fun cup_borrow<T>(c: &Cup<T>): &T {
&c.0
}
}
module b::other {
use a::cup::{Cup, cup_borrow as borrow};
fun example(c: &Cup<u64>) {
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 for more details.
Scoping
If not public
, a use fun
alias is local to its scope, much like a normal use
. For
example
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
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
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
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.
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.
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.