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-book] Function Value Documentation #858

Merged
merged 4 commits into from
Mar 21, 2025
Merged

[move-book] Function Value Documentation #858

merged 4 commits into from
Mar 21, 2025

Conversation

wrwg
Copy link
Contributor

@wrwg wrwg commented Mar 20, 2025

No description provided.

@wrwg wrwg requested review from vineethk and rahxephon89 March 20, 2025 03:07
Copy link

vercel bot commented Mar 20, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
developer-docs-nextra ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 21, 2025 3:30am

Comment on lines 878 to 879
module account { ... }
module caller {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So syntax highlighting works correctly, and it's valid syntax, it needs an address. I've been using 0x42 in examples

Suggested change
module account { ... }
module caller {
module 0x42::account { ... }
module 0x42::caller {

Comment on lines 852 to 866
module caller {
use addr::callee;
struct R{ count: u64 } has key;
fun calling() acquires R {
let r = &mut R[@addr];
// This callback is OK, because `R` is not accessed
callee::call_me(r, |x| do_something(x))
// This callback will lead to reentrancy runtime error
callee::call_me(r, |_| R[@addr].count += 1)
r.call_count += 1
}
fun do_something(r: &mut R) { .. }
}

module callee {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar with the addresses here

}

module callee {
fun call_me<T(x: &mut T, action: |&mut T|) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a closing >

Suggested change
fun call_me<T(x: &mut T, action: |&mut T|) {
fun call_me<T>(x: &mut T, action: |&mut T|) {

```move
module caller {
use addr::callee;
struct R{ count: u64 } has key;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
struct R{ count: u64 } has key;
struct R { count: u64 } has key;


_Since language version 2.2_ (preview)

Move supports *function values* as first-class citizen of the language. A function value is constructed from the name of a function or by a lambda expression, and is evaluated by passing parameters to it and causing the underlying function to be executed. This feature is often also called _dynamic dispatch_. Which concrete function is called, is not known to the caller, and determined from the runtime value. Dynamic dispatch is an important tool for composing applications. Move makes dynamic dispatch safe by providing builtin protection mechanisms against reentrancy, which can be further refined by user choice.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some simplification

Suggested change
Move supports *function values* as first-class citizen of the language. A function value is constructed from the name of a function or by a lambda expression, and is evaluated by passing parameters to it and causing the underlying function to be executed. This feature is often also called _dynamic dispatch_. Which concrete function is called, is not known to the caller, and determined from the runtime value. Dynamic dispatch is an important tool for composing applications. Move makes dynamic dispatch safe by providing builtin protection mechanisms against reentrancy, which can be further refined by user choice.
Move supports *function values* as first-class citizen of the language. A function value is constructed from the name of a function or by a lambda expression, and is evaluated by passing parameters to it and executing the underlying function. This feature is often also called _dynamic dispatch_. The caller does not know which concrete function is called, and determined from the runtime value. Dynamic dispatch is an important tool for composing applications. Move makes dynamic dispatch safe by providing built-in protection mechanisms against reentrancy, which can be further refined by user choice.


### Function Type Wrappers

Function types, specifically if they come together with abilities, can be verbose, and if the same type of function type is used many times in the code, repetitive. For this purpose, Move recognizes struct wrappers around function types as a special case. They can be used to effectively create named function types:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Function types, specifically if they come together with abilities, can be verbose, and if the same type of function type is used many times in the code, repetitive. For this purpose, Move recognizes struct wrappers around function types as a special case. They can be used to effectively create named function types:
Function types, specifically if they come together with abilities, can be verbose, and if the same type of function type is used many times in the code, can be repetitive. For this purpose, Move recognizes struct wrappers around function types as a special case. They can be used to effectively create named function types:


A public function is required to build a storable function value because it needs to be guaranteed that the underlying function persists so the function value can be safely restored from storage at any point in the future. However, code upgrade may change the underlying implementation of the function, while its signature is persistent.

If a function should storable which is non-public, an attribute can be used to mark this function as persistent, with the same upgrade behavior than public functions. This avoids exposing such a function outside of a package which can be a security risk:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If a function should storable which is non-public, an attribute can be used to mark this function as persistent, with the same upgrade behavior than public functions. This avoids exposing such a function outside of a package which can be a security risk:
If a function needs to be storable and is non-public, an attribute can be added to mark this function as persistent, with the same upgrade behavior as public functions. This avoids exposing such a function outside of a package which can be a security risk:


### Lambda Expressions and Closures

Function values can be denoted by _lambda expressions_ (as also available as parameters for [inline functions](#function-parameters-and-lambda-expressions)). Lambda expressions can capture context variables _by value_: those values are moved (or copied) into a _closure_, from where they are produced when the function is evaluated. Examples:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed a redundant clause, that comes from the meaning of moved or copied.

Suggested change
Function values can be denoted by _lambda expressions_ (as also available as parameters for [inline functions](#function-parameters-and-lambda-expressions)). Lambda expressions can capture context variables _by value_: those values are moved (or copied) into a _closure_, from where they are produced when the function is evaluated. Examples:
Function values can be denoted by _lambda expressions_ (as also available as parameters for [inline functions](#function-parameters-and-lambda-expressions)). Lambda expressions can capture context variables _by value_: those values are moved (or copied) into a _closure_, when the function is evaluated. Examples:

assert!(add(2) == 3)
```

Notice it is not possible to capture reference values at this point of time in Move. Similarly, it is not possible to mutate any locals in the context of a lambda. Specifically, the following pattern as known from lambdas in inline functions, is not supported:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Notice it is not possible to capture reference values at this point of time in Move. Similarly, it is not possible to mutate any locals in the context of a lambda. Specifically, the following pattern as known from lambdas in inline functions, is not supported:
Notice that it is not possible to capture references at this point of time in Move. Similarly, it is not possible to mutate any locals in the context of a lambda. Specifically, the following pattern as known from lambdas in inline functions, is not supported:

}
```

Notice that dispatching a function value to a concrete function in the same module is also considered to be reentrancy. If the function `callee::call_me` would be moved into the module `caller`, the same semantics is in effect.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Notice that dispatching a function value to a concrete function in the same module is also considered to be reentrancy. If the function `callee::call_me` would be moved into the module `caller`, the same semantics is in effect.
Notice that dispatching a function value to a concrete function in the same module is also considered to be reentrancy. If the function `callee::call_me` would be moved into the module `caller`, the same semantics are in effect.

Copy link
Collaborator

@gregnazario gregnazario left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approve, my suggestions are mostly for readability and some minor things, up to you.


The Move 2.2 language release adds the following features to Move:

- **Acquires Optional**: the `acquires` annotation on function declarations can be omitted, to be inferred by the compiler

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. is missing in the end, also **Optional Acquires**, The with the capital

assert!(f(1) == f(2))
```

Function values support equality and ordering. Note that those relations are based on the name of the underlying function behind a runtime value, and do not reflect semantic equivalence.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: as well as captured arguments for equality, + lexicographical comparison of captured arguments for ordering? it's not just the name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to keep the text until here neutral regards closures, so I added this into the later section.


### Reentrancy Check

Via dynamic dispatch of function values, reentrancy of modules in a chain of function calls is possible. If module `m1` uses module `m2`, and `m1` calls `m2::f` passing a function value to it, this function value can callback into `m1`. This situation is called _reentrancy_, and is not possible in Move without function values, since the module usage relation is acyclic.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a double space in this sentence, probably you can do a search in case there are other occurrences

```move
module account { ... }
module caller {
#[module_lock] // without this lock, the notify call could withdraw unlimited amount

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, so without module lock we can have re-entrancy, can you remind me? If so, this seems a problem because ideally we want module unlock, to not allow re-entrancy by default? Maybe I forgot some context here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the doc with explanation (filter/map/reduce doesn't really work with this).

Copy link
Contributor Author

@wrwg wrwg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing the other stuff @gregnazario

assert!(f(1) == f(2))
```

Function values support equality and ordering. Note that those relations are based on the name of the underlying function behind a runtime value, and do not reflect semantic equivalence.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to keep the text until here neutral regards closures, so I added this into the later section.

```move
module account { ... }
module caller {
#[module_lock] // without this lock, the notify call could withdraw unlimited amount
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the doc with explanation (filter/map/reduce doesn't really work with this).

@wrwg wrwg enabled auto-merge (squash) March 21, 2025 03:29
@wrwg wrwg merged commit 8e5c800 into main Mar 21, 2025
6 checks passed
@wrwg wrwg deleted the wrwg/move-2.2 branch March 21, 2025 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants