Cairo Programming Language: An Introduction to Variables and Basics

Cairo Programming Language: An Introduction to Variables and Basics

A Brief Introduction into the Cairo Programming Language Series

Introduction

Cairo is a high-level programming language used for writing provable programs on Starknet. Provable program in the sense that one party can prove to another party that a certain computation was executed correctly without needing the party to re-execute the same program. It is also a Turing complete program which means it can be used to perform any computation that can be described algorithmically. Cairo has the necessary features to express any computable function or algorithm, and manipulation of data in a way that allows for the execution of any algorithm. Cairo is inspired by Rust and has a similar syntax to that of the Rust programming language.

The earlier version of Cairo (Cairo 0) is compiled directly to Casm (Cairo Assembly) which had some faults such as;

  • Only valid statements can be proven in Cairo. It was impossible to prove an invalid code like assert 0 = 1 , this is a valid Cairo instruction that cannot be proven, as it translates to polynomial constraints that are not satisfiable.
  • The sequencer is not being compensated after work; sequencers in L2 are meant to take their execution fee after a transaction has been executed and also add the transaction into the block even if the transaction fails during execution. Still, it was not so with Cairo 0 which could also lead to a DOS attack on the sequencer. The sequencer can only predict that a transaction will fail if it does the work.

  • Distinguishing between an invalid transaction and the transactions that the sequencer does not accept would be impossible.

An extra layer between the user code (Cairo) and what is proven (CASM) had to be added to avoid these. The extra layer added is called Sierra (Safe Intermediate Representation); this layer allows us to ensure that all transactions are eventually provable. So, the current version of Cairo is first converted to Sierra, then compiled to a subset of Casm known as safe Casm. This guarantees that the user code is always provable.

Also, Cairo uses an immutable memory model. This means that after a value has been written into a memory cell it cannot be overwritten but can only be read from. Cairo made provision for developers to work around this constraint but it does not fully simulate mutability. In this article, we will be going through the introductory phase of this series. In the subsequent articles on Cairo, we would dive deep into more concepts in the Cairo programming language.

Installation of Cairo

You can install Cairo by just downloading Scarb. Scarb comes with the Cairo compiler and Cairo language server bundled together. Scarb is also a Cairo package manager; it helps build Cairo code (either pure cairo or Starknet contract). As a package manager, it helps handle multiple dependencies and a robust codebase.

To download scarb you can look into the scarb documentation here.

Here are some of the Scarb commands you will need to get things running for your Cairo project;

  • scarb new : it is used to spin up a new Cairo project.

  • scarb build : it is used to build your cairo code to generate the compiled Sierra code.

  • scarb test : it is used to run tests.

  • scarb run : it is used to call scripts in the Scarb.toml .

To write a Starknet contract you will need other tools such as Katana to create a local test node, Starknet Foundry to write the test for the contract, and Starkli as a CLI tool for interacting with the Starknet network. For the scope of this article, installing scarb will do. In the next section, we will be looking into variables in Cairo.

Variables in Cairo

Variable is a common concept to every programming language; It is a container that allows one to store and manage values in a computer's memory. However, in Cairo, the concept of variables is a bit different. Cairo uses an immutable memory model, which means that once a memory cell has been written to it can not be overwritten but be read from only.

So, breaking this down it means that whenever you assign a specific value to a variable name in Cairo, you will not be able to assign a new value to it, this makes your code more secure. You do not have to worry about any errors due to value changes in the code. Though Cairo works with an immutable memory model there is a feature that comes with the program that gives the developer the privilege to make the variable mutable if the developer chooses to.

Below is a code example;

use debug::PrintTrait;
fn main() {
    let x = 2;
    x.print();
    x = 13;
    x.print();
}

The following is the error that comes out of the terminal after running scarb cairo-run

The error in the image above shows that you can not assign an immutable variable.

You cannot just assign another value to a variable that has a value already in Cairo unless you specify that the specific variable should be mutable. The way it works is that you add the mut keyword before the variable name.

Below is an example that you can find;

use debug::PrintTrait;
fn main() {
    let mut x = 2;
    x.print();
    x = 13;
    x.print();
}

This is what your terminal should look like after running the scarb cairo-run command;

This shows that your code compiles successfully.

Under the hood, Cairo does not change what has already been assigned to a memory cell, as we said earlier it is immutable but it can change the memory address where the variable points to. Variable mutation is implemented as syntactic sugar, it translates the mutation operations into steps similar to variable shadowing. The key distinction is that, at the Cairo level, the variable is not redeclared, maintaining its type immutability.

Constant in Cairo

The concept of constant is similar to that of variable in Cairo but with a few differences. Unlike variable in Cairo which has a mutable option, Constant in Cairo does not have a mutable option. Constant by default is always immutable. Also, you cannot use the mut and the let keyword in a constant statement. When declaring a constant there are a few things to do; Firstly, add the const keyword to the variable, and secondly add the data type of the variable in front of the variable.

Here is an example;

const ONE_YEAR_SUBSCRIPTION_PLAN: u32 = 365; // Number of days

The above example describes what a constant variable looks like.

The naming convention in Cairo for constants is that the words must be in uppercase with an underscore between the words. Also, looking at the example you can see how comments are written in Cairo language with the double forward slash // syntax.

Constants in Cairo are set to a specific, fixed value that is directly written in the code and does not change during the execution of the program. The values can be determined at compile time and are not computed or derived during runtime.

It is also good to note some other properties and use cases of constant in Cairo;

  1. Declared in Global Scope: Constants are typically declared in global scope, meaning they are accessible from any part of the code. This makes them useful for values that many parts of the code need to know about without having to pass the values explicitly.

  2. Useful for Shared Information: Constants are valuable for values that multiple parts of the program might need to know about. For example, constants can represent universal values or parameters that are shared across different modules or components.

  3. Valid for the Entire Program Runtime: Constants retain their values for the entire duration of the program within the scope in which they were declared. This provides a consistent and unchanging reference for the specified value throughout the program’s execution.

  4. Application in Application Domain: Constants are particularly useful for values related to the application domain, such as rules, limits, or properties that remain constant across the program’s execution. Examples are interest rate, maximum loan amount, and exam passing score.

Constants enhance code readability and maintainability by providing named, unchanging values for widely used parameters or settings. They centralize important values, reducing the risk of errors, improving consistency, and making it easier to update or modify shared values across the codebase.

Shadowing in Cairo

What shadowing does is that it helps you re-assign another value to an already initialized variable without using the mut keyword which also allows you to use another data type on the variable different from the one previously in use. We would break everything down;

When you already have a value assigned to a variable, the shadowing feature allows you to redeclare the variable and re-initialize the variable by using the let keyword. When this happens the compiler picks the value of the most recent one and makes use of that whenever the variable is being used anywhere else in the code. This continues until that variable is overshadowed or has gone past the scope in which the shadowing occurs. It is also good to note that the variables are still immutable after they have gone through the shadowing process.

When a variable undergoes the shadowing process it is also equivalent to making a variable mutable in Cairo low-level. Regardless, there are still some differences between making a variable mutable and shadowing a variable; You cannot change the variable data type of a variable by just making it mutable. Redeclaring a variable by adding the let keyword can allow you to add a new data type cause at that point you declare a new variable but with the same name.

Here is an example;

use debug::PrintTrait;

fn main() {
    let  count: felt252 = 'hello';
    'Initial count value is:'.print();
    count.print();

    // Shadowing with a different type
    let count: u8 = 5;
    'Shadowed count with integer:'.print();
    count.print();

    // Inner scope with its own count
    {
        let count: u8 = count * 2; // Shadows the outer 'count' in this inner scope
        'Inner scope count value is:'.print();
        count.print();
    }

    // 'count' in the outer scope remains unchanged
    'Outer scope count value is:'.print();
    count.print();
}

This is what your terminal should look like after running scarb cairo-run

The above code describes many of the things we talked about like changing data type and assigning another value to a variable. I know that you are seeing some things we have not spoken about like the data types. We will talk about some of the other common cairo programming concepts in this series.

Conclusion

Variables lie at the heart of any programming language, and Cairo's approach is both unique and powerful. Embracing the concept of immutability in memory while providing the flexibility of variable mutation, Cairo strikes a delicate balance. Through our exploration of Cairo's variables, we've witnessed how syntactic sugar and the Sierra layer contribute to robust, provable computations.