You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: apps/nextra/pages/en/build/smart-contracts/book/functions.mdx
+49-34
Original file line number
Diff line number
Diff line change
@@ -773,10 +773,10 @@ struct S has key {
773
773
774
774
### Operations on Functions
775
775
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:
777
777
778
778
```move
779
-
let f: |u64|bool has copy = ...;
779
+
let f: |u64|bool has copy = |x| x > 0;
780
780
assert!(f(1) == f(2))
781
781
```
782
782
@@ -788,21 +788,14 @@ Function values support equality and ordering. Note that those relations are bas
788
788
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:
789
789
790
790
```move
791
-
struct Predicate<T>(|T|bool) has copy;
791
+
struct Predicate<T>(|&T|bool) has copy;
792
792
```
793
793
794
794
Move supports this feature by automatically converting function values into the wrapper type and vice versa. Examples:
795
795
796
796
```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
806
799
```
807
800
808
801
### Denoting Function Values
@@ -817,16 +810,19 @@ let f: |u64|bool has copy+drop+store = is_even;
817
810
let g: |u64|bool has copy+drop = is_odd;
818
811
```
819
812
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.
821
814
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:
823
816
824
817
```move
825
818
#[persistent] fun is_odd(x: u64): bool { x % 2 == 1 }
826
819
...
827
820
let g: |u64|bool has copy+drop+store = is_odd;
828
821
```
829
822
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
+
830
826
### Lambda Expressions and Closures
831
827
832
828
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
839
835
assert!(add(2) == 3)
840
836
```
841
837
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:
843
858
844
859
```move
845
860
let x = 0;
846
861
collection.for_each(|e| x += e) // DOES NOT COMPILE
847
862
```
848
863
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:
852
865
853
866
```move
854
-
#[persistent] fun add(x: u64, y: u64) { x + y }
855
-
...
856
867
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)
859
869
```
860
870
861
-
For reference, this mechanism is also called 'currying' of `add` in functional programming languages.
862
-
863
871
### Reentrancy Check
864
872
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.
866
874
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:
868
876
869
877
```move
870
-
module caller {
871
-
use addr::callee;
878
+
module 0x42::caller {
879
+
use 0x42::callee;
872
880
struct R{ count: u64 } has key;
873
881
fun calling() acquires R {
874
882
let r = &mut R[@addr];
@@ -881,7 +889,7 @@ module caller {
881
889
fun do_something(r: &mut R) { .. }
882
890
}
883
891
884
-
module callee {
892
+
module 0x42::callee {
885
893
fun call_me<T(x: &mut T, action: |&mut T|) {
886
894
action(x)
887
895
}
@@ -890,16 +898,23 @@ module callee {
890
898
891
899
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.
892
900
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:
894
903
895
904
```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.
899
908
fun transfer(from: address, to: address, amount: u64, notify: |u64|) {
909
+
// Oops. This should be really differently designed, using `Coin` type and moving it.
notify(amount); // attempt to reentrer `transfer` is blocked
912
+
notify(amount); // attempt to re-entrer `transfer` is blocked
902
913
account::withtdraw(from, amout);
903
914
}
904
915
}
905
916
```
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.
Copy file name to clipboardexpand all lines: apps/nextra/pages/en/build/smart-contracts/book/move-2.mdx
+1-1
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ The Move 2 language releases are described on this page. The reference documenta
6
6
7
7
The Move 2.2 language release adds the following features to Move:
8
8
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.
10
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).
0 commit comments