The as
operator
The as
operator is used to make a static type cast, i.e. it can be used when the type checker can guarantee that the value has the given type.
For example, the number literal 1
is inferred to have the type Int
by default.
If however it should be considered an UInt8
, the static cast 1 as UInt8
can be performed.
The type checker is able to check that the constant value 1 fits in the type UInt8
, which has a range of 0 to 255.
However, consider the following program:
fun intToUInt8(_ value: Int): UInt8 {
return value as UInt8
}
This program is not valid, because the type checker cannot guarantee that all possible values for variable value
(any integer!) will fit in the range of UInt8
.
Let us now consider fixed-point numbers. A fixed-point literal like 1.2
is inferred to have the type UFix64
.
If however it should be considered a Fix64
, the static cast 1.2 as Fix64
can be performed.
The type checker is able to check that the constant value 1.2 fits in the type Fix64
.
However, a cast like -1.0 as UFix64
will not type-check, because the type checker is able to determine that -1.0 is out of range for type UFix64
, which has a minimum of 0.0.
Similarly, consider the following program which uses resource interfaces and resources:
resource interface NFT {}
resource Kitty: NFT {}
let nft <- create Kitty()
Here, thee variable nft
is inferred to have type @Kitty
.
In some cases it might make sense to declare the variable with a less specific type, for example to prevent certain functions from being called accidentally.
So in the example, the static cast create Kitty() as @{NFT}
can be performed.
The type checker is able to check that the type of create Kitty()
, @Kitty
is a subtype of @{NFT}
, a kitty is definitely always an NFT.
However, the opposite is not true: a static type cast from @{NFT}
to @Kitty
is not valid, because not all NFTs are kitties.
Type conversion functions
There are cases where it is necessary to convert a value from one type to another.
For example, a function may take some value of type Int
and may need to convert it to another integer type, like UInt8
, or a fixed-point type like UFix64
.
The type checker can not guarantee that all integer values of type Int
can be converted to UInt8
or UFix64
.
For this purpose case Cadence offers number conversion functions for all number types.
For example, to convert any number to type UInt8
, use the type conversion function UInt8
.
These type conversion functions accept any number value, i.e. integer or fixed-point value, and attempt to convert the value into the target type.
If the given value is not convertible, the function aborts the program!
For example, the function intToUInt8
above could be written like so:
fun intToUInt8(_ value: Int): UInt8 {
return UInt8(value)
}
This program is valid and the type checker will accept it: the value passed to the function UInt8
has type Int
, which is a number.
However, not all calls to intToUInt8
will succeed, as not all values of type Int
can be converted at run-time to type UInt8
,
e.g. negative values (the minimum of UInt8
is 0).
Conclusion
Prefer using the static type cast when possible, because it is more efficient, as no conversion needs to be performed when the program is run.
For example, prefer 1 as UInt8
over UInt8(1)
, as for number literals a static type cast is possible.
(A future version of Cadence could optimize the latter case, the type conversion function call, when it can, but this optimization does currently not exist.)
Use a type conversion function when you want to convert between different types, at run-time.