Skip to content

Commit dcb5aa7

Browse files
committed
[move-book] Function Value Documentation
1 parent 3a17d18 commit dcb5aa7

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

apps/nextra/pages/zh/build/smart-contracts/book/functions.mdx

+153
Original file line numberDiff line numberDiff line change
@@ -732,3 +732,156 @@ The call `s.foo(1)` is syntactic sugar for `foo(&s, 1)`. Notice that the compile
732732
The type of the `self` argument can be a struct or an immutable or mutable reference to a struct. The struct must be declared in the same module as the function.
733733

734734
Notice that you do not need to `use` the modules which introduce receiver functions. The compiler will find those functions automatically based on the argument type of `s` in a call like `s.foo(1)`. This, in combination with the automatic insertion of reference operators, can make code using this syntax significantly more concise.
735+
736+
## Function Values
737+
738+
_Since language version 2.2_ (preview)
739+
740+
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.
741+
742+
### Function Types
743+
744+
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)`.
745+
746+
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)).
747+
748+
Function values can be stored in fields of structs or enums. In this case, the field type inherits the abilities of the struct:
749+
750+
```move
751+
struct S has key {
752+
func: |u64| bool /* has store */ // not needed since inherited
753+
}
754+
```
755+
756+
### Operations on Functions
757+
758+
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:
759+
760+
```move
761+
let f: |u64|bool has copy = ...;
762+
assert!(f(1) == f(2))
763+
```
764+
765+
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.
766+
767+
768+
### Function Type Wrappers
769+
770+
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:
771+
772+
```move
773+
struct Predicate<T>(|T|bool) has copy;
774+
```
775+
776+
Move supports this feature by automatically converting function values into the wrapper type and vice versa. Examples:
777+
778+
```move
779+
fun is_true(op: Predicate<u64>, param: u64) {
780+
// Automatically converts into wrapped function and evaluates
781+
op(param)
782+
}
783+
784+
fun is_true_on_value(f: |u64|bool has copy): bool {
785+
// Automatically converts into function wrapper
786+
is_true(f, 42)
787+
}
788+
```
789+
790+
### Denoting Function Values
791+
792+
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:
793+
794+
```move
795+
public fun is_even(x: u64): bool { x % 2 == 0 }
796+
fun is_odd(x: u64): bool { x % 2 == 1 }
797+
...
798+
let f: |u64|bool has copy+drop+store = is_even;
799+
let g: |u64|bool has copy+drop = is_odd;
800+
```
801+
802+
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.
803+
804+
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:
805+
806+
```move
807+
#[persistent] fun is_odd(x: u64): bool { x % 2 == 1 }
808+
...
809+
let g: |u64|bool has copy+drop+store = is_odd;
810+
```
811+
812+
### Lambda Expressions and Closures
813+
814+
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:
815+
816+
```move
817+
struct S(u64); // cannot be copied or dropped
818+
...
819+
let s = S(1);
820+
let add = |y| { let S(x) = s; x + y }; // s will be moved into the closure
821+
assert!(add(2) == 3)
822+
```
823+
824+
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:
825+
826+
```move
827+
let x = 0;
828+
collection.for_each(|e| x += e) // DOES NOT COMPILE
829+
```
830+
831+
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.
832+
833+
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:
834+
835+
```move
836+
#[persistent] fun add(x: u64, y: u64) { x + y }
837+
...
838+
let x = 22;
839+
let f: |u64|u64 has copy+drop+store = |y| add(x, y)
840+
let f: |u64|u64 has copy+drop+store = |y| add(y, x)
841+
```
842+
843+
For reference, this mechanism is also called 'currying' of `add` in functional programming languages.
844+
845+
### Reentrancy Check
846+
847+
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.
848+
849+
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:
850+
851+
```move
852+
module caller {
853+
use addr::callee;
854+
struct R{ count: u64 } has key;
855+
fun calling() acquires R {
856+
let r = &mut R[@addr];
857+
// This callback is OK, because `R` is not accessed
858+
callee::call_me(r, |x| do_something(x))
859+
// This callback will lead to reentrancy runtime error
860+
callee::call_me(r, |_| R[@addr].count += 1)
861+
r.call_count += 1
862+
}
863+
fun do_something(r: &mut R) { .. }
864+
}
865+
866+
module callee {
867+
fun call_me<T(x: &mut T, action: |&mut T|) {
868+
action(x)
869+
}
870+
}
871+
```
872+
873+
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.
874+
875+
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:
876+
877+
```move
878+
module account { ... }
879+
module caller {
880+
#[module_lock] // without this lock, the notify call could withdraw unlimited amount
881+
fun transfer(from: address, to: address, amount: u64, notify: |u64|) {
882+
account::deposit(to, amount)
883+
notify(amount); // attempt to reentrer `transfer` is blocked
884+
account::withtdraw(from, amout);
885+
}
886+
}
887+
```

apps/nextra/pages/zh/build/smart-contracts/book/global-storage-operators.mdx

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ module 0x42::counter {
119119

120120
## Annotating functions with `acquires`
121121

122+
*Note: Since language version 2.2, acquires annotations are optional. If no acquires is given, it will be inferred.*
123+
122124
In the `counter` example, you might have noticed that the `get_count`, `increment`, `reset`, and `delete` functions are annotated with `acquires Counter`. A Move function `m::f` must be annotated with `acquires T` if and only if:
123125

124126
- The body of `m::f` contains a `move_from<T>`, `borrow_global_mut<T>`, or `borrow_global<T>` instruction, or

apps/nextra/pages/zh/build/smart-contracts/book/move-2.mdx

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
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_".
44

5+
## Move 2.2
6+
7+
The Move 2.2 language release adds the following features to Move:
8+
9+
- **Acquires Optional**: the `acquires` annotation on function declarations can be omitted, to be inferred by the compiler
10+
- **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).
511
## Move 2.1
612

713
The Move 2.1 language release adds the following features to Move:

0 commit comments

Comments
 (0)