From 5fe8814841ab159bf515fcf800fae539b0a293fb Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 5 Dec 2023 15:02:38 -0500 Subject: [PATCH 1/3] remove references to destructors and add docs for default destroy events --- .../version-1.0/language/interfaces.mdx | 2 +- .../version-1.0/language/resources.mdx | 100 +++++++----------- .../version-1.0/language/run-time-types.md | 7 -- .../tutorial/05-non-fungible-tokens-2.md | 20 +--- .../tutorial/10-resources-compose.md | 4 - 5 files changed, 45 insertions(+), 88 deletions(-) diff --git a/versioned_docs/version-1.0/language/interfaces.mdx b/versioned_docs/version-1.0/language/interfaces.mdx index efacfb6..5f76bbf 100644 --- a/versioned_docs/version-1.0/language/interfaces.mdx +++ b/versioned_docs/version-1.0/language/interfaces.mdx @@ -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. diff --git a/versioned_docs/version-1.0/language/resources.mdx b/versioned_docs/version-1.0/language/resources.mdx index 645f28b..31940a0 100644 --- a/versioned_docs/version-1.0/language/resources.mdx +++ b/versioned_docs/version-1.0/language/resources.mdx @@ -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. @@ -344,6 +285,46 @@ 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. + +### 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. @@ -607,3 +588,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. + diff --git a/versioned_docs/version-1.0/language/run-time-types.md b/versioned_docs/version-1.0/language/run-time-types.md index 5ede0b0..cff249f 100644 --- a/versioned_docs/version-1.0/language/run-time-types.md +++ b/versioned_docs/version-1.0/language/run-time-types.md @@ -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. diff --git a/versioned_docs/version-1.0/tutorial/05-non-fungible-tokens-2.md b/versioned_docs/version-1.0/tutorial/05-non-fungible-tokens-2.md index 138d9f3..1c32735 100644 --- a/versioned_docs/version-1.0/tutorial/05-non-fungible-tokens-2.md +++ b/versioned_docs/version-1.0/tutorial/05-non-fungible-tokens-2.md @@ -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, @@ -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 @@ -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. diff --git a/versioned_docs/version-1.0/tutorial/10-resources-compose.md b/versioned_docs/version-1.0/tutorial/10-resources-compose.md index e2ddcf3..27f9e25 100644 --- a/versioned_docs/version-1.0/tutorial/10-resources-compose.md +++ b/versioned_docs/version-1.0/tutorial/10-resources-compose.md @@ -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 { From a352590841cb5533eabd3ecd1211e19ce4df5223 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 6 Dec 2023 10:47:41 -0500 Subject: [PATCH 2/3] review comments --- .../version-1.0/language/resources.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/versioned_docs/version-1.0/language/resources.mdx b/versioned_docs/version-1.0/language/resources.mdx index 31940a0..b11eefc 100644 --- a/versioned_docs/version-1.0/language/resources.mdx +++ b/versioned_docs/version-1.0/language/resources.mdx @@ -288,6 +288,25 @@ parent.child <-> otherChild When a resource containing nested resources in fields is destroyed with a `destroy` statement, all the nested resources are also destroyed. +// 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, From 643d864e2eba084d6dbfa3e455825018a0a39f71 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 6 Dec 2023 10:47:57 -0500 Subject: [PATCH 3/3] formatting --- versioned_docs/version-1.0/language/resources.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/versioned_docs/version-1.0/language/resources.mdx b/versioned_docs/version-1.0/language/resources.mdx index b11eefc..db22979 100644 --- a/versioned_docs/version-1.0/language/resources.mdx +++ b/versioned_docs/version-1.0/language/resources.mdx @@ -288,6 +288,7 @@ parent.child <-> otherChild 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 {