diff --git a/docs/content/developer/iota-101/move-overview/ownership-scope.mdx b/docs/content/developer/iota-101/move-overview/ownership-scope.mdx new file mode 100644 index 00000000000..c2ee2658722 --- /dev/null +++ b/docs/content/developer/iota-101/move-overview/ownership-scope.mdx @@ -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) \ No newline at end of file diff --git a/docs/content/developer/iota-101/move-overview/references.mdx b/docs/content/developer/iota-101/move-overview/references.mdx new file mode 100644 index 00000000000..ce59e61612a --- /dev/null +++ b/docs/content/developer/iota-101/move-overview/references.mdx @@ -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=/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=/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=/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=/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=/docs/examples/move/move-overview/references.move +``` \ No newline at end of file diff --git a/docs/content/sidebars/developer.js b/docs/content/sidebars/developer.js index c89180128ac..9d714144131 100644 --- a/docs/content/sidebars/developer.js +++ b/docs/content/sidebars/developer.js @@ -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', diff --git a/docs/examples/move/move-overview/references.move b/docs/examples/move/move-overview/references.move new file mode 100644 index 00000000000..afa9eb66b2c --- /dev/null +++ b/docs/examples/move/move-overview/references.move @@ -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 + } +}