-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(docs): add ownership and references sections (#3911)
* feat(docs): add ownership and references Signed-off-by: salaheldinsoliman <[email protected]> * Apply suggestions from code review Co-authored-by: vivekjain23 <[email protected]> * feat(docs): implement vivek's suggestion Signed-off-by: salaheldinsoliman <[email protected]> * Apply suggestions from code review Co-authored-by: Lucas Tortora <[email protected]> * feat(docs): add title and description Signed-off-by: salaheldinsoliman <[email protected]> * Update docs/content/developer/iota-101/move-overview/ownership-scope.mdx Co-authored-by: Lucas Tortora <[email protected]> --------- Signed-off-by: salaheldinsoliman <[email protected]> Co-authored-by: vivekjain23 <[email protected]> Co-authored-by: Lucas Tortora <[email protected]>
- Loading branch information
1 parent
9d050a7
commit 75e54d0
Showing
4 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
143 changes: 143 additions & 0 deletions
143
docs/content/developer/iota-101/move-overview/ownership-scope.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
--- | ||
title: Ownership and Scope | ||
description: Learn about variable ownership and scope in Move, how it works, and how to use it in Move. | ||
--- | ||
|
||
# Ownership and Scope | ||
|
||
:::info | ||
This section covers the concept of variable ownership and scope in Move, not to be confused with [object ownership](../objects/object-ownership/object-ownership.mdx) in the IOTA framework. | ||
::: | ||
|
||
Move's ownership and scope management was inspired by [Rust](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html): Every variable in Move has a scope and an owner. The scope is the range of code where the variable | ||
is valid, and the owner is the scope that this variable belongs to. Once the owner’s scope ends, the | ||
variable is dropped. This is a fundamental concept in Move, and it is important to understand how it | ||
works. | ||
|
||
|
||
## Ownership | ||
|
||
A variable defined in a function scope is owned by this scope. The runtime goes through the function | ||
scope and executes every expression and statement. Once the function scope ends, the variables | ||
defined in it are dropped or deallocated. | ||
|
||
```move | ||
module book::ownership; | ||
public fun owner() { | ||
let a = 1; // a is owned by the `owner` function | ||
} // a is dropped here | ||
public fun other() { | ||
let b = 2; // b is owned by the `other` function | ||
} // b is dropped here | ||
#[test] | ||
fun test_owner() { | ||
owner(); | ||
other(); | ||
// a & b is not valid here | ||
} | ||
``` | ||
|
||
In the example above, the variable `a` is owned by the `owner` function, and the variable `b` is | ||
owned by the `other` function. When each of these functions are called, the variables are defined, | ||
and when the function ends, the variables are discarded. | ||
|
||
## Returning a Value | ||
|
||
If we changed the `owner` function to return the variable `a`, then the ownership of `a` would be | ||
transferred to the caller of the function. | ||
|
||
```move | ||
module book::ownership; | ||
public fun owner(): u8 { | ||
let a = 1; // a defined here | ||
a // scope ends, a is returned | ||
} | ||
#[test] | ||
fun test_owner() { | ||
let a = owner(); | ||
// a is valid here | ||
} // a is dropped here | ||
``` | ||
|
||
## Passing by Value | ||
|
||
Additionally, if we passed the variable `a` to another function, the ownership of `a` would be | ||
transferred to this function. When performing this operation, we _move_ the value from one scope to | ||
another. This is also called _move semantics_. | ||
|
||
```move | ||
module book::ownership; | ||
public fun owner(): u8 { | ||
let a = 10; | ||
a | ||
} // a is returned | ||
public fun take_ownership(v: u8) { | ||
// v is owned by `take_ownership` | ||
} // v is dropped here | ||
#[test] | ||
fun test_owner() { | ||
let a = owner(); | ||
take_ownership(a); | ||
// a is not valid here | ||
} | ||
``` | ||
|
||
## Scopes with Blocks | ||
|
||
Each function has a main scope, and it can also have sub-scopes via the use of blocks. A block is a | ||
sequence of statements and expressions, and it has its own scope. Variables defined in a block are | ||
owned by this block, and when the block ends, the variables are dropped. | ||
|
||
```move | ||
module book::ownership; | ||
public fun owner() { | ||
let a = 1; // a is owned by the `owner` function's scope | ||
{ | ||
let b = 2; // b is owned by the block | ||
{ | ||
let c = 3; // c is owned by the block | ||
}; // c is dropped here | ||
}; // b is dropped here | ||
// a = b; // error: b is not valid here | ||
// a = c; // error: c is not valid here | ||
} // a is dropped here | ||
``` | ||
|
||
However, shall we use the return value of a block, the ownership of the variable is transferred to | ||
the caller of the block. | ||
|
||
```move | ||
module book::ownership; | ||
public fun owner(): u8 { | ||
let a = 1; // a is owned by the `owner` function's scope | ||
let b = { | ||
let c = 2; // c is owned by the block | ||
c // c is returned | ||
}; // c is dropped here | ||
a + b // both a and b are valid here | ||
} | ||
``` | ||
|
||
## Copyable Types | ||
|
||
Some types in Move are _copyable_, which means that they can be copied without transferring the | ||
ownership. This is useful for types that are small and cheap to copy, such as integers and booleans. | ||
Move compiler will automatically copy these types when they are passed to a function or returned | ||
from a function, or when they're _moved_ to a scope and then accessed in their original scope. | ||
|
||
|
||
## Next Steps | ||
|
||
There are scenarios where we want to pass a variable to a function without transferring the ownership. | ||
In this case, we can use references. To complete the understanding of ownership and scope, we recommend reading about: | ||
- [References](./references.mdx) |
77 changes: 77 additions & 0 deletions
77
docs/content/developer/iota-101/move-overview/references.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
--- | ||
title: References | ||
description: References in Move allow passing a value to a function without giving up the ownership. This section covers references, how they work, and how to use them in Move. | ||
--- | ||
|
||
# References | ||
|
||
In the [Ownership and Scope](./ownership-scope.mdx) section, we explained that when a value is | ||
passed to a function, the value's ownership is transferred to the function. This means that the function becomes | ||
the owner of the value, and the original scope (owner) can no longer use it. This is an important | ||
concept in Move, as it ensures that the value is not used in multiple places at the same time. | ||
However, there are use cases when we want to pass a value to a function but retain the ownership. | ||
This is where references come into play. | ||
|
||
To illustrate this, let's consider a simple example - an application for a metro (subway) pass card that can be: | ||
|
||
1. Purchased at the kiosk for a fixed price. | ||
2. Shown to inspectors to prove that the passenger has a valid pass. | ||
3. Used at the turnstile to enter the metro, and spend a ride. | ||
4. Recycled once it's empty. | ||
|
||
## Layout | ||
{/*TODO: Add refs for constants and error constants*/} | ||
The initial layout of the metro pass application is simple. We define the `Card` type and the `USES` | ||
constant that represents the number of rides for a single card. We also add an error constant for the case when the card is empty. | ||
|
||
```move file=<rootDir>/docs/examples/move/move-overview/references.move#L7-L19 | ||
``` | ||
|
||
## Reference | ||
|
||
References are a way to _show_ a value to a function without giving up the ownership. In our case, | ||
when we show the Card to the inspector, we don't want to give up the ownership of it, and we don't | ||
allow them to spend the rides. We just want to allow _reading_ the value of the Card and prove its | ||
ownership. | ||
|
||
To do so, in the function signature, we use the `&` symbol to indicate that we are passing a | ||
reference to the value, not the value itself. | ||
|
||
```move file=<rootDir>/docs/examples/move/move-overview/references.move#L21-L24 | ||
``` | ||
|
||
Now the function can't take the ownership of the card, and it can't spend the rides. But it can read | ||
its value. It's worth noting, that a signature like this makes it impossible to call the function without | ||
a Card at all. This is an important property which allows the | ||
[Capability Pattern](./patterns/capabilities.mdx). | ||
|
||
## Mutable Reference | ||
|
||
In some cases, we want to allow the function to change the value of the Card. For example, when we | ||
use the Card at the turnstile, we want to spend a ride. To implement it, we use the `&mut` keyword | ||
in the function signature. | ||
|
||
```move file=<rootDir>/docs/examples/move/move-overview/references.move#L26-L30 | ||
``` | ||
|
||
As you can see in the function body, the `&mut` reference allows mutating the value, and the | ||
function can spend the rides. | ||
|
||
## Passing by Value | ||
|
||
Lastly, let's give an illustration of what happens when we pass the value itself to the function. In | ||
this case, the function takes the ownership of the value, and the original scope can no longer use | ||
it. The owner of the Card can recycle it, and, hence, lose the ownership. | ||
|
||
```move file=<rootDir>/docs/examples/move/move-overview/references.move#L32-L36 | ||
``` | ||
|
||
In the `recycle` function, the Card is _taken by value_ and can be unpacked and destroyed. The | ||
original scope can't use it anymore. | ||
|
||
## Full Example | ||
|
||
To illustrate the full flow of the application, let's put all the pieces together including some tests. | ||
|
||
```move file=<rootDir>/docs/examples/move/move-overview/references.move | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// Copyright (c) Mysten Labs, Inc. | ||
// Modifications Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
module book::metro_pass { | ||
|
||
/// Error code for when the card is empty. | ||
const ENoUses: u64 = 0; | ||
|
||
/// Number of uses for a metro pass card. | ||
const USES: u8 = 3; | ||
|
||
/// A metro pass card | ||
public struct Card { uses: u8 } | ||
|
||
/// Purchase a metro pass card. | ||
public fun purchase(/* pass a Coin */): Card { | ||
Card { uses: USES } | ||
} | ||
|
||
/// Show the metro pass card to the inspector. | ||
public fun is_valid(card: &Card): bool { | ||
card.uses > 0 | ||
} | ||
|
||
/// Use the metro pass card at the turnstile to enter the metro. | ||
public fun enter_metro(card: &mut Card) { | ||
assert!(card.uses > 0, ENoUses); | ||
card.uses = card.uses - 1; | ||
} | ||
|
||
/// Recycle the metro pass card. | ||
public fun recycle(card: Card) { | ||
assert!(card.uses == 0, ENoUses); | ||
let Card { uses: _ } = card; | ||
} | ||
|
||
#[test] | ||
fun test_card() { | ||
// declaring variable as mutable because we modify it | ||
let mut card = purchase(); | ||
|
||
enter_metro(&mut card); | ||
|
||
assert!(is_valid(&card)); // read the card! | ||
|
||
enter_metro(&mut card); // modify the card but don't move it | ||
enter_metro(&mut card); // modify the card but don't move it | ||
|
||
recycle(card); // move the card out of the scope | ||
} | ||
|
||
#[test] | ||
fun test_card_2024() { | ||
// declaring variable as mutable because we modify it | ||
let mut card = purchase(); | ||
|
||
card.enter_metro(); // modify the card but don't move it | ||
assert!(card.is_valid()); // read the card! | ||
|
||
card.enter_metro(); // modify the card but don't move it | ||
card.enter_metro(); // modify the card but don't move it | ||
|
||
card.recycle(); // move the card out of the scope | ||
} | ||
} |