Cairo Language Documentation

repository·main·Indexed 23 days ago

https://github.com/starkware-libs/cairo

A Turing-complete language for creating provable programs for general computation, featuring a high-performance Rust compiler and extensive use in Starknet contract development. Documentation covers the Cairo development environment, compilation to Sierra and CASM, the cairo-run and cairo-test binaries, and language specifications including enums, the trait and impl system, and variable management using let and assign syntax.

Tokens
97.6K
Snippets
252
Records
551
Agent score
82%

What's inside Cairo

  1. Overview of Cairo data types

    main

    Cairo is a strongly-typed language. Data is categorized into several groups:

    • Basic data types: Includes Boolean, numeric types (felt252, Integers), Never, Unit, Error, and Snapshot.
    • Sequence types: Includes Tuple, Array, Felt252Dict, Slices and Span, and String types.
    • Pointer types: Includes Box and Nullable.
    • User-defined types: Allows creating custom Struct and Enum types.
    • Helper macros: Such as the Derive macro to assist with type implementation.
  2. What is the Snapshot type in Cairo?

    main

    In Cairo's linear type system, values are moved by default and cannot be used multiple times unless they implement the Copy trait. The snapshot type, denoted as @T, creates an immutable view of a value without moving or copying it.

    Key characteristics of snapshots:

    • Always copyable: @T implements Copy even if T does not.
    • Always droppable: @T implements Drop even if T does not.
    • Immutable: Snapshots provide read-only access; there is no such thing as a mutable snapshot.
    • Value-based: Unlike Rust's immutable references (&T) which are pointers, snapshots represent the original element including its full size and structure.
  3. What is a Span<T> and how does it differ from an Array<T>?

    main

    A Span<T> is an immutable, read-only view into a contiguous sequence of elements. It provides lightweight, zero-cost access to data without taking ownership.

    Key differences between Array<T> and Span<T>:

    • Mutability: Array<T> is mutable (can append); Span<T> is immutable (read-only).
    • Ownership: Array<T> owns the data; Span<T> references data via a snapshot.
    • Memory: Array<T> allocates and owns memory; Span<T> is a zero-cost reference.
    • Behavior: Operations like pop_front() on a span do not modify the underlying array; they merely advance the view position. Spans capture a snapshot of the array at the time of creation, so subsequent modifications to the original array do not affect existing spans.
    // The internal structure of Span<T>
    pub struct Span<T> {
        pub(crate) snapshot: @Array<T>,
    }
  4. Define functions in Cairo

    main

    A function is a unit of code that performs logic and is defined using the fn keyword. A function consists of a signature and a body. The signature defines the name, generic parameters, parameters, and return type. The body is enclosed in curly braces {...} and can contain statements followed by an optional 'tail expression' which serves as the return value.

    fn main() {
        let x = 3;
    }
    
    fn inc(x: u32) -> u32 {
        x + 1
    }
  5. Apply visibility to different Cairo constructs

    main

    Visibility can be applied to various language constructs as follows:

    • Modules: Use mod for a private module and pub mod to expose the module and its public members.
    • Functions: Both fn and extern fn can be marked pub or pub(crate). Otherwise, they are private.
    • Types:
      • struct and enum visibility is set on the type declaration itself.
      • Struct fields are private by default; use pub to make them public.
      • Enum variants automatically inherit the visibility of the enum (you cannot set visibility per variant).
    • Traits and Implementations:
      • pub trait exposes the trait. Methods within the trait definition do not have separate visibility modifiers.
      • pub impl ... is used to expose implementations.
    • Re-exports: Use pub use to re-export items through a public path.
  6. Manage gas consumption and resource limits

    main

    When executing on Starknet, Cairo programs consume gas based on three primary factors:

    • Execution steps: The number of Sierra/CASM instructions executed.
    • Builtin usage: The use of cryptographic and arithmetic operations.
    • Memory allocations: The amount of memory allocated during execution.

    To prevent unexpected reverts, the Sierra compiler includes gas metering, which allows programs to check available gas before performing expensive operations. If a program exceeds its allocated gas limit, execution reverts.

  7. Use expression and reference arguments in function calls

    main

    Cairo supports two types of arguments in function calls:

    1. Expression arguments: Passed to regular parameters. Any expression can be used as long as its type matches the parameter type.
    2. Reference arguments: Passed to reference parameters using the ref lvalue syntax. The lvalue is passed to the function and reassigned when the function returns, similar to an assignment statement.

    Argument evaluation order: Arguments are evaluated from left to right, starting with all expression arguments first, followed by all reference arguments. This order is critical because expression arguments might change the value of a reference argument before it is evaluated.

    fn main() {
        let x = 3;
        let mut y = A { z: 5 };
        foo(x, ref y.z);
    }
  8. Use Impl aliases to rename or re-export implementations

    main

    Impl aliases provide alternative names for existing implementations (impls). They do not create new implementations; they simply refer to an existing one, potentially with concrete generic arguments applied.

    Use cases include:

    • Providing shorter or more descriptive names for complex implementations.
    • Re-exporting implementations from other modules.
    • Fixing specific generic arguments of an implementation while leaving others generic.

    Syntax:

    impl ImplAliasName<GenericParams> = path::to::Impl<GenericArgs>;
    • ImplAliasName: The new name for the implementation.
    • GenericParams: (Optional) Generic parameters introduced by the alias.
    • path::to::Impl: The path to the existing implementation or another alias.
    • GenericArgs: (Optional) Concrete generic arguments passed to the underlying implementation.
    trait Pow<T> {
        fn pow(base: T, exp: u32) -> T;
    }
    
    impl AnyAlgebraPow<T, impl AlgImpl: Algebra<T>> of Pow<T> {
        fn pow(base: T, exp: u32) -> T {
            // Implementation details.
            base
        }
    }
    
    // Impl alias for Pow of felt252.
    impl FeltPow = AnyAlgebraPow<felt252, FeltAlgebra>;
    
    fn main() {
        // Call through the trait name.
        let x = Pow::pow(5, 3);
    
        // Call through the impl alias name.
        let y = FeltPow::pow(5, 3);
    }
  9. Define and use Tuple types in Cairo

    main

    A tuple type is a parenthesized, comma-separated list of types. The number of elements in the list determines the arity of the tuple (e.g., a 2-ary tuple has 2 fields).

    Key Rules:

    • 1-ary Tuples: To distinguish a 1-ary tuple from a simple parenthesized type, you must include a trailing comma after the element type (e.g., (bool,)).
    • Unit Type: A tuple with no fields () is referred to as the Unit type.

    Usage:

    • Construction: Use tuple expressions to create values of a tuple type.
    • Access: Access individual elements by index using member access syntax (e.g., t.0, t.1).
    • Deconstruction: Use patterns to deconstruct tuples into their constituent parts.
    () // Unit type.
    (bool,)
    (u32, u256)
    (felt252, u16, Option<u8>)
  10. Use Destructors for non-droppable values

    main

    Some values (like Dict) cannot be dropped. To handle these, you can implement or derive the Destruct trait. When a value implementing Destruct goes out of scope, the .destruct() method is called automatically to clean it up.

    Requirements:

    • The derived implementation requires all fields to implement Destruct.
    • Manual implementations of Destruct must be nopanic, as destructors may be called during a panic event.
    use core::dict::Felt252Dict;
    
    #[derive(Destruct)]
    struct A {
        d: Felt252Dict<u32>
    }
    
    fn main() {
        let _a = A { d: Default::default() };
        // No error, _a will be destructed.
    }
  11. Understand the ByteArray type in Cairo

    main

    In Cairo, the primary string type is ByteArray, which supports dynamic strings of any length.

    Important Distinction: Do not confuse ByteArray with "short strings" (single-quoted literals like 'hello'). Short strings are actually numeric types (like felt252 or u128) with ASCII encoding and are not part of the ByteArray string type system.

    ByteArray is implemented as a struct that packs bytes into 31-byte chunks for efficiency, storing any remaining bytes in a pending_word.

    let s: ByteArray = "Hello, world!";
    let empty: ByteArray = "";
  12. Define custom types using structs

    main

    A struct is a collection of named fields (members) used to define custom user types. Members can be any defined type, including core types (like u16, u32), other structs, or enums. Use the struct keyword to define them.

    struct Tree {
        height: u16,
        number_of_leaves: u32,
    }
    
    struct Forest {
        number_of_trees: u32,
        highest_tree: Tree,
        lowest_tree: Tree,
    }