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 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 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.
_10fun CompositeType(_ identifier: String): Type?_10fun InterfaceType(_ identifier: String): Type?_10fun RestrictedType(identifier: String?, restrictions: [String]): Type?
Given a type identifier (as well as a list of identifiers for restricting interfaces
in the case of RestrictedType
), 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 RestrictedType
) the provided combination of
identifiers would not type-check statically, these functions will produce nil
.
_10struct Test {}_10struct interface I {}_10let type: Type = CompositeType("A.0000000000000001.Test")_10// `type` is `Type<Test>`_10_10let type2: Type = RestrictedType(_10 identifier: type.identifier,_10 restrictions: ["A.0000000000000001.I"]_10)_10// `type2` is `Type<Test{I}>`
Other built-in functions will construct compound types from other run-types.
_10fun OptionalType(_ type: Type): Type_10fun VariableSizedArrayType(_ type: Type): Type_10fun ConstantSizedArrayType(type: Type, size: Int): Type_10fun FunctionType(parameters: [Type], return: Type): Type_10// returns `nil` if `key` is not valid dictionary key type_10fun DictionaryType(key: Type, value: Type): Type?_10// returns `nil` if `type` is not a reference type_10fun CapabilityType(_ type: Type): Type?_10fun ReferenceType(authorized: bool, type: Type): Type
Asserting the Type of a Value
The method 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:
_67pub resource SimpleSale {_67_67 /// The resource for sale._67 /// Once the resource is sold, the field becomes `nil`._67 ///_67 pub var resourceForSale: @AnyResource?_67_67 /// The price that is wanted for the purchase of the resource._67 ///_67 pub let priceForResource: UFix64_67_67 /// The type of currency that is required for the purchase._67 ///_67 pub let requiredCurrency: Type_67 pub let paymentReceiver: Capability<&{FungibleToken.Receiver}>_67_67 /// `paymentReceiver` is the capability that will be borrowed_67 /// once a valid purchase is made._67 /// It is expected to target a resource that allows depositing the paid amount_67 /// (a vault which has the type in `requiredCurrency`)._67 ///_67 init(_67 resourceForSale: @AnyResource,_67 priceForResource: UFix64,_67 requiredCurrency: Type,_67 paymentReceiver: Capability<&{FungibleToken.Receiver}>_67 ) {_67 self.resourceForSale <- resourceForSale_67 self.priceForResource = priceForResource_67 self.requiredCurrency = requiredCurrency_67 self.paymentReceiver = paymentReceiver_67 }_67_67 destroy() {_67 // When this sale resource is destroyed,_67 // also destroy the resource for sale._67 // Another option could be to transfer it back to the seller._67 destroy self.resourceForSale_67 }_67_67 /// buyObject allows purchasing the resource for sale by providing_67 /// the required funds._67 /// If the purchase succeeds, the resource for sale is returned._67 /// If the purchase fails, the program aborts._67 ///_67 pub fun buyObject(with funds: @FungibleToken.Vault): @AnyResource {_67 pre {_67 // Ensure the resource is still up for sale_67 self.resourceForSale != nil: "The resource has already been sold"_67 // Ensure the paid funds have the right amount_67 funds.balance >= self.priceForResource: "Payment has insufficient amount"_67 // Ensure the paid currency is correct_67 funds.isInstance(self.requiredCurrency): "Incorrect payment currency"_67 }_67_67 // Transfer the paid funds to the payment receiver_67 // by borrowing the payment receiver capability of this sale resource_67 // and depositing the payment into it_67_67 let receiver = self.paymentReceiver.borrow()_67 ?? panic("failed to borrow payment receiver capability")_67_67 receiver.deposit(from: <-funds)_67 let resourceForSale <- self.resourceForSale <- nil_67 return <-resourceForSale_67 }_67}