- Go back to TOP(インデックスページへ)
- Interface Declaration
- Interface Implementation
- Interfaces in Types
- Interface Default Functions
- Interface inheritance
インターフェースは、インターフェースを実装する型の振る舞いを指定する抽象型です。インターフェースには以下を宣言します。必要な関数及びフィールド、それらに対するアクセス制御、pre条件、post条件。これを実装した型が提供する必要があるもの(以降これを要件と示します)を示します。(補足: このインターフェース型を明示したCapabilityはこのインターフェースに宣言した関数しか呼び出せません。)
インターフェースには以下の3種類があります。
構造体、リソース、およびコントラクト型は、複数のインターフェースを実装する事ができます。
イベントやenum(列挙型)のインターフェースはサポートされていません。
名称型(Nominal typing)は、インターフェースを実装する複合型に適用されます。つまり、型がインターフェースを実装するのは、適合性を明示的に宣言した場合のみであり、複合型は、インターフェースのすべての要件を満たしていても、暗黙的にインターフェースに適合することはありません。
インターフェースは、それを実装する型が提供しなければならない関数およびフィールドから構成されます。その為インターフェース内とその実装は、常に少なくともpublicです。(publicでない関数をインターフェースに書く事は出来ません)
変数のフィールドには、パブリックにセット可能(settable)にさせるアノテーションを付けることもできます。
関数の要件には、関数名、パラメータ型、オプション(必須ではない)の戻り値型、オプションのpre条件およびpost条件から構成されます。
フィールドの要件は、フィールド名と型で構成されます。オプション(必須ではない)で、getterとsetter(それぞれにpre条件とpost条件が伴う)を宣言することができます。
インターフェースにpre条件とpost条件を伴う関数を呼び出すことで、具体的な実装に代わってプログラムのセキュリティを向上させることができます。実装が変更された場合でも、その一部は常に保持されることが保証するためです。(補足: セキュリティを向上させる目的は実装にも最低限引き継がれる為)
Interface Declaration
インターフェースは、struct
、resource
、またはcontract
キーワードを使用して宣言し、その後にinterface
キーワード、インターフェース名を続けます。その後に開始波括弧と終了波括弧で囲んだ実装要件を記述します。
フィールドの要件には、実装を変数フィールドとすることを要求するためにvarキーワードを使用したり、実装を定数フィールドとすることを要求するためにletキーワードを使用したりすることができます。フィールドの要件では何も指定しないことも出来、その場合は実装が変数フィールド
、定数フィールドのどちらであっても構いません。
フィールドの要件と関数の要件には、必要とされるアクセスレベルを指定しなければなりません。アクセスは少なくともパブリックでなければなりませんので、access(all)キーワードが必ずつけられます。
インターフェースは型の中で使用できます。これは「Interfaces in Types」のセクションで詳しく説明されています。現時点では、{I}
シンタックスはインターフェースI
を実装する値の型と解釈できます。
// Declare a resource interface for a fungible token.
// Only resources can implement this resource interface.
//
access(all)
resource interface FungibleToken {
// Require the implementing type to provide a field for the balance
// that is readable in all scopes (`access(all)`).
//
// Neither the `var` keyword, nor the `let` keyword is used,
// so the field may be implemented as either a variable
// or as a constant field.
//
access(all)
balance: Int
// Require the implementing type to provide an initializer that
// given the initial balance, must initialize the balance field.
//
init(balance: Int) {
pre {
balance >= 0:
"Balances are always non-negative"
}
post {
self.balance == balance:
"the balance must be initialized to the initial balance"
}
// NOTE: The declaration contains no implementation code.
}
// Require the implementing type to provide a function that is
// callable in all scopes, which withdraws an amount from
// this fungible token and returns the withdrawn amount as
// a new fungible token.
//
// The given amount must be positive and the function implementation
// must add the amount to the balance.
//
// The function must return a new fungible token.
// The type `{FungibleToken}` is the type of any resource
// that implements the resource interface `FungibleToken`.
//
access(all)
fun withdraw(amount: Int): @{FungibleToken} {
pre {
amount > 0:
"the amount must be positive"
amount <= self.balance:
"insufficient funds: the amount must be smaller or equal to the balance"
}
post {
self.balance == before(self.balance) - amount:
"the amount must be deducted from the balance"
}
// NOTE: The declaration contains no implementation code.
}
// Require the implementing type to provide a function that is
// callable in all scopes, which deposits a fungible token
// into this fungible token.
//
// No precondition is required to check the given token's balance
// is positive, as this condition is already ensured by
// the field requirement.
//
// The parameter type `{FungibleToken}` is the type of any resource
// that implements the resource interface `FungibleToken`.
//
access(all)
fun deposit(_ token: @{FungibleToken}) {
post {
self.balance == before(self.balance) + token.balance:
"the amount must be added to the balance"
}
// NOTE: The declaration contains no implementation code.
}
}
イニシャライザ(init関数)および関数の要件には実行可能なコードが一切含まれまない事に注目してください。
構造体とリソースのインターフェースは、スマートコントラクトの内部で直接宣言するのみであって、関数の内部では宣言できません。コントラクトインターフェースはグローバルでのみ宣言でき、コントラクトの内部では宣言できません。
Interface Implementation
型がインターフェースを準拠していることを宣言するには、複合型(構造体やリソースなど)の型宣言時に行います。複合型の種類(resourceかstruct)と名前の後にコロン(:
)を置き、その後にそれが実装する1つまたは複数のインターフェース名を入力します。
これにより、スマートコントラクトのデプロイチェッカーは指定されたインターフェイスの要件を、宣言された型が実装することを強制します。
インターフェースの実装を宣言し、インターフェイスの要件の全てのフィールドの宣言と、インターフェイスの要件のすべての関数の実装すると、型がインターフェイスを実装(準拠)したことになります。(補足: リソースや構造体は宣言するとそれが一つの型ということになるらしい)
実装する型の中のフィールド宣言は、インターフェースのフィールド要件と一致していなければならず、それは名前、型、宣言の種類(定数、変数など)が要件として指定されている場合です。例えば、インターフェースが特定の名前と型を持つフィールドを要求する場合でも、そのフィールドの種類(変数か定数か)は実装に任せる場合があります。
関数の実装は、関数の要件と一致していなければならず、名前、引数のラベル、引数の型、戻り値の型はインターフェースの関数と一致していなければなりません。
// Declare a resource named `ExampleToken` that has to implement
// the `FungibleToken` interface.
//
// It has a variable field named `balance`, that can be written
// by functions of the type, but outer scopes can only read it.
//
access(all)
resource ExampleToken: FungibleToken {
// Implement the required field `balance` for the `FungibleToken` interface.
// The interface does not specify if the field must be variable, constant,
// so in order for this type (`ExampleToken`) to be able to write to the field,
// but limit outer scopes to only read from the field, it is declared variable,
// and only has public access (non-settable).
//
access(all)
var balance: Int
// Implement the required initializer for the `FungibleToken` interface:
// accept an initial balance and initialize the `balance` field.
//
// This implementation satisfies the required postcondition.
//
// NOTE: the postcondition declared in the interface
// does not have to be repeated here in the implementation.
//
init(balance: Int) {
self.balance = balance
}
// Implement the required function named `withdraw` of the interface
// `FungibleToken`, that withdraws an amount from the token's balance.
//
// The function must be public.
//
// This implementation satisfies the required postcondition.
//
// NOTE: neither the precondition nor the postcondition declared
// in the interface have to be repeated here in the implementation.
//
access(all)
fun withdraw(amount: Int): @ExampleToken {
self.balance = self.balance - amount
return create ExampleToken(balance: amount)
}
// Implement the required function named `deposit` of the interface
// `FungibleToken`, that deposits the amount from the given token
// to this token.
//
// The function must be public.
//
// NOTE: the type of the parameter is `{FungibleToken}`,
// i.e., any resource that implements the resource interface `FungibleToken`,
// so any other token – however, we want to ensure that only tokens
// of the same type can be deposited.
//
// This implementation satisfies the required postconditions.
//
// NOTE: neither the precondition nor the postcondition declared
// in the interface have to be repeated here in the implementation.
//
access(all)
fun deposit(_ token: @{FungibleToken}) {
if let exampleToken <- token as? ExampleToken {
self.balance = self.balance + exampleToken.balance
destroy exampleToken
} else {
panic("cannot deposit token which is not an example token")
}
}
}
// Declare a constant which has type `ExampleToken`,
// and is initialized with such an example token.
//
let token <- create ExampleToken(balance: 100)
// Withdraw 10 units from the token.
//
// The amount satisfies the precondition of the `withdraw` function
// in the `FungibleToken` interface.
//
// Invoking a function of a resource does not destroy the resource,
// so the resource `token` is still valid after the call of `withdraw`.
//
let withdrawn <- token.withdraw(amount: 10)
// The postcondition of the `withdraw` function in the `FungibleToken`
// interface ensured the balance field of the token was updated properly.
//
// `token.balance` is `90`
// `withdrawn.balance` is `10`
// Deposit the withdrawn token into another one.
let receiver: @ExampleToken <- // ...
receiver.deposit(<-withdrawn)
// Run-time error: The precondition of function `withdraw` in interface
// `FungibleToken` fails, the program aborts: the parameter `amount`
// is larger than the field `balance` (100 > 90).
//
token.withdraw(amount: 100)
// Withdrawing tokens so that the balance is zero does not destroy the resource.
// The resource has to be destroyed explicitly.
//
token.withdraw(amount: 90)
実装の中でのフィールド変数はアクセスレベル(access level)がインターフェースの要件より制限が緩い場合があります。例えば、インターフェースではフィールドが少なくともコントラクトアクセスを要件(access(contract)
)にしていても、実装ではパブリックアクセス(access(all)
)のフィールド変数にすることができます。
access(all)
struct interface AnInterface {
// Require the implementing type to provide a contract-readable
// field named `a` that has type `Int`. It may be a variable
// or a constant field.
//
access(contract)
a: Int
}
access(all)
struct AnImplementation: AnInterface {
// Declare a public variable field named `a` that has type `Int`.
// This implementation satisfies the requirement for interface `AnInterface`:
//
access(all)
var a: Int
init(a: Int) {
self.a = a
}
}
Interfaces in Types
インターフェースは、次のようにして型として使用できます。{I}
という型は、インターフェースI
を実装する全オブジェクトの型になります。
これは、intersection typeと呼ばれます。この型はこのインターフェースの機能(メンバーおよび関数)にしかアクセスできなくなります。
// Declare an interface named `Shape`.
//
// Require implementing types to provide a field which returns the area,
// and a function which scales the shape by a given factor.
//
access(all)
struct interface Shape {
access(all)
fun getArea(): Int
access(all)
fun scale(factor: Int)
}
// Declare a structure named `Square` the implements the `Shape` interface.
//
access(all)
struct Square: Shape {
// In addition to the required fields from the interface,
// the type can also declare additional fields.
//
access(all)
var length: Int
// Provided the field `area` which is required to conform
// to the interface `Shape`.
//
// Since `area` was not declared as a constant, variable,
// field in the interface, it can be declared.
//
access(all)
fun getArea(): Int {
return self.length * self.length
}
access(all)
init(length: Int) {
self.length = length
}
// Provided the implementation of the function `scale`
// which is required to conform to the interface `Shape`.
//
access(all)
fun scale(factor: Int) {
self.length = self.length * factor
}
}
// Declare a structure named `Rectangle` that also implements the `Shape` interface.
//
access(all)
struct Rectangle: Shape {
access(all)
var width: Int
access(all)
var height: Int
// Provided the field `area which is required to conform
// to the interface `Shape`.
//
access(all)
fun getArea(): Int {
return self.width * self.height
}
access(all)
init(width: Int, height: Int) {
self.width = width
self.height = height
}
// Provided the implementation of the function `scale`
// which is required to conform to the interface `Shape`.
//
access(all)
fun scale(factor: Int) {
self.width = self.width * factor
self.height = self.height * factor
}
}
// Declare a constant that has type `Shape`, which has a value that has type `Rectangle`.
//
var shape: {Shape} = Rectangle(width: 10, height: 20)
インターフェースを実装した値は、インターフェースを型とする変数に代入することができます。
// Assign a value of type `Square` to the variable `shape` that has type `Shape`.
//
shape = Square(length: 30)
// Invalid: cannot initialize a constant that has type `Rectangle`.
// with a value that has type `Square`.
//
let rectangle: Rectangle = Square(length: 10)
インターフェースを実装した型の値からは、インターフェースで宣言されたフィールドにアクセスでき、インターフェースで宣言された関数を呼び出すことができます。
// Declare a constant which has the type `Shape`.
// and is initialized with a value that has type `Rectangle`.
//
let shape: {Shape} = Rectangle(width: 2, height: 3)
// Access the field `area` declared in the interface `Shape`.
//
shape.area // is `6`
// Call the function `scale` declared in the interface `Shape`.
//
shape.scale(factor: 3)
shape.area // is `54`
Interface Nesting
🚧 ステータス
現在、ネストされたインターフェースをサポートしているのはコントラクトおよびコントラクトインターフェースのみです。
インターフェースは任意にネストすることができます。別のインターフェース内にインターフェースを宣言しても、外側のインターフェースの型を実装する必要はありません。
// Declare a resource interface `OuterInterface`, which declares
// a nested structure interface named `InnerInterface`.
//
// Resources implementing `OuterInterface` do not need to provide
// an implementation of `InnerInterface`.
//
// Structures may just implement `InnerInterface`.
//
resource interface OuterInterface {
struct interface InnerInterface {}
}
// Declare a resource named `SomeOuter` that implements the interface `OuterInterface`.
//
// The resource is not required to implement `OuterInterface.InnerInterface`.
//
resource SomeOuter: OuterInterface {}
// Declare a structure named `SomeInner` that implements `InnerInterface`,
// which is nested in interface `OuterInterface`.
//
struct SomeInner: OuterInterface.InnerInterface {}
コントラクトインターフェースは、イベントを宣言することもでき、その場合も、外部インターフェースの実装型がそのイベントを「実装」する必要はありません。イベントは、宣言しているインターフェース、条件、または関数の(デフォルト動作の)実装内で発行することができます。
例:
// Declare a contract interface
//
contract interface ContractInterface {
// some event declaration
//
event SomeEvent()
// some function that emits `SomeEvent` when called
//
fun eventEmittingFunction() {
pre {
emit SomeEvent()
}
}
}
// A contract implementing `ContractInterface`
// Note that no declaration of `SomeEvent` is required
//
contract ImplementingContract: ContractInterface {
// implementation of `eventEmittingFunction`;
// this will emit `SomeEvent` when called
//
fun eventEmittingFunction() {
// ...
}
}
Interface Default Functions
インターフェースはデフォルトの関数を提供できる:インターフェースを実装している具体的な型が、インターフェースが要求する関数の実装を提供しない場合、インターフェースのデフォルトの関数が実装で使用される。
// Declare a struct interface `Container`,
// which declares a default function `getCount`.
//
struct interface Container {
let items: [AnyStruct]
fun getCount(): Int {
return self.items.length
}
}
// Declare a concrete struct named `Numbers` that implements the interface `Container`.
//
// The struct does not implement the function `getCount` of the interface `Container`,
// so the default function for `getCount` is used.
//
struct Numbers: Container {
let items: [AnyStruct]
init() {
self.items = []
}
}
let numbers = Numbers()
numbers.getCount() // is 0
インターフェースはデフォルトの初期化子(init関数)を提供することはできません。
1つの適合性だけがデフォルトの関数を提供できます。
Interface inheritance
インターフェースは、同じ種類の他のインターフェースから継承(準拠)することができます。例えば、リソースインターフェースは別のリソースインターフェースから継承することができますが、構造体インターフェースから継承することはできません。インターフェースが別のインターフェースから継承する場合、親インターフェースのすべてのフィールド、関数、および型は、継承したインターフェースで暗黙的に利用可能になります。
access(all)
resource interface Receiver {
access(all)
fun deposit(_ something: @AnyResource)
}
// `Vault` interface inherits from `Receiver` interface.
access(all)
resource interface Vault: Receiver {
access(all)
fun withdraw(_ amount: Int): @Vault
}
上記の例では、Vault
はReceiver
を継承します。Vault
インターフェースを実装する場合は、Receiver
インターフェースも実装する必要があります。
access(all)
resource MyVault: Vault {
// Must implement all the methods coming from both `Vault` and `Receiver` interfaces.
access(all)
fun deposit(_ something: @AnyResource) {}
access(all)
fun withdraw(_ amount: Int): @Vault {}
}
Duplicate interface members
あるインターフェースが別のインターフェースを実装する場合、2つのインターフェースに同じ名前のメンバーが存在することが可能です。以下のセクションでは、さまざまなシナリオにおけるこれらの曖昧さの解決方法について説明します。
Fields
同一の名前を持つ2つのフィールドが同一の型である場合、それは有効です。
access(all)
resource interface Receiver {
access(all)
var id: UInt64
}
access(all)
resource interface Vault: Receiver {
// `id` field has the same type as the `Receiver.id`. Hence this is valid.
access(all)
var id: UInt64
}
そうでなければ、インターフェース準拠が無効となります。
access(all)
resource interface Receiver {
access(all)
var id: Int
}
access(all)
resource interface Vault: Receiver {
// `id` field has a different type than the `Receiver.id`. Hence this is invalid.
access(all)
var id: UInt64
}
Function
同じ名前の関数で(引数など)シグネチャも同じ場合は、有効です。
access(all)
resource interface Receiver {
access(all)
fun deposit(_ something: @AnyResource)
}
access(all)
resource interface Vault: Receiver {
// `deposit` function has the same signature as the `Receiver.deposit`.
// Also none of them have any default implementations.
// Hence this is valid.
access(all)
fun deposit(_ something: @AnyResource)
}
2つの関数の(引数など)シグネチャが異なっている場合、インターフェース準拠が無効となります。
access(all)
resource interface Receiver {
access(all)
fun deposit(_ something: @AnyResource)
}
access(all)
resource interface Vault: Receiver {
// Error: `deposit` function has a different signature compared to the `Receiver.deposit`.
// So these two cannot co-exist.
access(all)
fun deposit()
}
Functions with conditions
同一の名前とシグネチャを持つ2つの関数に事前/事後条件がある場合、それは依然として有効です。ただし、事前/事後条件はリニア化(Linearizing Conditionsのセクションを参照)され、条件の実行順序が決定されます。与えられる事前/事後条件はview
のみであり、実行順序が条件に影響を与えることはありません。
access(all)
resource interface Receiver {
access(all)
fun deposit(_ something: @AnyResource) {
pre{ self.balance > 100 }
}
}
access(all)
resource interface Vault: Receiver {
// `deposit` function has the same signature as the `Receiver.deposit`.
// Having pre/post condition is valid.
// Both conditions would be executed, in a pre-determined order.
access(all)
fun deposit(_ something: @AnyResource) {
pre{ self.balance > 50 }
}
}
Default functions
インターフェースは、継承された関数にデフォルトの実装を提供することができます。
access(all)
resource interface Receiver {
access(all)
fun log(_ message: String)
}
access(all)
resource interface Vault: Receiver {
// Valid: Provides the implementation for `Receiver.log` method.
access(all)
fun log(_ message: String) {
log(message.append("from Vault"))
}
}
しかし、インターフェースは継承されたデフォルトの関数実装を上書きすることはできません。
access(all)
resource interface Receiver {
access(all)
fun log(_ message: String) {
log(message.append("from Receiver"))
}
}
access(all)
resource interface Vault: Receiver {
// Invalid: Cannot override the `Receiver.log` method.
access(all)
fun log(_ message: String) {
log(message.append("from Vault"))
}
}
また、インターフェースに対して2つ以上のデフォルト実装を継承することも無効です。
access(all)
resource interface Receiver {
access(all)
fun log(_ message: String) {
log(message.append("from Receiver"))
}
}
access(all)
resource interface Provider {
access(all)
fun log(_ message: String) {
log(message.append("from Provider"))
}
}
// Invalid: Two default functions from two interfaces.
access(all)
resource interface Vault: Receiver, Provider {}
とはいえ、同じデフォルト機能が異なる継承パス経由で利用できる状況もあり得ます。
access(all)
resource interface Logger {
access(all)
fun log(_ message: String) {
log(message.append("from Logger"))
}
}
access(all)
resource interface Receiver: Logger {}
access(all)
resource interface Provider: Logger {}
// Valid: `Logger.log()` default function is visible to the `Vault` interface
// via both `Receiver` and `Provider`.
access(all)
resource interface Vault: Receiver, Provider {}
上記の例では、Logger.log()
のデフォルト関数は、Vault
インターフェースからReceiver
およびProvider
の両方を通じて参照できます。2つの異なるインターフェースから利用可能であっても、両者は同じデフォルト実装を参照しています。したがって、上記のコードは有効です。
Conditions with Default functions
より複雑な状況は、デフォルト機能が1つの継承パスで利用可能であり、事前/事後条件が別の継承パスで利用可能な場合です。
access(all)
resource interface Receiver {
access(all)
fun log(_ message: String) {
log(message.append("from Receiver"))
}
}
access(all)
resource interface Provider {
access(all)
fun log(_ message: String) {
pre{ message != "" }
}
}
// Valid: Both the default function and the condition would be available.
access(all)
resource interface Vault: Receiver, Provider {}
そのような状況では、デフォルト関数の継承および条件継承に適用されるすべてのルールが適用されます。したがって、Receiver
インターフェースからのデフォルト関数と、Provider
インターフェースからの条件は、継承されたインターフェースで利用可能になります。
Types and event definitions
型およびイベントの定義も、デフォルトの関数と同様に動作します。継承されたインターフェースは、型定義およびイベント定義を上書き(オーバーライド)することができます。
access(all)
contract interface Token {
access(all)
struct Foo {}
}
access(all)
contract interface NonFungibleToken: Token {
access(all)
struct Foo {}
}
access(all)
contract MyToken: NonFungibleToken {
access(all)
fun test() {
let foo: Foo // This will refer to the `NonFungibleToken.Foo`
}
}
ユーザーがスーパーインターフェースToken
からFoo
構造体にアクセスする必要がある場合、完全修飾名を使用してアクセスすることができます。例:let foo: Token.Foo
ただし、インターフェースに対して、同一の名前を持つ2つ以上の継承された 型やイベント定義を持つことは許可されていません。
access(all)
contract interface Token {
access(all)
struct Foo {}
}
access(all)
contract interface Collectible {
access(all)
struct Foo {}
}
// Invalid: Two type definitions with the same name from two interfaces.
access(all)
contract NonFungibleToken: Token, Collectible {
}
デフォルト関数と同様に、異なる継承パスを経由して同じ型/イベント定義が利用できる状況が考えられます。
access(all)
contract interface Logger {
access(all)
struct Foo {}
}
access(all)
contract interface Token: Logger {}
access(all)
contract interface Collectible: Logger {}
// Valid: `Logger.Foo` struct is visible to the `NonFungibleToken` interface via both `Token` and `Collectible`.
access(all)
contract interface NonFungibleToken: Token, Collectible {}
上記の例では、Logger.Foo
型の定義は、NonFungibleToken
インターフェースに対して、Token
およびCollectible
の両方から参照可能です。2つの異なるインターフェースから参照可能であっても、両者は同じ型の定義を参照しています。したがって、上記のコードは有効です。
しかし、チェーンの途中にあるインターフェースの少なくとも1つが型の定義Foo
もオーバーライドしている場合、複数の実装が存在することになり、あいまい性につながるため、コードは無効になります。
access(all)
contract interface Logger {
access(all)
struct Foo {}
}
access(all)
contract interface Token: Logger {
access(all)
struct Foo {}
}
access(all)
contract interface Collectible: Logger {}
// Invalid: The default implementation of the `Foo` struct by the `Logger`
// interface is visible to the `NonFungibleToken` via the `Collectible` interface.
// Another implementation of `Foo` struct is visible to the `NonFungibleToken` via the `Token` interface.
// This creates ambiguity.
access(all)
resource interface NonFungibleToken: Token, Provider {}
Linearizing Conditions
functions with conditionsのセクションで述べたように、事前条件と事後条件が実行される順序を決定するために、関数の条件をリニア化する必要があります。これは、インターフェースをリニア化することで行われ、したがって条件も、深さ優先で重複なしの事前順序でリニア化されます。
例えば、以下のようなインターフェース継承階層を考えてみましょう。
A
/ \
B C
/ \ /
D E
where an edge from A (top) to B (bottom) means A inherits B.
これは、以下に似たCadenceの実装に変換されます。
struct interface A: B, C {
access(all)
fun test() {
pre { print("A") }
}
}
struct interface B: D, E {
access(all)
fun test() {
pre { print("B") }
}
}
struct interface C: E {
access(all)
fun test() {
pre { print("C") }
}
}
struct interface D {
access(all)
fun test() {
pre { print("D") }
}
}
struct interface E {
access(all)
fun test() {
pre { print("E") }
}
}
A
のインターフェースを実装する具体的なタイプは、A
からE
までのリニア化されたすべてのインターフェースを実装することと同等です。
struct Foo: A {
access(all)
fun test() {
pre { print("Foo") }
}
}
線形化されたインターフェースの順序は次のようになります:[A, B, D, E, C]。
すなわち、次のようになるのと同じです。
access(all)
fun test() {
pre { print("Foo") }
}
}
したがって、Foo
のtest
メソッドを呼び出すと、まず[A, B, D, E, C]の事前条件がその順序で呼び出され、最終的に具体的な実装であるFoo
の事前条件が実行されます。
let foo = Foo()
foo.test()
上記の出力結果:
A
B
D
E
C
Foo
同様に、事後条件についても同じインターフェースのリニア化が使用され、事後条件は逆の順序で実行されます。例えば、上記の例のpre
条件を、まったく同じ内容のpost
条件に置き換えると、以下のような出力が得られます。
Foo
C
E
D
B
A
翻訳元->https://cadence-lang.org/docs/language/interfaces