Skip to content

Commit

Permalink
Merge pull request #31 from onflow/sainati/remove-destructor
Browse files Browse the repository at this point in the history
remove references to destructors and add docs for default destroy events
  • Loading branch information
dsainati1 committed Dec 6, 2023
2 parents 7ec6063 + d213b68 commit df21a6b
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 88 deletions.
2 changes: 1 addition & 1 deletion versioned_docs/version-1.0/language/interfaces.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ let numbers = Numbers()
numbers.getCount() // is 0
```

Interfaces cannot provide default initializers or default destructors.
Interfaces cannot provide default initializers.

Only one conformance may provide a default function.

Expand Down
120 changes: 61 additions & 59 deletions versioned_docs/version-1.0/language/resources.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -259,69 +259,10 @@ let oldX <- x <- create R()
destroy oldX
```

### Resource Destructors

Resource may have a destructor, which is executed when the resource is destroyed.
Destructors have no parameters and no return value and are declared using the `destroy` name.
A resource may have only one destructor.

```cadence
var destructorCalled = false
access(all) resource Resource {
// Declare a destructor for the resource, which is executed
// when the resource is destroyed.
//
destroy() {
destructorCalled = true
}
}
let res <- create Resource()
destroy res
// `destructorCalled` is `true`
```

### Nested Resources

Fields in composite types behave differently when they have a resource type.

If a resource type has fields that have a resource type,
it **must** declare a destructor,
which **must** invalidate all resource fields, i.e. move or destroy them.

```cadence
access(all) resource Child {
let name: String
init(name: String)
self.name = name
}
}
// Declare a resource with a resource field named `child`.
// The resource *must* declare a destructor
// and the destructor *must* invalidate the resource field.
//
access(all) resource Parent {
let name: String
var child: @Child
init(name: String, child: @Child) {
self.name = name
self.child <- child
}
// Declare a destructor which invalidates the resource field
// `child` by destroying it.
//
destroy() {
destroy self.child
}
}
```

Accessing a field or calling function on a resource field is valid,
however moving a resource out of a variable resource field is **not** allowed.
Instead, use a swap statement to replace the resource with another resource.
Expand All @@ -344,6 +285,66 @@ parent.child <-> otherChild
// `otherChild` is the first child, Child 1.
```

When a resource containing nested resources in fields is destroyed with a `destroy` statement,
all the nested resources are also destroyed.

```cadence
// Declare a resource with resource fields
//
access(all) resource Parent {
var child1: @Child
var child2: @Child
init(child1: @Child, child2: @Child) {
self.child1 <- child1
self.child2 <- child2
}
}
```

The order in which the nested resources are destroyed is deterministic but unspecified,
and cannot be influenced by the developer. E.g., in this example, when `Parent` is destroyed,
the `child1` and `child2` fields are both also destroyed in some unspecified order.

In previous versions of Cadence it was possible to define a special `destroy` function that
would execute arbitrary code when a resource was destroyed, but this is no longer the case.

### Destroy Events

While it is not possible to specify arbitrary code to execute upon the destruction of a resource,
it is possible to specify a special [event](./events.md) to be automatically emitted when a resource is destroyed.
The event has a reserved name: `ResourceDestroyed`, and uses special syntax:

```cadence
resource R {
event ResourceDestroyed(id: UInt64 = self.id)
let id: UInt64
init(_ id: UInt64) {
self.id = id
}
}
```

Whenever a value of type `R` defined this way is destroyed, a special `R.ResourceDestroyed` event will be emitted.
The special syntax used in the definition of the `ResourceDestroyed` specifies what the values associated with each event
parameter will be; in this case the `id` field of the `R.ResourceDestroyed` event will be the value that the `id` field held
immediately before the resource was destroyed. In general, for some `ResourceDestroyed` event defined as:

```cadence
event ResourceDestroyed(field1: T1 = e1, field2: T2 = e2, ...)
```

The value of `field1` on the event will be the result of evaluating `e1` before destroying the resource,
the value of `field2` on the event will be the result of evaluating `e2` before destroying the resource,
and so on. As one might expect, `e1` and `e2` must also expressions of type `T1` and `T2` respectively.

In order to guarantee that these events can be emitted with no chance of failure at runtime, there are restrictions
placed on which kinds of types and expressions can be used in their definitions. In general, an expression
defining the value of a field (the `e` in the general definition above) can only be a member or indexed access on `self`
(or `base` in the case of an [attachment](./attachments.mdx)) or a literal. The types of event fields are restricted to
number types, `String`s, `Boolean`s, `Address`es and `Path`s.

### Resources in Closures

Resources can not be captured in closures, as that could potentially result in duplications.
Expand Down Expand Up @@ -607,3 +608,4 @@ Otherwise the field is `nil`.
The field's value changes when the resource is moved from outside account storage
into account storage, when it is moved from the storage of one account
to the storage of another account, and when it is moved out of account storage.

7 changes: 0 additions & 7 deletions versioned_docs/version-1.0/language/run-time-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,6 @@ access(all) resource SimpleSale {
self.paymentReceiver = paymentReceiver
}
destroy() {
// When this sale resource is destroyed,
// also destroy the resource for sale.
// Another option could be to transfer it back to the seller.
destroy self.resourceForSale
}
/// buyObject allows purchasing the resource for sale by providing
/// the required funds.
/// If the purchase succeeds, the resource for sale is returned.
Expand Down
20 changes: 3 additions & 17 deletions versioned_docs/version-1.0/tutorial/05-non-fungible-tokens-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ This contract expands on the `BasicNFT` we looked at by adding:
2. An `NFTReceiver` interface that exposes three public functions for the collection.
3. Declares a resource called `Collection` that implements the `NFTReceiver` interface
4. The `Collection` will declare fields and functions to interact with it,
including `ownedNFTs`, `init()`, `withdraw()`, `destroy()`, and other important functions
including `ownedNFTs`, `init()`, `withdraw()`, and other important functions
5. Next, the contract declares functions that create a new NFT (`mintNFT()`) and an empty collection (`createEmptyCollection()`)
7. Finally, the contract declares an initializer that initializes the path fields,
creates an empty collection as well as a reference to it,
Expand Down Expand Up @@ -216,10 +216,6 @@ access(all) contract ExampleNFT {
access(all) fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
destroy() {
destroy self.ownedNFTs
}
}
// creates a new empty Collection resource and returns it
Expand Down Expand Up @@ -288,18 +284,8 @@ You wouldn't want it getting lost by accident!
As we learned in the resource tutorial, you can destroy any resource
by explicity invoking the `destroy` command.

If the NFT `Collection` resource is destroyed with the `destroy` command,
it needs to know what to do with the resources it stores in the dictionary.
This is why resources that store other resources have to include
a `destroy` function that runs when `destroy` is called on it.
This destroy function has to either explicitly destroy the contained resources
or move them somewhere else. In this example, we destroy them.

```cadence
destroy() {
destroy self.ownedNFTs
}
```
When the NFT `Collection` resource is destroyed with the `destroy` command,
all the resources stored in the dictionary are also `destroy`ed.

When the `Collection` resource is created, the initializer is run
and must explicitly initialize all member variables.
Expand Down
4 changes: 0 additions & 4 deletions versioned_docs/version-1.0/tutorial/10-resources-compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,6 @@ access(all) contract KittyVerse {
var removed <- self.items.remove(key: key)
return <- removed
}
destroy() {
destroy self.items
}
}
access(all) fun createKitty(): @Kitty {
Expand Down

0 comments on commit df21a6b

Please sign in to comment.