Previous << Inbox
Next >> Contracts
Attachmentsは、開発者が構造体やリソースタイプ(宣言されていないものも含む)を、そのタイプのオリジナルの作成者が意図した動作を計画したり考慮したりすることなく、新しい機能で拡張できるように設計されたCadenceの機能です。
Declaring Attachments
Attachmentsは、attachment
キーワードで宣言され、これは新しい形式の複合宣言を使用して宣言されます。attachment <Name> for <Type>: <Conformances> { ... }
ここで、Attachmentsの関数とフィールドは本体で宣言されます。したがって、以下はAttachmentsの合法(有効)な宣言の例となります。
access(all)
attachment Foo for MyStruct {
// ...
}
attachment Bar for MyResource: MyResourceInterface {
// ...
}
attachment Baz for MyInterface: MyOtherInterface {
// ...
}
他のすべての型宣言と同様に、Attachmentsはall
のアクセス権限で宣言される必要があります。
Attachmentsの種類(構造体またはリソース)を指定する必要はありません。その種類は、拡張する型と同じ種類になるからです。ベース型(base type)は、具体的な複合型(composite type)またはインターフェースのいずれかであることに注意してください。前者の場合、Attachmentsは、そのベース型に明確に属する値でのみ使用可能であり、インターフェースの場合、Attachmentsは、そのインターフェースに準拠する任意のタイプで使用可能です。
Attachmentsの本体は、複合型と同じ宣言規則に従います。特に、フィールド・メンバーと関数メンバーの両方を持つことができ、フィールド・メンバーはすべて初期化子(init)で初期化する必要があります。リソース・メンバーを持つことができるのは、リソース・タイプであるAttachmentsのみです。
self
キーワードはアタッチメント本体で使用できますが、複合型とは異なり、self
は複合型ではなく参照型です。A
に対するアタッチメント宣言では、self
の型はA
への参照となり、他の複合型宣言のようにA
ではなくなります。この参照が持つ特定の権限は、self
参照が現れるメンバ関数に関連付けられたアクセス修飾子に依存し、以下で詳しく説明します。
Attachmentのリソースがdestroy
された場合、そのAttachmentはすべて不特定の順序でdestroy
されます。この場合のAttachmentの破棄順序について唯一保証されているのは、ベースリソース(base resource)が最後に破棄されるということです。
Attachmentの本文内にはbase
キーワードも利用可能であり、Attachmentのベース値への参照が含まれています。つまり、Attachmentが添付されている複合型です。したがって、その型はAttachmentの宣言されたベース型への参照となります。つまり、access(all) attachment Foo for Bar
と宣言されたAttachmentの場合、Foo
のbase
フィールドの型は&Bar
となります。したがって、例えば、以下のようなAttachmentの宣言は有効です。
access(all)
resource R {
access(all)
let x: Int
init (_ x: Int) {
self.x = x
}
access(all)
fun foo() { ... }
}
access(all)
attachment A for R {
access(all)
let derivedX: Int
init (_ scalar: Int) {
self.derivedX = base.x * scalar
}
access(all)
fun foo() {
base.foo()
}
}
外部による変異チェックやaccess controlの目的では、Attachmentはベースタイプとは別の宣言とみなされます。したがって、開発者はbase
値のaccess(self)
フィールド(またはAttachmentが別のスマートコントラクトで定義されている場合はaccess(contract)
フィールド)にアクセスすることはできません。また、配列やディクショナリ型のフィールドを変更することもできません。
access(all)
resource interface SomeInterface {
access(all)
let b: Bool
access(self)
let i: Int
access(all)
let a: [String]
}
access(all)
attachment SomeAttachment for SomeContract.SomeStruct {
access(all)
let i: Int
init(i: Int) {
if base.b {
self.i = base.i // cannot access `i` on the `base` value
} else {
self.i = i
}
}
access(all)
fun foo() {
base.a.append("hello") // cannot mutate `a` outside of the composite where it was defined
}
}
Attachmentのメンバ関数内では、base
およびself
参照は、関数のアクセス修飾子が指定するものと同じ権限を有します。例えば、access(all) attachment A for R
として宣言されたAttachmentにおいて、関数access(E) fun foo()
の定義内では、base
の型はauth(E) &R
となり、self
の型はauth(E) &A
となります。したがって、以下の定義は有効です。
resource R {
access(E)
fun foo() {
//...
}
}
access(all)
attachment A for R {
access(E)
fun bar() {
base.foo() // available because `E` is required above, and thus `base` is type `auth(E) &R`.
}
}
しかし、これは動作しません(無効です)。
resource R {
access(E)
fun foo() {
//...
}
}
access(all)
attachment A for R {
access(self)
fun bar() {
base.foo() // unavailable because this function has `self` access, and thus `base` only is type `&R`.
}
}
ここで、self
およびbase
値に権限(entitlements)が伝播される方法の結果として、Attachment定義はベース値(base values)がサポートする権限のみをサポートできることに注意してください。すなわち、R
用に定義されたAttachment A
は、その定義で権限Eを使用できるのは、R
がその定義(または準拠するインターフェースの定義)でE
も使用している場合のみです。
Attachment Types
access(all)
宣言されたAttachment A for C { ... }
の名目上の型は A
となります。
アタッチメントは第一級の値(原文: first. class values)ではないため、その使用法は一定の制限を受けることに注意することが重要です。そのため、その使用方法には一定の制限があります。特に、それらの型は参照型の外側には現れません。例えば、Attachmentの宣言attachment A for X {}
が与えられた場合、型A
, A?
, [A]
, fun(): A
は有効なtype annotationではありませんが、&A
, &A?
, [&A]
とfun(): &A
は有効です。
Creating Attachments
Attachmentは、attach
式を使用して作成され、この式では、Attachmentの初期化とベース値へのアタッチメントが単一の操作で実行されます。Attachmentは第一級の値ではなく、ベース値から独立して存在することはできず、単独で移動させることもできません。つまり、Attachmentコンストラクタを呼び出せるのは、attach
式の中だけです。Attachment値の作成とAttachmentを密接に結合することで、ユーザーにとってアタッチメントの処理がよりシンプルになります。また、この理由により、リソース添え字は、attach
式に現れる場合、明示的な<-
移動演算子を必要としません。
attach式は、attach
キーワード、添え字値のコンストラクタ呼び出し、to
キーワード、および添え字の基本値を評価する式で構成されます。添え字の初期化子(init)に必要な引数はすべて、コンストラクタ呼び出しで指定されます。
access(all)
resource R {}
access(all)
attachment A for R {
init(x: Int) {
//...
}
}
// ...
let r <- create R()
let r2 <- attach A(x: 3) to <-r
to
キーワードの右辺の式は、アタッチメントのベースのサブタイプである複合値として評価されなければならず、to
の左辺のコンストラクタが呼び出される前に評価されます。つまり、base
値はアタッチメントの初期化子(init)の中で利用可能ですが、作成中のアタッチメントはコンストラクタの実行が終了するまではbase
上でアクセスできないことに注意することが重要です(以下の"Accessing Attachments"のセクションを参照)。
access(all)
resource interface I {}
access(all)
resource R: I {}
access(all)
attachment A for I {}
// ...
let r <- create R()
/* has type @R */
let r2 <- attach A() to <-r
/* ok, because `R` is a subtype of `I`, still has type @R */
アタッチメントはtype別にbaseに保存されるため、値に同時に存在できるアタッチメントは、各typeにつき1つだけです。 値にすでにアタッチメントが存在する際に、ユーザーがその値にさらにアタッチメントを追加しようとすると、Cadenceは実行時エラーを発生させます。 attach
式によって返される型は、to
の右辺の式と同じ型です。アタッチメントを値にattach(アタッチ)しても、その型の変更は行われません。
Accessing Attachments
Attachmentは、type-indexを介して複合型でアクセスされます。複合型の値は、キーがtypeで値がAttachmentであるディクショナリのような機能を持ちます。複合型値v
が与えられた場合、インデックス構文を使用して、v
上のA
という名前のAttachmentを検索することができます。
let a = v[A] // has type `&A?`
この構文では、A
が名詞アタッチメント型であり、v
がA
の宣言された基本型のサブタイプである複合型を持つことが必要です。前述の通り、アタッチメントは第一級の値ではないため、このインデックス処理では、v
上のアタッチメントへの参照が返され、アタッチメント自体は返されません。指定された型のアタッチメントが v
上に存在しない場合、この式は nil
を返します。
アタッチメントへのアクセスが許可される権限のセットは、ベース値が許可される権限のセットと同じです。例えば、A
の定義が以下のようになっている場合:
entitlement E
entitlement F
resource R {
access(E)
fun foo() {
// ...
}
access(F)
fun bar() {
// ...
}
}
attachment A for R {
access(E | F)
fun qux() {
// ...
}
}
// ...
let a = v[A]!
v
が型&R
を持つ場合、a
の結果の型は未承認の &A
となります。逆に、v
が型 auth(E) &R
を持つ場合、a
の型は同じものに承認されます。すなわち、auth(E) &A
となります 最後に、v
が参照(すなわち、R
型の所有値)でない場合、a
はA
に対して「完全な権利」を持つことになります。A
によって言及されるすべての権利が与えられます。すなわち、この場合、a
の型はauth(E, F) &A
となります。
これは、Identity
権限マッピングの動作とほぼ同等です。実際、アタッチメントは、ベース値上のIdentity
マッピング・フィールドであると考えることができます。
Removing Attachments
アタッチメントは、remove
ステートメントを使用して値から削除することができます。このステートメントは、remove
キーワード、削除するアタッチメントの名詞(原文: nominal)型、from
キーワード、およびアタッチメントを削除する対象となる値で構成されます。
from の右辺の値は、アタッチメントの型の宣言されたベースのサブタイプである複合値でなければなりません。
例えば、アタッチメントをサポートするタイプを持つリソース r
からアタッチメント A
を削除するには、以下のようになります。
remove A from r
ステートメントが実行された後、from
の右辺にある複合値にはAttachmentは含まれなくなります。 値に remove
キーワードの後に表示されるAttachmentが含まれない場合、このステートメントは効果を持ちません。
Attachmentは、どのタイプからでも任意の順序で削除することができます。そのため、ユーザーは、他のAttachmentの特定の動作に依存するようなアタッチメントを設計しないよう注意する必要があります。
Attachmentを含むリソースがdestroy
された場合、そのアタッチメントはすべて任意の順序でdestroy
されます。
翻訳元
Flow BlockchainのCadence version1.0ドキュメント (Attachments)