-
Notifications
You must be signed in to change notification settings - Fork 133
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -232,10 +232,12 @@ script { | |
} | ||
``` | ||
|
||
Entry functions can accept parameters that are: primitive types, reference to a `signer`, vectors (where the element type is itself acceptable), | ||
Entry functions can accept parameters that are: primitive types, reference to a | ||
`signer`, vectors (where the element type is itself acceptable), | ||
and certain standard library types such as `String`, `Object`, and `Option`. | ||
Entry functions must not have any return values. | ||
|
||
|
||
### Name | ||
|
||
Function names can start with letters `a` to `z` or letters `A` to `Z`. After the first character, function names can contain underscores `_`, letters `a` to `z`, letters `A` to `Z`, or digits `0` to `9`. | ||
|
@@ -736,7 +738,7 @@ Notice that you do not need to `use` the modules which introduce receiver functi | |
|
||
The receiver style syntax can also be used on generic functions, like shown below for the generic function `std::vector::remove<T>(self: &mut vector<T>, i: u64): T`. | ||
|
||
```move | ||
```move | ||
module 0x42::example { | ||
fun bar() { | ||
let v = vector[1, 2, 3]; | ||
|
@@ -747,3 +749,157 @@ module 0x42::example { | |
} | ||
} | ||
``` | ||
|
||
|
||
## Function Values | ||
|
||
_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. | ||
|
||
### Function Types | ||
|
||
The type of functions values is already known from [inline functions](#function-parameters-and-lambda-expressions). A function type is denoted, for example, as `|u64|bool`, indicating a function which takes a number and returns a boolean. Lists of types are separated by comma, as in `|u64, bool|(bool,u4)`. | ||
|
||
Function types can have associated abilities, written as `|u64|bool has copy`. Multiple abilities are separated by plus, as in `|u64|bool has copy+drop`. If no abilities are provided, the value can be only moved around and evaluated (for evaluation of function values, see [below](#function-evaluation)). | ||
|
||
Function values can be stored in fields of structs or enums. In this case, the field type inherits the abilities of the struct: | ||
|
||
```move | ||
struct S has key { | ||
func: |u64| bool /* has store */ // not needed since inherited | ||
} | ||
``` | ||
|
||
### Operations on Functions | ||
|
||
A function is evaluated by providing the corresponding number of parameters, similar as when calling a named function. When a function is evaluated, the function value is *consumed*. Hence if the value needs to be evaluated multiple times, it's type must have the `copy` ability: | ||
|
||
```move | ||
let f: |u64|bool has copy = ...; | ||
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. | ||
|
||
|
||
### 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: | ||
|
||
```move | ||
struct Predicate<T>(|T|bool) has copy; | ||
``` | ||
|
||
Move supports this feature by automatically converting function values into the wrapper type and vice versa. Examples: | ||
|
||
```move | ||
fun is_true(op: Predicate<u64>, param: u64) { | ||
// Automatically converts into wrapped function and evaluates | ||
op(param) | ||
} | ||
|
||
fun is_true_on_value(f: |u64|bool has copy): bool { | ||
// Automatically converts into function wrapper | ||
is_true(f, 42) | ||
} | ||
``` | ||
|
||
### Denoting Function Values | ||
|
||
Function values can be constructed by directly using a function name. The resulting function type is derived from the signature of the underlying function, with abilities `copy+drop`. If the function is public, those function values have the `store` ability as well: | ||
|
||
```move | ||
public fun is_even(x: u64): bool { x % 2 == 0 } | ||
fun is_odd(x: u64): bool { x % 2 == 1 } | ||
... | ||
let f: |u64|bool has copy+drop+store = is_even; | ||
let g: |u64|bool has copy+drop = is_odd; | ||
``` | ||
|
||
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: | ||
wrwg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```move | ||
#[persistent] fun is_odd(x: u64): bool { x % 2 == 1 } | ||
... | ||
let g: |u64|bool has copy+drop+store = is_odd; | ||
``` | ||
|
||
### 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: | ||
|
||
```move | ||
struct S(u64); // cannot be copied or dropped | ||
... | ||
let s = S(1); | ||
let add = |y| { let S(x) = s; x + y }; // s will be moved into the closure | ||
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: | ||
wrwg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```move | ||
let x = 0; | ||
collection.for_each(|e| x += e) // DOES NOT COMPILE | ||
``` | ||
|
||
The type of the closure constructed by a lambda expression is inferred from the expression in the usual way (for example, the type of `add` in the example above is inferred as `|u64|u64`). The abilities of this function type are derived as follows. By default, the function underlying a closure is a private function, so the function itself is `copy+drop` (and not `store`). This is intersected with the abilities of all the captured context variables. | ||
|
||
A closure can have the `store` ability under the condition that the underlying function is persistent. This is the case if the lambda denoting the closure is based on solely delaying some of the parameters of an underlying persistent function. Example: | ||
|
||
```move | ||
#[persistent] fun add(x: u64, y: u64) { x + y } | ||
... | ||
let x = 22; | ||
let f: |u64|u64 has copy+drop+store = |y| add(x, y) | ||
let f: |u64|u64 has copy+drop+store = |y| add(y, x) | ||
``` | ||
|
||
For reference, this mechanism is also called 'currying' of `add` in functional programming languages. | ||
|
||
### 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. | ||
|
||
The Move VM dynamically detects reentrancy for a module, and _locks_ all resources declared in this module from being accessed. Thus during reentrancy, calling the functions `borrow_global`, `borrow_global_mut`, and `move_from` lead to an abort. Here is an example: | ||
|
||
```move | ||
module caller { | ||
use addr::callee; | ||
struct R{ count: u64 } has key; | ||
fun calling() acquires R { | ||
let r = &mut R[@addr]; | ||
wrwg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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 { | ||
fun call_me<T(x: &mut T, action: |&mut T|) { | ||
action(x) | ||
} | ||
} | ||
``` | ||
|
||
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. | ||
|
||
The default reentrancy check ensures consistency of Move's reference semantics and suppresses side-effects for the resources in a reentered module. But it does not protect from side-effects on other state of the app. For this purposes, the `#[module_lock]` attribute can be attached to a function. In a calling context where this attribute is active, reentrancy will be fully suppressed, causing an abort at the moment a module is reentered. In the example below, the state effected is owned by another module `account` instead of the module `caller` for which the can potentially happen via the `notify` callback: | ||
|
||
```move | ||
module account { ... } | ||
module caller { | ||
#[module_lock] // without this lock, the notify call could withdraw unlimited amount | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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... There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). |
||
fun transfer(from: address, to: address, amount: u64, notify: |u64|) { | ||
account::deposit(to, amount) | ||
notify(amount); // attempt to reentrer `transfer` is blocked | ||
account::withtdraw(from, amout); | ||
} | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,12 @@ | |
|
||
The Move 2 language releases are described on this page. The reference documentation of the new features is integrated into the book, and marked in the text with "_Since language version 2.n_". | ||
|
||
## Move 2.2 | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. . is missing in the end, also |
||
- **Function Values**: Move now supports function values, which can be passed around as parameters and stored in resources. See the [reference doc here](functions.mdx#function-values). | ||
## Move 2.1 | ||
|
||
The Move 2.1 language release adds the following features to Move: | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.