Skip to content

Commit

Permalink
[docs] Fix "programming with objects" (MystenLabs#3767)
Browse files Browse the repository at this point in the history
* [docs] Fix "programming with objects"

- Fixed after UID to ID renaming
- Fixed references to ChildRef (as it was removed)
- Small other consistency changes
  • Loading branch information
tnowacki authored Aug 11, 2022
1 parent 934b255 commit 1424010
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 136 deletions.
24 changes: 12 additions & 12 deletions doc/src/build/programming-with-objects/ch1-object-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ struct Color {
}
```
The above `struct` defines a data structure that can represent RGB color. `struct`s like this can be used to organize data with complicated semantics. However, instances of `struct`s like `Color` are not Sui objects yet.
To define a struct that represents a Sui object type, we must add a `key` capability to the definition, and the first field of the struct must be the `id` of the object with type `Info` from the [object library](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move):
To define a struct that represents a Sui object type, we must add a `key` capability to the definition, and the first field of the struct must be the `id` of the object with type `UID` from the [object module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move):
```rust
use sui::object::Info;
use sui::object::UID;

struct ColorObject has key {
info: Info,
id: UID,
red: u8,
green: u8,
blue: u8,
Expand All @@ -26,22 +26,22 @@ struct ColorObject has key {
Now `ColorObject` represents a Sui object type and can be used to create Sui objects that can be eventually stored on the Sui chain.
> :books: In both core Move and Sui Move, the [key ability](https://github.com/move-language/move/blob/main/language/documentation/book/src/abilities.md#key) denotes a type that can appear as a key in global storage. However, the structure of global storage is a bit different: core Move uses a (type, `address`)-indexed map, whereas Sui Move uses a map keyed by object IDs.
> :bulb: The `Info` type is internal to Sui, and you most likely won't need to deal with it directly. For curious readers, it contains the unique `ID` of the object and the version of the object. Each time a mutable object is used in a transaction, its version will increase by 1.
> :bulb: The `UID` type is internal to Sui, and you most likely won't need to deal with it directly. For curious readers, it contains the "unique ID" that defines an object. It is unique in the sense that no two values of type `UID` will ever have the same underlying set of bytes.
### Create Sui object
Now that we have learned how to define a Sui object type, how do we create/instantiate a Sui object? In order to create a new Sui object from its type, we must assign an initial value to each of the fields, including `info`. The only way to create a new unique `Info` for a Sui object is to call `object::new`. The `new` function takes the current transaction context as an argument to generate unique IDs. The transaction context is of type `&mut TxContext` and should be passed down from an [entry function](../move/index.md#entry-functions) (a function that can be called directly from a transaction). Let's look at how we may define a constructor for `ColorObject`:
Now that we have learned how to define a Sui object type, how do we create/instantiate a Sui object? In order to create a new Sui object from its type, we must assign an initial value to each of the fields, including `id`. The only way to create a new `UID` for a Sui object is to call `object::new`. The `new` function takes the current transaction context as an argument to generate unique `ID`s. The transaction context is of type `&mut TxContext` and should be passed down from an [entry function](../move/index.md#entry-functions) (a function that can be called directly from a transaction). Let's look at how we may define a constructor for `ColorObject`:
```rust
// object represents the object module, which allows us call
// object creates an alias to the object module, which allows us call
// functions in the module, such as the `new` function, without fully
// qualifying, e.g. `sui::object::new`.
use sui::object;
// tx_context::TxContext represents the TxContext struct in tx_context module.
// tx_context::TxContext creates an alias to the the TxContext struct in tx_context module.
use sui::tx_context::TxContext;


fun new(red: u8, green: u8, blue: u8, ctx: &mut TxContext): ColorObject {
ColorObject {
info: object::new(ctx),
id: object::new(ctx),
red,
green,
blue,
Expand All @@ -53,7 +53,7 @@ fun new(red: u8, green: u8, blue: u8, ctx: &mut TxContext): ColorObject {
### Store Sui object
We have defined a constructor for the `ColorObject`. Calling this constructor will put the value in a local variable where it can be returned from the current function, passed to other functions, or stored inside another struct. And of course, the object can be placed in persistent global storage so it can be read by the outside world and accessed in subsequent transactions.

All of the APIs for adding objects to persistent storage live in the [`Transfer`](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/transfer.move) module. One key API is:
All of the APIs for adding objects to persistent storage live in the [`transfer`](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/transfer.move) module. One key API is:
```rust
public fun transfer<T: key>(obj: T, recipient: address)
```
Expand Down Expand Up @@ -111,19 +111,19 @@ let scenario = &mut test_scenario::begin(&owner);
color_object::create(255, 0, 255, ctx);
};
```
>:books: Note there is a "`;`" after "`}`". This is required except for the last statement in the function. Refer to the [Move book](https://move-book.com/syntax-basics/expression-and-scope.html) for a detailed explanation.
>:books: Note there is a "`;`" after "`}`". `;` is required to sequence a series of expressions, and even the block `{ ... }` is an expression! Refer to the [Move book](https://move-book.com/syntax-basics/expression-and-scope.html) for a detailed explanation.
Now account `@0x1` should own the object. Let's first make sure it's not owned by anyone else:
```rust
let not_owner = @0x2;
// Check that @not_owner does not own the just-created ColorObject.
// Check that not_owner does not own the just-created ColorObject.
test_scenario::next_tx(scenario, &not_owner);
{
assert!(!test_scenario::can_take_owned<ColorObject>(scenario), 0);
};
```

`test_scenario::next_tx` switches the transaction sender to `@0x2`, which is a new address than the previous one.
`test_scenario::next_tx` switches the transaction sender to `@0x2`, which is a new address different from the previous one.
`test_scenario::can_take_owned` checks whether an object with the given type actually exists in the global storage owned by the current sender of the transaction. In this code, we assert that we should not be able to remove such an object, because `@0x2` does not own any object.
> :bulb: The second parameter of `assert!` is the error code. In non-test code, we usually define a list of dedicated error code constants for each type of error that could happen in production. For unit tests though, it's usually unnecessary because there will be way too many assetions and the stacktrace upon error is sufficient to tell where the error happened. Hence we recommend just putting `0` there in unit tests for assertions.
Expand Down
18 changes: 9 additions & 9 deletions doc/src/build/programming-with-objects/ch2-using-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ There are two ways to pass objects by reference: read-only references (`&T`) and
The `ColorObject` we defined in the previous chapter looks like:
```rust
struct ColorObject has key {
info: Info,
id: UID,
red: u8,
green: u8,
blue: u8,
Expand All @@ -34,7 +34,7 @@ In the above function signature, `from_object` can be a read-only reference beca
> :bulb: Although `from_object` is a read-only reference in this transaction, it is still a mutable object in Sui storage--another transaction could be sent to mutate the object at the same time! To prevent this, Sui must lock any mutable object used as a transaction input, even when it's passed as a read-only reference. In addition, only an object's owner can send a transaction that locks the object.
Let's write a unit test to see how we could interact with multiple objects of the same type in tests.
In the previous chapter, we introduced the `take_owned<T>` API, which takes an object of type `T` from the global storage created by previous transactions. However, what if there are multiple objects of the same type? `take_owned<T>` will no longer be able to tell which one to return. To solve this problem, we need to use two new APIs. The first is `tx_context::last_created_object_id(ctx)`, which returns the ID of the most recently created object. The second is `test_scenario::take_owned_by_id<T>`, which returns an object of type `T` with a specific object ID.
In the previous chapter, we introduced the `take_owned<T>` API, which takes an object of type `T` from the global storage created by previous transactions. However, what if there are multiple objects of the same type? `take_owned<T>` will no longer be able to tell which one to return. To solve this problem, we need to use two new, test-only APIs. The first is `tx_context::last_created_object_id(ctx)`, which returns the ID of the most recently created object. The second is `test_scenario::take_owned_by_id<T>`, which returns an object of type `T` with a specific object ID.
Now let's take a look at the test (`test_copy_into`):
```rust
let owner = @0x1;
Expand Down Expand Up @@ -76,27 +76,27 @@ test_scenario::next_tx(scenario, &owner);
```

### Pass objects by value
Objects can also be passed by value into an entry function. By doing so, the object is moved out of Sui storage (a.k.a. deleted). It is then up to the Move code to decide where this object should go.
Objects can also be passed by value into an entry function. By doing so, the object is moved out of Sui storage. It is then up to the Move code to decide where this object should go.

> :books: Since every [Sui object struct type](./ch1-object-basics.md#define-sui-object) must include `Info` as a field, and the [Info struct](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move) does not have the `drop` ability, the Sui object struct type [must not](https://github.com/move-language/move/blob/main/language/documentation/book/src/abilities.md#drop) have `drop` ability either. Hence, any Sui object cannot be arbitrarily dropped and must be either consumed (e.g., transferred to another owner) or deleted by [unpacking](https://move-book.com/advanced-topics/struct.html#destructing-structures), as described below.
> :books: Since every [Sui object struct type](./ch1-object-basics.md#define-sui-object) must include `UID` as its first field, and the [UID struct](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move) does not have the `drop` ability, the Sui object struct type [cannot](https://github.com/move-language/move/blob/main/language/documentation/book/src/abilities.md#drop) have the `drop` ability either. Hence, any Sui object cannot be arbitrarily dropped and must be either consumed (e.g., transferred to another owner) or deleted by [unpacking](https://move-book.com/advanced-topics/struct.html#destructing-structures), as described below.
There are two ways we can deal with a pass-by-value Sui object in Move:

#### Option 1. Delete the object
If the intention is to actually delete the object, we can unpack the object. This can be done only in the module that defined the struct type, due to Move's [privileged struct operations rules](https://github.com/move-language/move/blob/main/language/documentation/book/src/structs-and-resources.md#privileged-struct-operations). Upon unpacking, if any field is also of struct type, recursive unpacking and deletion will be required.

However, the `info` field of a Sui object requires special handling. We must call the following API in the [object](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move) module to signal Sui that we intend to delete this object:
However, the `id` field of a Sui object requires special handling. We must call the following API in the [object](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move) module to signal Sui that we intend to delete this object:
```rust
public fun delete(info: Info);
public fun delete(id: UID) { ... }
```
Let's define a function in the `ColorObject` module that allows us to delete the object:
```rust
public entry fun delete(object: ColorObject) {
let ColorObject { info, red: _, green: _, blue: _ } = object;
object::delete(info);
let ColorObject { id, red: _, green: _, blue: _ } = object;
object::delete(id);
}
```
As we can see, the object is unpacked, generating individual fields. The u8 values are primitive types and can all be dropped. However the `info` cannot be dropped and must be explicitly deleted through the `object::delete` API. At the end of this call, the object will no longer be stored on-chain.
As we can see, the object is unpacked, generating individual fields. The u8 values are primitive types and can all be dropped. However the `id` (which has type `UID`) cannot be dropped and must be explicitly deleted through the `object::delete` API. At the end of this call, the object will no longer be stored on-chain.

We can add a unit test for it, as well:
```rust
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ Objects in Sui can have different types of [ownership](../objects.md#object-owne

### Create immutable object

Regardless of whether an object was just created or already owned by an account, to turn this object into an immutable object, we need to call the following API in the [Transfer Library](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/transfer.move):
Regardless of whether an object was just created or already owned by an account, to turn this object into an immutable object, we need to call the following API in the [transfer module](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/transfer.move):
```rust
public native fun freeze_object<T: key>(obj: T);
```
After this call, the specified object will become permanently immutable. This is a non-reversible operation; hence, freeze an object only when you are certain that it will never need to be mutated.

Let's add an entry function to the [ColorObject](https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/objects_tutorial/sources/color_object.move) module to turn an existing (owned) `ColorObject` into an immutable object:
Let's add an entry function to the [color_object module](https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/objects_tutorial/sources/color_object.move) to turn an existing (owned) `ColorObject` into an immutable object:
```rust
public entry fun freeze_object(object: ColorObject) {
transfer::freeze_object(object)
Expand All @@ -34,7 +34,7 @@ In this function, a fresh new `ColorObject` is created and immediately turned in

### Use immutable object
Once an object becomes immutable, the rules of who could use this object in Move calls change:
1. An immutable object can be passed only as a read-only reference to Move entry functions as `&T`.
1. An immutable object can be passed only as a read-only, immutable reference to Move entry functions as `&T`.
2. Anyone can use immutable objects.

Recall that we defined a function that copies the value of one object to another:
Expand Down
Loading

0 comments on commit 1424010

Please sign in to comment.