Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Move 2024] Method Syntax #14063

Open
tnowacki opened this issue Oct 2, 2023 · 1 comment
Open

[Move 2024] Method Syntax #14063

tnowacki opened this issue Oct 2, 2023 · 1 comment
Labels
design devx move Type: Major Feature Major new functionality or integration, which has a significant impact on the network

Comments

@tnowacki
Copy link
Contributor

tnowacki commented Oct 2, 2023

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 normal use aliases).
  • Global aliases (used by any code without importing) are declared by marking the use fun as public, e.g. public use fun a::m::f as X.g;. But! Only the module that defines X can declare a public 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 type X, the function fun foo(x: &X) { ... } gets an implicit alias public use fun foo as X.foo; . This is done regardless of the visibility of foo so that these aliases are discoverable in all cases, which is particularly helpful for public(package) functions.
      However, the function fun bar(flag: bool, x: &X) { ... } would not get an implicit alias since X is not the first argument (and one is not created for bool since bool is not defined in that module).
  • Normal use aliases for functions also create an implicit use fun when the functions first argument is a type declared in that module, e.g. use a::m::f as g also creates a use fun a::m::f as X.g assuming a::m::X is the first argument of f (either by reference or by value)
    • Alternatively, you could view this as the alias use a::m::f as g as affecting the implicit public use fun a::m::f as X.g from the module a::m

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 funs 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)
    }
}
@tnowacki tnowacki added Type: Major Feature Major new functionality or integration, which has a significant impact on the network devx move design labels Oct 2, 2023
Copy link
Contributor

github-actions bot commented Dec 2, 2023

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@github-actions github-actions bot added the Stale label Dec 2, 2023
@tnowacki tnowacki removed the Stale label Dec 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design devx move Type: Major Feature Major new functionality or integration, which has a significant impact on the network
Projects
None yet
Development

No branches or pull requests

1 participant