Skip to content

Commit c36b2da

Browse files
committed
Addressing reviewer comments
1 parent 4adfb2e commit c36b2da

File tree

2 files changed

+50
-35
lines changed

2 files changed

+50
-35
lines changed

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

+49-34
Original file line numberDiff line numberDiff line change
@@ -773,10 +773,10 @@ struct S has key {
773773

774774
### Operations on Functions
775775

776-
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:
776+
A function value is evaluated by providing the corresponding number of parameters, similar as when calling a named function. During evaluation, the function value is *consumed*. Hence if the value needs to be evaluated multiple times, it's type must have the `copy` ability:
777777

778778
```move
779-
let f: |u64|bool has copy = ...;
779+
let f: |u64|bool has copy = |x| x > 0;
780780
assert!(f(1) == f(2))
781781
```
782782

@@ -788,21 +788,14 @@ Function values support equality and ordering. Note that those relations are bas
788788
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:
789789

790790
```move
791-
struct Predicate<T>(|T|bool) has copy;
791+
struct Predicate<T>(|&T|bool) has copy;
792792
```
793793

794794
Move supports this feature by automatically converting function values into the wrapper type and vice versa. Examples:
795795

796796
```move
797-
fun is_true(op: Predicate<u64>, param: u64) {
798-
// Automatically converts into wrapped function and evaluates
799-
op(param)
800-
}
801-
802-
fun is_true_on_value(f: |u64|bool has copy): bool {
803-
// Automatically converts into function wrapper
804-
is_true(f, 42)
805-
}
797+
let f: Predicate<u64> = |x| *x > 0; // lambda converts to Predicate
798+
assert!(f(&22)) // Predicate callable
806799
```
807800

808801
### Denoting Function Values
@@ -817,16 +810,19 @@ let f: |u64|bool has copy+drop+store = is_even;
817810
let g: |u64|bool has copy+drop = is_odd;
818811
```
819812

820-
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.
813+
A _persistent_ function is required to build a storable function value because it needs to be guaranteed that the underlying function exists and 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.
821814

822-
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:
815+
While `public` and `entry` functions are persistent by default, a none-public function needs to be marked with the attribute `#[persistent]` to become storable:
823816

824817
```move
825818
#[persistent] fun is_odd(x: u64): bool { x % 2 == 1 }
826819
...
827820
let g: |u64|bool has copy+drop+store = is_odd;
828821
```
829822

823+
Using the `#[persistent]` attribute is preferred if the only objective is to make a function storable, avoiding security implications with public or entry visibility.
824+
825+
830826
### Lambda Expressions and Closures
831827

832828
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:
@@ -839,36 +835,48 @@ let add = |y| { let S(x) = s; x + y }; // s will be moved into the closure
839835
assert!(add(2) == 3)
840836
```
841837

842-
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:
838+
Closures with captured values are lexicographical ordered using first the name of the underlying function (which maybe generated from lambda lifting), and then the captured values.
839+
840+
The type of the closure constructed by a lambda expression is inferred from the expression (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. However, there is a special case for lambdas where instead of a private function an underlying persistent function can be identified, such that the lambda just 'delays' certain arguments of this function. This pattern is also called 'currying' in functional programming (named after the mathematician Curry). Here are some examples:
841+
842+
```move
843+
#[persistent] fun add(x: u64, y: u64) { x + y }
844+
...
845+
let x = 22;
846+
let f: |u64|u64 has copy+drop+store = |y| add(x, y); // 1st argument captured, 2nd argument delayed
847+
let f: |u64|u64 has copy+drop+store = |y| add(y, x); // 1st argument delayed, 2nd argument captured
848+
```
849+
850+
Notice it is not possible to _capture_ reference values at this point of time in Move. Thus, the following code does not compile:
851+
852+
```move
853+
let x = &22;
854+
let f = |y| add(*x, y) // DOES NOT COMPILE
855+
```
856+
857+
Related, it is not possible to mutate any locals in the context of a lambda. Specifically, the following pattern as known from lambdas with inline functions, is not supported:
843858

844859
```move
845860
let x = 0;
846861
collection.for_each(|e| x += e) // DOES NOT COMPILE
847862
```
848863

849-
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.
850-
851-
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:
864+
However, the actual parameters of lambdas can be references, only captured values are restricted. For example:
852865

853866
```move
854-
#[persistent] fun add(x: u64, y: u64) { x + y }
855-
...
856867
let x = 22;
857-
let f: |u64|u64 has copy+drop+store = |y| add(x, y)
858-
let f: |u64|u64 has copy+drop+store = |y| add(y, x)
868+
let f : |&u64|u64 = |y| add(x, *y)
859869
```
860870

861-
For reference, this mechanism is also called 'currying' of `add` in functional programming languages.
862-
863871
### Reentrancy Check
864872

865-
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.
873+
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.
866874

867-
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:
875+
The Move VM dynamically detects reentrancy of a module, and _locks_ all resources declared in this module from being accessed. Thus during reentrancy of `m`, calling resource operations like `&m::R[addr]`, `&mut m::R[addr]`, and `move_from<m::R>` lead to an abort. Here is an example:
868876

869877
```move
870-
module caller {
871-
use addr::callee;
878+
module 0x42::caller {
879+
use 0x42::callee;
872880
struct R{ count: u64 } has key;
873881
fun calling() acquires R {
874882
let r = &mut R[@addr];
@@ -881,7 +889,7 @@ module caller {
881889
fun do_something(r: &mut R) { .. }
882890
}
883891
884-
module callee {
892+
module 0x42::callee {
885893
fun call_me<T(x: &mut T, action: |&mut T|) {
886894
action(x)
887895
}
@@ -890,16 +898,23 @@ module callee {
890898

891899
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.
892900

893-
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:
901+
The default reentrancy check ensures consistency of Move's reference semantics and suppresses side effects of reentrancy for the resources owned by the re-entered module. However, re-entered code is allowed to still access resource state managed by modules outside the reentrancy path. Such state accesses can be considered bad design, but they exist.
902+
For these purposes, the `#[module_lock]` attribute can be attached to a function:
894903

895904
```move
896-
module account { ... }
897-
module caller {
898-
#[module_lock] // without this lock, the notify call could withdraw unlimited amount
905+
module 0x42::account { ... }
906+
module 0x42::caller {
907+
#[module_lock] // without this lock, the notify call could withdraw more than intended.
899908
fun transfer(from: address, to: address, amount: u64, notify: |u64|) {
909+
// Oops. This should be really differently designed, using `Coin` type and moving it.
910+
assert!(account::balance(from) - MIN_BALANCE >= amount);
900911
account::deposit(to, amount)
901-
notify(amount); // attempt to reentrer `transfer` is blocked
912+
notify(amount); // attempt to re-entrer `transfer` is blocked
902913
account::withtdraw(from, amout);
903914
}
904915
}
905916
```
917+
918+
While a function with this attribute is running, all calls reentering any module will lead to an abort, given a stronger protection.
919+
920+
The attribute `#[module_lock]` restriction is not the default behavior since it is too strong for typical patterns of higher-order programming. For example, `collection.find(|x| cond(x))` will lead to a reentrancy of the module which contains this expression, from the module which defines the collection type.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The Move 2 language releases are described on this page. The reference documenta
66

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

9-
- **Acquires Optional**: the `acquires` annotation on function declarations can be omitted, to be inferred by the compiler
9+
- **Optional Acquires**: The `acquires` annotation on function declarations can be omitted, to be inferred by the compiler.
1010
- **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).
1111
## Move 2.1
1212

0 commit comments

Comments
 (0)