The Cairo programming language is static and requires that the types of all variables be specified during the compilation process. In Cairo, data types are divided into specific groups.


Scalar Types


A scalar type represents a single value. Cairo has three main scalar types: felt, integer, and boolean. Let's take a look at how these types work.


Felt252


In Cairo, if you don't specify the type of a variable or argument, its type defaults to a field element, represented by the keyword felt252. In the context of Cairo, when we say “a field element” we mean an integer in the range 0 <= x < P, where P is a very large prime number currently equal to P = 2^{251} + 17 * 2^{192}+1. When adding, subtracting, or multiplying, if the result falls outside the specified range of the prime number, an overflow occurs, and an appropriate multiple of P is added or subtracted to bring the result back within the range (i.e., the result is computed modulo P).


The most important difference between integers and field elements is division: Division of field elements (and therefore division in Cairo) is unlike regular CPU division, where integer division x / y is defined as [x/y] where the integer part of the quotient is returned (so you get 7 / 3 = 2) and it may or may not satisfy the equation (x / y) * y == x, depending on the divisibility of x by y.


fn main() {
  // max value of felt252
  let x: felt252 = 3618502788666131213697322783095070105623107215331596699973092056135872020480;
  let y: felt252 = 1;
  assert(x + y == 0, 'P == 0 (mod P)');
}



Since the Felt252 type is the default data type, there is no need to specify the type of the variable. Variables that do not specify a type are automatically defined as type felt252.

fn main() {
  // max value of felt252
  let x = 3618502788666131213697322783095070105623107215331596699973092056135872020480;
  let y = 1;
  assert(x + y == 0, 'P == 0 (mod P)');
}


Integer


Even though Felt252 is the base type for all types, programmers are advised to use integer types whenever possible. This is because integer types provide extra protection against potential vulnerabilities in the code.


The Integer type declaration specifies the number of bits the programmer can use to store the integer. The integer types built into Cairo are shown in the table below. You can use any of these types to specify the type of an integer value.



Length  --   Unsigned

8-bit        --    u8
16-bit      --    u16
32-bit      --    u32
64-bit      --    u64
128-bit    --    u128
256-bit    --    u256
32-bit      --    usize



Each type has an explicit size. Since variables are unsigned, they cannot contain a negative number. This will cause the program to crash:


fn u8_sub(x: u8, y: u8) -> u8 {
  x - y
}

fn main() {
  u8_sub(1, 3);
}



you will get an error output like this


Run panicked with [608642109794502019480482122260311927 ('u8_sub Overflow'), ].



Numerical Operations


Cairo supports basic mathematical operations for all integer types: addition, subtraction, multiplication, division, and leftover.


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; 
  let quotient = 64_u128 / 32_u128;

  // remainder
  let remainder = 43_u128 % 5_u128;-
}


You can access the list of all operators supported by Cairo from the link below.


https://book.cairo-lang.org/appendix-02-operators-and-symbols.html#operators


Boolean


In Cairo, as in many other programming languages, the Boolean type has two possible values: true and false. In Cairo, the Boolean type is specified as bool.


fn main() {
  let t = true;

  let f: bool = false; 
}


String


In Cairo, a string type is a collection of characters stored in felt252. A string expression can be up to 31 characters long.


use debug::PrintTrait;
fn main() {
  let x = 'Cairo is awesome';
  x.print();
  let c = 'A';
  c.print();
}


Type Conversions


In Cairo, you can use the try_into and into methods provided by the TryInto and Into features to convert one type to another. The try_into method allows safe type conversion in cases where the target type may not match the source value. try_into returns the type Option to access the new value, and the unwrap() method is used to unwrap that value.


fn main() {
    let my_felt252 = 10;
    // Since a felt252 might not fit in a u8, we need to unwrap the Option<T> type
    let my_u8: u8 = my_felt252.try_into().unwrap();
    let my_u16: u16 = my_u8.into();
    let my_u32: u32 = my_u16.into();
    let my_u64: u64 = my_u32.into();
    let my_u128: u128 = my_u64.into();
    // As a felt252 is smaller than a u256, we can use the into() method
    let my_u256: u256 = my_felt252.into();
    let my_usize: usize = my_felt252.try_into().unwrap();
    let my_other_felt252: felt252 = my_u8.into();
    let my_third_felt252: felt252 = my_u16.into();
}



Tuple Type


A tuple is a generic way of grouping a set of values of various types into a single composite type. Tuples have a fixed length: once declared, they cannot grow or shrink in size.


We can create a tuple by writing a comma-separated list of values in parentheses. Different values in the tuple can have different types.


use debug::PrintTrait;
fn main() {
    let tup = (500, 6, true);

    let (x, y, z) = tup;

    if y == 6 {
        'y is six!'.print();
    }
}



Here is an example that calculates the volume of a rectangular prism whose side lengths we define as tuples:


use debug::PrintTrait;

fn volume(sides: (u64, u64, u64)) -> u64 {
  let (x, y, z) = sides;
  x * y * z
}

fn main() {
  let rectangular_prism = (20, 30, 40); // Width x Length x Depth
  let v = volume(rectangular_prism);
  v.print(); 
}



Full Code From the tutorial


use core::debug::PrintTrait;
use core::traits::Into;
use core::option::OptionTrait;
use core::traits::TryInto;
//scalar: felts integers and booleans
// felt: felt252 field element (x / y) * y == x
// u8 (0 - 2^8 - 1)  u16 (0 - 2^16 - 1) u32 u64 u128 
// u256{low: u128, high: u128}   usize = u32


fn sub_u8s(x: u8, y: u8) -> u8 {
    x - y
}
fn add_u8s(x: u8, y: u8) -> u8 {
    x + y
}
fn main() {
    // overflows
    // sub_u8s(1, 3);
    // add_u8s(1, 255);


    let sum = 5_u128 + 10_u128;
    let difference = 95_u128 - 4_u128;
    let product = 4_u128 * 30_u128;


    let quotient = 56_u128 / 32_u128; // result is 1 
    let quotient = 64_u128 / 32_u128; // result is 2


    let remainder = 43_u128 % 5_u128;


    let t = true;


    let f: bool = false;


    let my_first_char = 'c';
    let my_first_string = 'Hello World';


    let my_felt252 = 10;


    let my_u8: u8 = my_felt252.try_into().unwrap();
    let my_u16: u16 = my_u8.into();
    let my_u32: u32 = my_u16.into();


    let my_u256: u256 = my_felt252.into();


    let tup: (u32, u64, bool) = (10, 20, true);


    let (x, y, z) = tup;


    if (z) {
        'hey'.print();
    }
}