Learning Cairo - Data Types (1)

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.