-
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
module account { ... } | ||
module caller { |
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.
So syntax highlighting works correctly, and it's valid syntax, it needs an address. I've been using 0x42 in examples
module account { ... } | |
module caller { | |
module 0x42::account { ... } | |
module 0x42::caller { |
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 { |
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.
Similar with the addresses here
} | ||
|
||
module callee { | ||
fun call_me<T(x: &mut T, action: |&mut T|) { |
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.
Missing a closing >
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; |
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.
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. |
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.
Some simplification
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: |
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.
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: |
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.
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: |
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.
Removed a redundant clause, that comes from the meaning of moved or copied.
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: |
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.
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. |
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.
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. |
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 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 |
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.
. 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. |
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.
|
||
### 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. |
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.
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 |
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.
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 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).
d6b023c
to
4adfb2e
Compare
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.
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. |
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.
```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 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).
No description provided.