Previous << Intersection Types
Next >> Imports
オブジェクト、すなわちリソースや構造体への参照を作成することができます。参照を使用して、参照先のオブジェクトのフィールドにアクセスしたり、関数を呼び出したりすることができます。
参照はコピーされます。すなわち値型となります。
参照の型は&T
で、T
は参照されるオブジェクトの型です。
参照は&
演算子を使用して作成されます。参照の型は明示的に指定する必要があり、例えば変数宣言上のtype annotation、またはas
演算子を使用した型断定(type assertion)を使って参照の型を指定します。
let hello = "Hello"
/* Create a reference to the `String` `hello`.
* Provide the reference type `&String` using a type assertion
*/
let helloRef = &hello as &String
helloRef.length // is `5`
/* Create another reference to the `String` `hello`.
* Provide the reference type `&String` using a type annotation instead
*/
let alsoHelloRef: &String = &hello
/* Invalid: Cannot create a reference without an explicit type */
let unknownRef = &hello
参照の型(reference type)は、参照されるオブジェクトの型のスーパータイプでなければなりません。
/* Invalid: Cannot create a reference to `hello`
* typed as `&Int`, as it has type `String`
*/
let intRef = &hello as &Int
オプショナル値への参照を作成すると、結果はオプショナル参照となります。参照された値が nil の場合、結果の参照自体も nil となります。参照された値が存在する場合、オプショナル参照を強制すると、その値への参照が得られます。(補足: オプショナルが外れます)
let nilValue: String? = nil
let nilRef = &nilValue as &String? /* r has type &String? */
let n = nilRef! /* error, forced nil value */
let strValue: String? = ""
let strRef = &strValue as &String? /* r has type &String? */
let n = strRef! /* n has type &String */
参照はベースの型においてcovariant(共起表現)です。例えば、T
がU
のサブタイプである場合、&T
は&U
のサブタイプです。
/* Declare a resource interface named `HasCount`,
* that has a field `count`
*/
resource interface HasCount {
count: Int
}
/* Declare a resource named `Counter` that conforms to `HasCount` */
resource Counter: HasCount {
access(all)
var count: Int
access(all)
init(count: Int) {
self.count = count
}
access(all)
fun increment() {
self.count = self.count + 1
}
}
/* Create a new instance of the resource type `Counter`
* and create a reference to it, typed as `&Counter`,
* so the reference allows access to all fields and functions
* of the counter
*/
let counter <- create Counter(count: 42)
let counterRef: &Counter = &counter as &Counter
counterRef.count /* is `42` */
counterRef.increment()
counterRef.count /* is `43` */
参照は自由にupcast(アップキャスト)およびdowncast(ダウンキャスト)でき、参照された型に対してcovariant(共起表現)です。例えば、あるstruct(構造体)S
の場合、&S
は&AnyStruct
のサブタイプですが、&Int
のサブタイプではありません。
/* Create an reference to the counter,
* typed with the intersection type `&{HasCount}`,
* i.e. some resource that conforms to the `HasCount` interface
*/
let countRef = &counter as &{HasCount}
countRef.count /* is `43` */
/* Invalid: The function `increment` is not available
* for the type `&{HasCount}`
*/
countRef.increment()
/* We can conditionally downcast `countRef` to a `Counter` if it has
* that type at runtime.
*/
let counterRef2: &Counter = countRef as? &Counter
counterRef2.increment()
参照は一時的なものであり、つまり保存することはできません。代わりに、必要に応じたCapabilityの保存や借用(borrow)で代用することを検討してください。
Authorized References
デフォルト状態では、参照は認証されていません。しかし、参照を一連のEntitlementに認証することもできます。
認証された参照には、auth
修飾子と、認証された一連の権限が付与されます。
E
、F
、G
全ての認証が与えられた参照のシンタックスはauth(E, F, G) &T
であり、E
、F
、G
いずれかの認証が与えられた参照のシンタックスは auth(E | F | G) &T
です。認証された参照は認証されていない参照のサブタイプです。
参照が作成されたときにのみ権限は付与することができ、値への参照は値の所有者のみが作成できます。参照を作成する時、値の所有者は追加したい任意の権限セットをその参照に付与することができます。
権限を保有していると参照はその参照先の型がついた関数やフィールドにアクセスできるようになります。例えば、HasCount
インタフェースを拡張する場合:
entitlement Reset
resource interface HasCount {
count: Int
access(Reset)
fun resetCount()
}
次に、&{HasCount}
という型の認証されていない参照は、resetCount
を呼び出すことができません。しかし、次のようにして呼び出しが可能な参照を作り出すことができます。
let authCountRef: auth(Reset) &{HasCount} = &counter
/* Valid, because `authCountRef` is authorized to `Reset` */
authCountRef.resetCount()
重要なのは、参照は参照の型に対してcovariant(つまりダウンキャストが可能)である一方で、参照の認証された部分は決してダウンキャストできないということです。実際、参照に権限を「add」できる唯一の方法は、上記の例のように、参照の作成時に追加することだけです。参照は、作成時に設定された以上の権限を持つことは決してなく、参照の権限セットの実行時には静的(static)に表現された型のセットと常に一致します。結論として、認証された参照をアップキャストすると、実行時の型が変更されます。
let authCountRef: auth(Reset) &{HasCount} = &counter
let unauthCountRef = authCountRef as &{HasCount}
let authCountRef2 = unauthCountRef as? auth(Reset) &{HasCount}
/* Invalid: `authCountRef2` is `nil`, as the upcast of `authCountRef` cleared the
`Reset` entitlement from the reference, meaning that it cannot be regained on downcasting. */
authCountRef2.resetCount()
これの利点は、権限に関して「予期せぬ」動作が決して起こらないこと、すべての参照の値が実行時に何ができるのかが透明であることです。
参照に対する権限セットはダウンキャストできませんが、アップキャストしたり、スーパータイプを期待する場所で使用したりできます。また、それらが|
または,
で区切られたセットであるかどうか、に基づいた特別なサブタイピングルールがあります。
一般的に、{Us}
が{Vs}
よりも多くの権利を含んでいる場合、権利セット{Us}
は権利セット{Vs}
のサブタイプです。またどちらも(ほとんどの場合がそうであるように),
で区切られている場合、この{Us}
が{Vs}
のスーパーセットである場合、{Us}
は{Vs}
のサブセットであるというルールが正確に適用されます。
逆に、両者が|
区切りである場合、このルールは逆転します。
{Us}
は、{Vs}
のサブセットである場合、{Vs}
のサブセットとなります。この場合、{Us}
は{Vs}
よりもより具体的であると考えるのが役立つかもしれません。
{Vs}
は参照が所有している可能性がある一連の権利を表現していますが、{Us}
はより具体的な潜在的な権利のセットを表現しています。
最後に、{Us}
が,
で区切られているのに対し、{Vs}
が|
で区切られている場合、{Us}
は{Vs}
のサブセットとなります。ただし、Us
のいずれかが{Vs}
にも含まれている場合に限ります。その理由を理解するには、{Vs}
が参照が所有している可能性のある権利のセットを表していることを再度考慮し、これらの権利のうち少なくとも1つが{Us}
に含まれている限り (参照が所有している可能性がある権利の集合であり、参照が所有しているとわかっている権利の集合)に含まれる限り、{Vs}
によって提供される記述は正しいことになります。
これらのルールを説明する例:
let eRef: auth(E) &T = ...
let efRef: auth(E, F) &T = ...
let eOrFRef: auth(E | F) &T = ...
/* Invalid, `eRef` only has `E` but `F` is required */
eRef as auth(F) &T
/* Invalid, `eRef` only has `E` but both `E` and `F` are required */
eRef as auth(E, F) &T
/* Valid, `eRef` definitely has `E` and either `E` or `F` is sufficient */
eRef as auth(E | F) &T
/* Valid, `efRef` both `E` and `F` but only `F` is required */
efRef as auth(F) &T
/* Valid, `efRef` both `E` and `F`, and either is sufficient */
efRef as auth(E | F) &T
/* Invalid, `eOrFRef` has one of `E` or `F` but we need to definitely have `F` */
eOrFRef as auth(F) &T
/* Invalid, `eOrFRef` has one of `E` or `F` but we need both */
eOrFRef as auth(E, F) &T
References and Entitlement Mappings
ほとんどの状況では、権限マッピングは参照の型のauth
部分で有効です。ただし、複合型のフィールドまたは関数の宣言において、権限マッピングがauth
修飾子で使用できる場合があります。
フィールドが権限マッピングで宣言されている場合:
entitlement mapping M {
/* omitted */
}
resource interface I {
access(M)
let foo: auth(M) &T
}
ここで、M
はauth(M) &T
で、iRef.foo
へのアクセスによって生成される参照が持つ権限は、I
に対する権限によって決定されることを示します。iRef
は、{I}
への参照であるiRef
値を持っています。概念的には、"output"参照の型と"input"アクセス修飾子の対応関係が作成されます。
アクセッサー関数(function)が権限マッピングとともに定義される場合:
entitlement mapping M {
/* omitted */
}
resource I {
access(self)
let myField: T
access(M)
fun getMyField(): auth(M) &T {
return &self.myField as auth(M) &T
}
}
関数の戻り値のtype annotationのauth(M) &T
におけるM
は、フィールドの場合と同じ意味を示します。ただし、この例では、M
は関数の本体内の参照の型でも使用されています。権限マッピング付きアクセスを持つ関数の本体内では、権限マッピングの名前をmapの出力権限の代替として使用できます。
Field and Index Access
container型(構造体/リソース、ディクショナリ、配列)への参照を使用して、コンテナのフィールドまたは要素にアクセス(読み取り/書き込み)することができます。
参照を通じてフィールド/インデックスが読み込まれると、以下が返されます。
- フィールド/インデックスの値もcontainer型である場合、参照が返されます。
- または、値がプリミティブ型である場合、具体的な値が返されます。
例えば、以下のCollection
リソースには2つのフィールドがあります。1つ(id)はString型、もう1つ(ownedNFTs)はディクショナリ型です。
resource Collection {
/* Primitive-typed field */
access(all)
var id: String
/* Dictionary typed field */
access(all)
var ownedNFTs: @{UInt64: NFT}
}
したがって、
var collectionRef: &Collection = ...
/* `collectionRef.ownedNFTs` would return a reference of type `&{UInt64: NFT}`. */
var ownedNFTsRef: &{UInt64: NFT} = collectionRef.ownedNFTs
/* Whereas, `collectionRef.id` would return the value, since it is a primitive type. */
var id: String = collectionRef.id
同様に、配列/ディクショナリの要素にアクセスすると参照が返されます。
/* Index-access to an array reference would return a reference to the element. */
var resourceArrayRef: &[AnyResource] = ...
var elementRef: &AnyResource = collectionArrayRef[2]
/* Whereas, if the array is of a primitive type, it will return the concrete value. */
var intArrayRef: &[Int] = ...
var element: Int = intArrayRef[2]
/* Index-access to a dictionary reference would return a reference to the value. */
var resourceDictionaryRef: &{String: AnyResource} = ...
var valueRef: &AnyResource? = resourceDictionaryRef["two"]
/* Whereas, if the dictionary values are of a primitive type, it will return the concrete value. */
var intDictionaryRef: &{String: Int} = ...
var value: Int? = intDictionaryRef["two"]
また、上記の例では、返された参照には権限がありません。つまり、非認証参照です。
構造体/リソースフィールドの権限付き参照を取得するには、権限マッピングで定義する必要があります。しかし、配列/ディクショナリ参照のインデックス/キーの値にアクセスすると、常に非認証参照が返されます。
Index Assignment
配列やディクショナリの参照のインデックスへの代入は、権利付き操作です。つまり、配列/ディクショナリ用の代入演算子には、Mutate
およびInsert
のビルトインEntitlementもあります。
代入は、Mutate
または(Insert
、Remove
)の権利を持つ組み込み関数であると考えることができます。例:
access(Mutate | (Insert, Remove))
set(keyOrIndex, value) { ... }
(Mutate | (Insert, Remove))
のようなアクセス修飾子でネストした権限を持つ構文は現在サポートされていませんが、これは説明のための例です。
したがって、
var arrayRef = &array as &[String]
arrayRef[2] = "John"
/* Static Error: updating via a read-only reference */
var mutableArrayRef = &array as auth(Mutate) &[String]
mutableArrayRef[2] = "John"
/* OK */
var insertableArrayRef = &array as auth(Insert) &[String]
insertableArrayRef[2] = "John"
/* Static Error: doesn't have the required entitlement */
var removableArrayRef = &array as auth(Remove) &[String]
removableArrayRef[2] = "John"
/* Static Error: doesn't have the required entitlement */
var insertableAndRemovableArrayRef = &array as auth(Insert, Remove) &[String]
insertableAndRemovableArrayRef[2] = "John"
/* OK */
Reference Validity
一時的な参照は、プログラムの実行中、有効な状態を維持します。しかし、参照されたリソースが参照後に移動または破棄された場合、リソースへの参照はプログラムの実行中に無効になる可能性があります。
let r <-create R()
/* Take a reference to resource. */
let ref = &r as &R
/* Then transfer the resource into an account.
This will invalidate all the references taken to the resource `r`. */
account.storage.save(<-r, to: /storage/r)
/* Static error, since the referenced resource has been moved. */
ref.id = 2
参照元と参照先の場所に関わらず、リソースが最初に転送された時点で参照は無効になります。
let ref = &r as &R
/* Moving a resource to a different variable invalidates all references to it. */
let r2 <- r
/* Static error, since the referenced resource has been moved. */
ref.id = 2
💡 TIP
ストレージ参照の無効化は静的に捕捉されず、実行時のみに捕捉されます。
Dereferencing values
プリミティブ値(およびプリミティブ値の配列またはディクショナリ)は、単項*
演算子を使用して「参照解除」することができます。この操作により参照された値のコピーが作成されるため、例えば、以下のようなコードが与えられた場合:
var x = 3
let ref: &Int = &x
var y = *ref
y = y + 1
このコードの実行が終了した時点で、y
は明らかに4
になりますが、x
は依然として3
のままです。*ref
操作により値がコピーされるためです。これは配列の例を使用すると、より明確になります。
let x = [0]
let ref: &[Int] = &x
var y = *ref
y.append(1)
この実行の終了時には、y
には[0, 1]
が格納され、x
には[0]
のみが残ります。
非プリミティブ値(例:構造体、リソース、contract、列挙型)への参照は、間接参照できません。
翻訳元
Previous << Intersection Types
Flow BlockchainのCadence version1.0ドキュメント (References)