Run-time Types
Types can be represented at run-time.
To create a type value, use the constructor function Type<T>()
, which accepts the static type as a type argument.
This is similar to e.g. T.self
in Swift, T::class
/KClass<T>
in Kotlin, and T.class
/Class<T>
in Java.
For example, to represent the type Int
at run-time:
_10let intType: Type = Type<Int>()
This works for both built-in and user-defined types. For example, to get the type value for a resource:
_10resource Collectible {}_10_10let collectibleType = Type<@Collectible>()_10_10// `collectibleType` has type `Type`
Type values are comparable.
_10_10Type<Int>() == Type<Int>()_10_10Type<Int>() != Type<String>()
The method view fun isSubtype(of: Type): Bool
can be used to compare the run-time types of values.
_10Type<Int>().isSubtype(of: Type<Int>()) // true_10_10Type<Int>().isSubtype(of: Type<String>()) // false_10_10Type<Int>().isSubtype(of: Type<Int?>()) // true
To get the run-time type's fully qualified type identifier, use the let identifier: String
field:
_10let type = Type<Int>()_10type.identifier // is "Int"
_10// in account 0x1_10_10struct Test {}_10_10let type = Type<Test>()_10type.identifier // is "A.0000000000000001.Test"
Getting the Type from a Value
The method view fun getType(): Type
can be used to get the runtime type of a value.
_10let something = "hello"_10_10let type: Type = something.getType()_10// `type` is `Type<String>()`
This method returns the concrete run-time type of the object, not the static type.
_10// Declare a variable named `something` that has the *static* type `AnyResource`_10// and has a resource of type `Collectible`_10//_10let something: @AnyResource <- create Collectible()_10_10// The resource's concrete run-time type is `Collectible`_10//_10let type: Type = something.getType()_10// `type` is `Type<@Collectible>()`
Constructing a Run-time Type
Run-time types can also be constructed from type identifier strings using built-in constructor functions.
_10view fun CompositeType(_ identifier: String): Type?_10view fun InterfaceType(_ identifier: String): Type?_10view fun IntersectionType(types: [String]): Type?
Given a type identifier (or a list of identifiers for interfaces
in the case of IntersectionType
), these functions will look up nominal types and
produce their run-time equivalents. If the provided identifiers do not correspond
to any types, or (in the case of IntersectionType
) the provided combination of
identifiers would not type-check statically, these functions will produce nil
.
_10struct Test: I {}_10struct interface I {}_10let type: Type = CompositeType("A.0000000000000001.Test")_10// `type` is `Type<Test>`_10_10let type2: Type = IntersectionType(_10 restrictions: ["A.0000000000000001.I"]_10)_10// `type2` is `Type<{I}>`
Other built-in functions will construct compound types from other run-types.
_10view fun OptionalType(_ type: Type): Type_10view fun VariableSizedArrayType(_ type: Type): Type_10view fun ConstantSizedArrayType(type: Type, size: Int): Type_10view fun FunctionType(parameters: [Type], return: Type): Type_10// returns `nil` if `key` is not valid dictionary key type_10view fun DictionaryType(key: Type, value: Type): Type?_10// returns `nil` if `type` is not a reference type_10view fun CapabilityType(_ type: Type): Type?_10view fun ReferenceType(entitlements: [String], type: Type): Type?
Asserting the Type of a Value
The method view fun isInstance(_ type: Type): Bool
can be used to check if a value has a certain type,
using the concrete run-time type, and considering subtyping rules,
_19// Declare a variable named `collectible` that has the *static* type `Collectible`_19// and has a resource of type `Collectible`_19//_19let collectible: @Collectible <- create Collectible()_19_19// The resource is an instance of type `Collectible`,_19// because the concrete run-time type is `Collectible`_19//_19collectible.isInstance(Type<@Collectible>()) // is `true`_19_19// The resource is an instance of type `AnyResource`,_19// because the concrete run-time type `Collectible` is a subtype of `AnyResource`_19//_19collectible.isInstance(Type<@AnyResource>()) // is `true`_19_19// The resource is *not* an instance of type `String`,_19// because the concrete run-time type `Collectible` is *not* a subtype of `String`_19//_19collectible.isInstance(Type<String>()) // is `false`
Note that the concrete run-time type of the object is used, not the static type.
_19// Declare a variable named `something` that has the *static* type `AnyResource`_19// and has a resource of type `Collectible`_19//_19let something: @AnyResource <- create Collectible()_19_19// The resource is an instance of type `Collectible`,_19// because the concrete run-time type is `Collectible`_19//_19something.isInstance(Type<@Collectible>()) // is `true`_19_19// The resource is an instance of type `AnyResource`,_19// because the concrete run-time type `Collectible` is a subtype of `AnyResource`_19//_19something.isInstance(Type<@AnyResource>()) // is `true`_19_19// The resource is *not* an instance of type `String`,_19// because the concrete run-time type `Collectible` is *not* a subtype of `String`_19//_19something.isInstance(Type<String>()) // is `false`
For example, this allows implementing a marketplace sale resource:
_66access(all)_66resource SimpleSale {_66_66 /// The resource for sale._66 /// Once the resource is sold, the field becomes `nil`._66 ///_66 access(all)_66 var resourceForSale: @AnyResource?_66_66 /// The price that is wanted for the purchase of the resource._66 ///_66 access(all)_66 let priceForResource: UFix64_66_66 /// The type of currency that is required for the purchase._66 ///_66 access(all)_66 let requiredCurrency: Type_66 access(all)_66 let paymentReceiver: Capability<&{FungibleToken.Receiver}>_66_66 /// `paymentReceiver` is the capability that will be borrowed_66 /// once a valid purchase is made._66 /// It is expected to target a resource that allows depositing the paid amount_66 /// (a vault which has the type in `requiredCurrency`)._66 ///_66 init(_66 resourceForSale: @AnyResource,_66 priceForResource: UFix64,_66 requiredCurrency: Type,_66 paymentReceiver: Capability<&{FungibleToken.Receiver}>_66 ) {_66 self.resourceForSale <- resourceForSale_66 self.priceForResource = priceForResource_66 self.requiredCurrency = requiredCurrency_66 self.paymentReceiver = paymentReceiver_66 }_66_66 /// buyObject allows purchasing the resource for sale by providing_66 /// the required funds._66 /// If the purchase succeeds, the resource for sale is returned._66 /// If the purchase fails, the program aborts._66 ///_66 access(all)_66 fun buyObject(with funds: @FungibleToken.Vault): @AnyResource {_66 pre {_66 // Ensure the resource is still up for sale_66 self.resourceForSale != nil: "The resource has already been sold"_66 // Ensure the paid funds have the right amount_66 funds.balance >= self.priceForResource: "Payment has insufficient amount"_66 // Ensure the paid currency is correct_66 funds.isInstance(self.requiredCurrency): "Incorrect payment currency"_66 }_66_66 // Transfer the paid funds to the payment receiver_66 // by borrowing the payment receiver capability of this sale resource_66 // and depositing the payment into it_66_66 let receiver = self.paymentReceiver.borrow()_66 ?? panic("failed to borrow payment receiver capability")_66_66 receiver.deposit(from: <-funds)_66 let resourceForSale <- self.resourceForSale <- nil_66 return <-resourceForSale_66 }_66}