Learning Cairo - Data Types (1)
Prerequisite
Install cairo-run.
Data Type
Cairo and Solidity are both statically-typed languages, which means that Cairo’s data types need to be explicitly defined at compile-time and cannot be changed later. For example, if we define a variable a
of type u128
, then any subsequent assignment to a
must be of type u128
:
use debug::PrintTrait;
fn main() {
let mut a: u128 = 1_u128;
a = true;
}
Assigning true
to a variable of type u128
results in a compilation error:
error: Unexpected argument type. Expected: "core::integer::u128", found: "core::bool".
--> main.cairo:4:9
a = true;
^**^
Felt Type
In Cairo, if the type of a variable is not specified, it defaults to a field element
type represented by the keyword felt252
. This is a new concept for Solidity developers because in Solidity, all variables need to have their types explicitly specified. However, due to Cairo’s type inference mechanism, it is possible to omit type specification, and Cairo will automatically set the variable type to felt252
.
fn main() {
let a = 1;
let b: u128 = 1;
}
In the example above, a
is of type felt252
, while b
is of type u128
.
Integer Types
The felt252
type serves as the foundation for all types in Cairo, including integer types. However, it is recommended to use specific integer types instead of felt252
in development because integer types offer more safety checks, clearer type indication, and better support for development and maintenance.
The integer types available in Cairo include the following:
- u8
- u16
- u32
- u64
- u128
- u256
- usize
The integer type names represent the number of bits each type can hold. For example, u8
represents an 8-bit type. Note that usize
currently represents u32
, but this may change in the future. Unlike other types, u256
is not a native 256-bit type but a type composed of two u128
types, which is determined by Cairo’s underlying architecture.
Compared to Cairo, Solidity has many more integer types, ranging from uint8
to uint256
, with each type differing by 8 bits. For example, uint248
and uint40
do not exist in Cairo. Additionally, Cairo currently only supports unsigned integers and does not support negative numbers.
Cairo supports basic arithmetic operations for integers, including addition, subtraction, multiplication, division, and remainder. The rules for these operations are the same as in Solidity:
fn main() {
// addition
let sum = 5_u128 + 10_u128;
// subtraction
let difference = 95_u128 - 4_u128;
// multiplication
let product = 4_u128 * 30_u128;
// division
let quotient = 56_u128 / 32_u128; // result is 1
let quotient = 64_u128 / 32_u128; // result is 2
// remainder
let remainder = 43_u128 % 5_u128; // result is 3
}
Bool Type
Cairo also supports
the bool
type, which can have values of true
or false
.
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
Short String Type
Cairo does not have a native string
type, but it is possible to store a short string type using felt252
. It can hold a maximum of 31 characters, fitting within 252 bits. A short string can be defined as follows:
let my_first_char = 'C';
let my_first_string = 'Hello world';
Note that single quotes are used when defining a short string.
In Cairo, assert
keyword is used for validating parameters, for example:
fn test(a: u128) {
assert(a < 100, 'a is too big');
}
The error message in this case is also a short string, and its length cannot exceed 31 characters. If it exceeds 31 characters, an error will occur:
fn test(a: u128) {
assert(a < 100, 'a is too big xxxxxxxxxxxxxxxxxxx');
}
Error message:
error: The value does not fit within the range of type core::felt252.
--> main.cairo:9:21
assert(a < 100, 'a is too big xxxxxxxxxxxxxxxxxxx');
^********************************^
Error: failed to compile: main.cairo
Type Casting
Cairo supports type conversions between felt252
and integer types. By importing the TryInto
and Into
traits, we can use the try_into
and into
methods for conversions.
Here’s an example:
use traits::TryInto;
use option::OptionTrait;
fn main(){
let my_felt = 255;
let my_u8: u8 = my_felt.try_into().unwrap();
}
In this example, we define a felt252
variable my_felt
with a value of 255. In the next line, we convert it to the u8
type using try_into().unwrap()
. try_into()
returns an Option<T>
type, so we need to use unwrap()
to extract the value.
Compiling and running this code produces no errors. Let’s try assigning it a value of 256:
use traits::TryInto;
use option::OptionTrait;
fn main(){
let my_felt = 256;
let my_u8: u8 = my_felt.try_into().unwrap();
}
This will result in an error:
Run panicked with err values: [29721761890975875353235833581453094220424382983267374]
This error occurs because u8
can only hold values up to 8 bits, which is a maximum of 255. Assigning 256 to it causes an overflow. This demonstrates the purpose of try_into
because we are trying to convert a larger range type to a smaller range type, which may result in overflow. When converting from a smaller range type to a larger range type, we can simply use into
for the conversion:
use traits::Into;
use option::OptionTrait;
fn main(){
let my_u8: u8 = 1_u8;
let my_felt: felt252 = my_u8.into();
}
When using try_into
and into
conversions, the type of the new variable must be explicitly specified:
use traits::TryInto;
use traits::Into;
use option::OptionTrait;
fn main(){
let my_felt =
10;
let my_u8: u8 = my_felt.try_into().unwrap(); // Since a felt252 might not fit in a u8, we need to unwrap the Option<T> type
let my_u16: u16 = my_felt.try_into().unwrap();
let my_u32: u32 = my_felt.try_into().unwrap();
let my_u64: u64 = my_felt.try_into().unwrap();
let my_u128: u128 = my_felt.try_into().unwrap();
let my_u256: u256 = my_felt.into(); // As a felt252 is smaller than a u256, we can use the into() method
let my_usize: usize = my_felt.try_into().unwrap();
let my_felt2: felt252 = my_u8.into();
let my_felt3: felt252 = my_u16.into();
}
Tuple Type
Similar to Python, Cairo also has tuple types, which allow combining multiple pieces of data into a single type. Once defined, the length of a tuple remains fixed and cannot be changed.
fn main() {
let tup: (u32, u64, bool) = (10, 20, true);
}
In the example above, we define a tuple with three elements, where each element can have a different type. We can access individual elements of a tuple using the following syntax:
use debug::PrintTrait;
fn main() {
let tup = (500, 6, true);
let (x, y, z) = tup;
if y == 6 {
'y is six!'.print();
}
}
This operation is called destructuring, and we can also declare multiple variables directly using a tuple:
fn main() {
let (x, y): (felt252, felt252) = (2, 3);
}
Summary
Cairo supports various data types, with felt252
being the foundational type. Integer types are built upon felt252
. Tuple types are also supported, allowing the combination of multiple data elements.
Call out StarkNet Beta Testers!
Reddio is building developer tools for StarkNet to help you accelerate the process to develop StarkNet applications. We are inviting all of StarkNet developers to join our beta testing group, try out brand-new features and tell us what you think.
https://share.hsforms.com/1E88oQkqMSJifUV1CqR_WrQd30xn
Low latency and free Starknet node awaits!
For a limited time, Reddio is offering unrestricted access to its high-speed StarkNet Node, completely free of charge. This is an unparalleled opportunity to experience the fastest connection with the lowest delay. All you need to do is register an account on Reddio at https://dashboard.reddio.com/ and start exploring the limitless possibilities.
You can discover why Reddio claims the fastest connection by reading more here.