Previous << Composite Types
Next >> Access control
リソース(Resource)は、同時に1箇所の場所にしか存在できない型です。
リソースは、create
キーワードを使用して作成する必要があります。(インスタンス化します)
関数の終了時には、リソース(変数、定数、パラメータ)がその関数のスコープ内に存在する場合、必ずリソースはストレージに移動もしくは破棄する必要があります。
変数に初期値として渡す場合や異なる変数に割り当てる場合、関数に引数として渡す場合、関数の戻り値とする場合に、リソースは移動します。
リソースは、destroy
キーワードを使用して明示的に破棄することができます。
リソースが移動されると、移動前にリソースを参照していた定数または変数は無値(無効)になります。無値(無効な)リソースは、使用することができません。
リソース型の使用法と動作を明確にするために、変数または定数の宣言、パラメータ、および戻り値の型のアノテーションで、接頭辞 @
を付ける必要があります。
The Move Operator
リソースの移動を明示的に行うには、移動演算子<-
を使用する必要があります。リソースが変数の初期値である場合、異なる変数に移動される場合、引数として関数に移動される場合、関数から返される場合です。
/* Declare a resource named `SomeResource`, with a variable integer field. */
access(all)
resource SomeResource {
access(all)
var value: Int
init(value: Int) {
self.value = value
}
}
/* Declare a constant with value of resource type `SomeResource`. */
let a: @SomeResource <- create SomeResource(value: 0)
/* *Move* the resource value to a new constant. */
let b <- a
/* Invalid: Cannot use constant `a` anymore as the resource that it referred to
* was moved to constant `b`.
*/
a.value
/* Constant `b` owns the resource. */
b.value // equals 0
/* Declare a function which accepts a resource.
*
* The parameter has a resource type, so the type annotation must be prefixed with `@`.
*/
access(all)
fun use(resource: @SomeResource) {
/* ... */
}
/* Call function `use` and move the resource into it. */
use(resource: <-b)
/* Invalid: Cannot use constant `b` anymore as the resource
* it referred to was moved into function `use`.
*/
b.value
リソースオブジェクトがスコープ外に出て動的に失われることはありません。Cadenceのプログラムは明示的にそれを破棄するか、別のコンテキストに移動する必要があります。
panic("something went wrong: ...")
assert
{
/* Declare another, unrelated value of resource type `SomeResource`. */
let c <- create SomeResource(value: 10)
/* Invalid: `c` is not used before the end of the scope, but must be.
It cannot be lost. */
}
/* Declare another, unrelated value of resource type `SomeResource`. */
let d <- create SomeResource(value: 20)
/* Destroy the resource referred to by constant `d`. */
destroy d
/* Invalid: Cannot use constant `d` anymore as the resource
* it referred to was destroyed.
*/
d.value
型がリソース型であり、リソースに関連するルールに従わなければならないことを明確にするために、すべての型アノテーションに接頭辞@を付ける必要があります。例えば、変数宣言、パラメータ、または関数の戻り値の型にです。
/* Declare a constant with an explicit type annotation.
*
* The constant has a resource type, so the type annotation must be prefixed with `@`.
*/
let someResource: @SomeResource <- create SomeResource(value: 5)
/* Declare a function which consumes a resource and destroys it.
*
* The parameter has a resource type, so the type annotation must be prefixed with `@`.
*/
access(all)
fun use(resource: @SomeResource) {
destroy resource
}
/* Declare a function which returns a resource.
*
* The return type is a resource type, so the type annotation must be prefixed with `@`.
* The return statement must also use the `<-` operator to make it explicit the resource is moved.
*/
access(all)
fun get(): @SomeResource {
let newResource <- create SomeResource()
return <-newResource
}
リソース必ず一度だけ使用しなければなりません。(関数の引数に2回渡せません)
/* Declare a function which consumes a resource but does not use it.
* This function is invalid, because it would cause a loss of the resource.
*/
access(all)
fun forgetToUse(resource: @SomeResource) {
/* Invalid: The resource parameter `resource` is not used, but must be. */
}
/* Declare a constant named `res` which has the resource type `SomeResource`. */
let res <- create SomeResource()
/* Call the function `use` and move the resource `res` into it. */
use(resource: <-res)
/* Invalid: The resource constant `res` cannot be used again,
* as it was moved in the previous function call.
*/
use(resource: <-res)
/* Invalid: The resource constant `res` cannot be used again,
* as it was moved in the previous function call.
*/
res.value
/* Declare a function which has a resource parameter.
* This function is invalid, because it does not always use the resource parameter,
* which would cause a loss of the resource.
*/
access(all)
fun sometimesDestroy(resource: @SomeResource, destroyResource: Bool) {
if destroyResource {
destroy resource
}
/* Invalid: The resource parameter `resource` is not always used, but must be.
* The destroy statement is not always executed, so at the end of this function
* it might have been destroyed or not. */
}
/* Declare a function which has a resource parameter.
* This function is valid, as it always uses the resource parameter,
* and does not cause a loss of the resource.
*/
access(all)
fun alwaysUse(resource: @SomeResource, destroyResource: Bool) {
if destroyResource {
destroy resource
} else {
use(resource: <-resource)
}
/* At the end of the function the resource parameter was definitely used:
It was either destroyed or moved in the call of function `use`. */
}
/* Declare a function which has a resource parameter.
* This function is invalid, because it does not always use the resource parameter,
* which would cause a loss of the resource.
*/
access(all)
fun returnBeforeDestroy(move: Bool) {
let res <- create SomeResource(value: 1)
if move {
use(resource: <-res)
return
} else {
/* Invalid: When this function returns here, the resource variable
`res` was not used, but must be. */
return
}
/* Invalid: the resource variable `res` was potentially moved in the
* previous if-statement, and both branches definitely return,
* so this statement is unreachable. */
destroy res
}
Resource Variables
リソース変数に値を代入することはできません。これは、変数の現在のリソース値が失われることになるからです。
代わりに、スワップ文(<->
)またはシフト文(<- target <-
)を使用して、リソース変数を別のリソースに置き換えます。
access(all)
resource R {}
var x <- create R()
var y <- create R()
/* Invalid: Cannot assign to resource variable `x`,
* as its current resource would be lost
*/
x <- y
/* Instead, use a swap statement. */
var replacement <- create R()
x <-> replacement
/* `x` is the new resource.
`replacement` is the old resource. */
/* Or use the shift statement (`<- target <-`)
* This statement moves the resource out of `x` and into `oldX`,
* and at the same time assigns `x` with the new value on the right-hand side. */
let oldX <- x <- create R()
/* oldX still needs to be explicitly handled after this statement */
destroy oldX
Nested Resources
複合型(配列やディクショナリなど)のフィールドは、リソース型を持つと通常と異なる動作をします。
フィールドのリソース内のフィールドにアクセスしたり、関数を呼び出すことは可能ですが、リソースをそのフィールドから移動することは許可されていません。その代わり、リソースを別のリソースと置き換えるために、スワップステートメントを利用できます。
let child <- create Child(name: "Child 1")
child.name /* is "Child 1" */
let parent <- create Parent(name: "Parent", child: <-child)
parent.child.name /* is "Child 1" */
/* Invalid: Cannot move resource out of variable resource field. */
let childAgain <- parent.child
/* Instead, use a swap statement. */
var otherChild <- create Child(name: "Child 2")
parent.child <-> otherChild
/* `parent.child` is the second child, Child 2.
`otherChild` is the first child, Child 1. */
リソースがネストされたリソースを持つ状態でdestroy
ステートメントで破棄すると、すべてのネストされたリソースも破棄されます。
/* 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
}
}
入れ子になったリソースが破棄される順序は決定論的(deterministic)ですが、特定されておらず、開発者によって影響を与えることはできません。例えば、この例では、Parent
が破棄されると、child1
およびchild2
フィールドも特定されていない順序で破棄されます。
Cadence の以前のバージョンでは、リソースが破棄されたときに任意のコードを実行する特別なdestroy
関数を定義することが可能でしたが、今はもう利用できません。
Destroy Events
リソースの破棄時に実行する任意のコードを指定することはできませんが、リソースが破棄された際に自動的に発行される特別なイベントを指定することは可能です。このイベントには予約名ResourceDestroyed
が割り当てられており、特別な構文を使用します。
resource R {
event ResourceDestroyed(id: UInt64 = self.id)
let id: UInt64
init(_ id: UInt64) {
self.id = id
}
}
このように定義された型R
の値が破棄されると、特別な
R.ResourceDestroyed
イベントが発行されます。 ResourceDestroyed
の定義で使用されている特別な構文は、全てのイベントパラメータに関連付いた値を指定します。
この場合、R.ResourceDestroyed
イベントのid
フィールドは、リソースRが破棄される直前に保持していたid
フィールドの値です。 一般的に、いくつかのResourceDestroyed
イベントは次のように定義される:
event ResourceDestroyed(field1: T1 = e1, field2: T2 = e2, ...)
イベントのfield1
の値は、リソースを破棄する前にe1
を評価した結果となります。イベントのfield2
の値は、リソースを破棄する前にe2
を評価した結果となります。以下同様です。当然のことながら、e1
およびe2
もそれぞれ、T1
およびT2
型の式でなければなりません。
これらのイベントが実行時に失敗することなく確実に発せられることを保証するために、それらの定義で使用できる型と式の種類には制限があります。
一般的には、フィールドの値を宣言する式は、self
(アタッチメントの場合はbase
)のメンバー、selfのインデックス付きアクセス、またはリテラルのみです。イベントのフィールドの型は、数値型、String
、Boolean
、Address
、Path
に限定されます。
Resources in Closures
リソースはクロージャで捕捉できません。重複が発生する可能性があるためです。
resource R {}
/* Invalid: Declare a function which returns a closure which refers to
* the resource parameter `resource`. Each call to the returned function
* would return the resource, which should not be possible.
*/
fun makeCloner(resource: @R): fun(): @R {
return fun (): @R {
return <-resource
}
}
let test = makeCloner(resource: <-create R())
Resources in Arrays and Dictionaries
配列とディクショナリは、リソースを含む場合、異なる動作をします。配列にインデックスを指定して特定のインデックスの要素を読み取ったり、そこに値を代入したり、ディクショナリにインデックスを指定して特定のキーの値を読み取ったり、キーに値を設定したりすることはできません。
代わりに、swapステートメント(<->
)またはshiftステートメント(<- target <-
)を使用して、リソースを別の変数に置き換えます。
resource R {}
/* Declare a constant for an array of resources.
* Create two resources and move them into the array.
* `resources` has type `@[R]`
*/
let resources <- [
<-create R(),
<-create R()
]
/* Invalid: Reading an element from a resource array is not allowed. */
let firstResource <- resources[0]
/* Invalid: Setting an element in a resource array is not allowed,
* as it would result in the loss of the current value.
*/
resources[0] <- create R()
/* Instead, when attempting to either read an element or update an element
* in a resource array, use a swap statement with a variable to replace
* the accessed element.
*/
var res <- create R()
resources[0] <-> res
/* `resources[0]` now contains the new resource.
`res` now contains the old resource. */
/* Use the shift statement to move the new resource into
the array at the same time that the old resource is being moved out */
let oldRes <- resources[0] <- create R()
/* The old object still needs to be handled */
destroy oldRes
ディクショナリについても同様です。
/* Declare a constant for a dictionary of resources.
* Create two resources and move them into the dictionary.
* `resources` has type `@{String: R}`
*/
let resources <- {
"r1": <-create R(),
"r2": <-create R()
}
/* Invalid: Reading an element from a resource dictionary is not allowed.
* It's not obvious that an access like this would have to remove
* the key from the dictionary.
*/
let firstResource <- resources["r1"]
/* Instead, make the removal explicit by using the `remove` function. */
let firstResource <- resources.remove(key: "r1")
/* Invalid: Setting an element in a resource dictionary is not allowed,
* as it would result in the loss of the current value.
*/
resources["r1"] <- create R()
/* Instead, when attempting to either read an element or update an element
* in a resource dictionary, use a swap statement with a variable to replace
* the accessed element.
*
* The result of a dictionary read is optional, as the given key might not
* exist in the dictionary.
* The types on both sides of the swap operator must be the same,
* so also declare the variable as an optional.
*/
var res: @R? <- create R()
resources["r1"] <-> res
/* `resources["r1"]` now contains the new resource.
`res` now contains the old resource. */
/* Use the shift statement to move the new resource into
the dictionary at the same time that the old resource is being moved out */
let oldRes <- resources["r2"] <- create R()
/* The old object still needs to be handled */
destroy oldRes
リソースを配列やディクショナリに複数回移動することはできません。重複が発生するためです。
let resource <- create R()
/* Invalid: The resource variable `resource` can only be moved into the array once. */
let resources <- [
<-resource,
<-resource
]
let resource <- create R()
/* Invalid: The resource variable `resource` can only be moved into the dictionary once. */
let resources <- {
"res1": <-resource,
"res2": <-resource
}
リソースの配列やディクショナリは破棄できます。
let resources <- [
<-create R(),
<-create R()
]
destroy resources
let resources <- {
"r1": <-create R(),
"r2": <-create R()
}
destroy resources
配列の関数であるappend
、insert
、remove
は、リソース以外の配列と同様に動作します。
let resources <- [<-create R()]
/* `resources.length` is `1` */
resources.append(<-create R())
/* `resources.length` is `2` */
let first <- resource.remove(at: 0)
/* `resources.length` is `1` */
destroy first
resources.insert(at: 0, <-create R())
/* `resources.length` is `2` */
/* Invalid: The statement ignores the result of the call to `remove`,
which would result in a loss. */
resource.remove(at: 0)
destroy resources
配列関数contains
は利用できません。これは不可能だからです。(リソースがcontains
関数に渡されれば、それは定義上、配列には含まれないからです。)
配列関数concat
は利用できません。リソースが重複してしまうからです。
insert
やremove
などのディクショナリ関数は、リソース以外のディクショナリの場合と同様に動作します。
let resources <- {"r1": <-create R()}
/* `resources.length` is `1` */
let first <- resource.remove(key: "r1")
/* `resources.length` is `0` */
destroy first
let old <- resources.insert(key: "r1", <-create R())
/* `old` is nil, as there was no value for the key "r1"
`resources.length` is `1` */
let old2 <- resources.insert(key: "r1", <-create R())
/* `old2` is the old value for the key "r1"
`resources.length` is `1` */
destroy old
destroy old2
destroy resources
Resource Identifier
リソースには暗黙的な一意の識別子が関連付けられており、各リソースにあらかじめ宣言されたpublicフィールドlet uuid: UInt64
によって実装されています。
この識別子は、リソースが作成された際に、リソースの初期化子(init関数)が呼び出される前に自動的に設定され(つまり、識別子は初期化子(init関数)で使用できます)、リソースが破棄された後でも一意であり続けます。つまり、2つのリソースが同じ識別子を持つことはありません。
/* Declare a resource without any fields. */
resource R {}
/* Create two resources */
let r1 <- create R()
let r2 <- create R()
/* Get each resource's unique identifier */
let id1 = r1.uuid
let id2 = r2.uuid
/* Destroy the first resource */
destroy r1
/* Create a third resource */
let r3 <- create R()
let id3 = r3.uuid
id1 != id2 // true
id2 != id3 // true
id3 != id1 // true
⚠ WARNING
識別子の生成方法の詳細は、実装の詳細です。
Cadenceプログラムにおける特定の動作を前提としたり、それに依存したりしないでください。
Resource Owner
リソースには、暗黙的なフィールドlet owner: &Account?
があります。リソースがアカウントのストレージに保存されている場合、このフィールドにアカウントのpublicアクセス可能部分が含まれます。それ以外の場合、このフィールドにはnil
が入りります。
リソースがアカウント外のストレージからアカウントのストレージに移動した場合、あるアカウントのストレージから別のアカウントのストレージに移動した場合、およびアカウントのストレージから移動した場合、このフィールドの値は変更されます。
翻訳元
Flow BlockchainのCadence version1.0ドキュメント (Resources)