Skip to content

Commit

Permalink
feat(docs): add ownership and references sections (#3911)
Browse files Browse the repository at this point in the history
* 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
3 people authored Nov 13, 2024
1 parent 9d050a7 commit 75e54d0
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 0 deletions.
143 changes: 143 additions & 0 deletions docs/content/developer/iota-101/move-overview/ownership-scope.mdx
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 docs/content/developer/iota-101/move-overview/references.mdx
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
```
2 changes: 2 additions & 0 deletions docs/content/sidebars/developer.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const developer = [
'developer/iota-101/move-overview/package-upgrades/custom-policies',
],
},
'developer/iota-101/move-overview/ownership-scope',
'developer/iota-101/move-overview/references',
'developer/iota-101/move-overview/generics',
{
type: 'category',
Expand Down
66 changes: 66 additions & 0 deletions docs/examples/move/move-overview/references.move
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
}
}

0 comments on commit 75e54d0

Please sign in to comment.