Printing

When writing a program, it is quite common to print some data to the console, either for the normal process of the program or for debugging purpose. In this chapter, we describe the options you have to print simple and complex data types.

Printing Standard Data Types

Cairo provides two macros to print standard data types:

  • println! which prints on a new line,
  • print! with inline printing.

Both take a ByteArray string as first parameter (See Data Types) which can be a simple string to print a message or a string with placeholders to format the way values are printed.

There are two ways to use these placeholders and both can be mixed:

  • empty curly brackets {} are replaced by values given as parameters to the print! macro, in the same order.
  • curly brackets with variable names are directly replaced by the variable value.

Here are some examples:

fn main() {
    let a = 10;
    let b = 20;
    let c = 30;

    println!("Hello world!");
    println!("{} {} {}", a, b, c); // 10 20 30
    println!("{c} {a} {}", b); // 30 10 20
}

print! and println! macros use the Display trait under the hood, and are therefore used to print the value of types that implement it. This is the case for basic data types, but not for more complexe ones. If you try to print complex data types values with these macros, e.g., for debugging purpose, you will get an error. In that case, you can either manually implement the Display trait for your type, or use the Debug trait (see below).

Formatting

Cairo also provides a useful macro to handle strings formatting: format!. This macro works like println!, but instead of printing the output to the screen, it returns a ByteArray with the contents. In the following example, we perform string concatenation using either the + operator or the format! macro. The version of the code using format! is much easier to read, and the code generated by the format! macro uses snapshots so that this call doesn’t take ownership of any of its parameters.

fn main() {
    let s1: ByteArray = "tic";
    let s2: ByteArray = "tac";
    let s3: ByteArray = "toe";
    let s = s1 + "-" + s2 + "-" + s3;
    // using + operator consumes the strings, so they can't be used again!

    let s1: ByteArray = "tic";
    let s2: ByteArray = "tac";
    let s3: ByteArray = "toe";
    let s = format!("{s1}-{s2}-{s3}"); // s1, s2, s3 are not consumed by format!
    // or
    let s = format!("{}-{}-{}", s1, s2, s3);

    println!("{}", s);
}

Printing Custom Data Types

As previously explained, if you try to print the value of a custom data type with print! or println! macros, you'll get an error telling you that the Display trait is not implemented for your custom type:

error: Trait has no implementation in context: core::fmt::Display::<package_name::struct_name>

The println! macro can do many kinds of formatting, and by default, the curly brackets tell println! to use formatting known as Display: output intended for direct end user consumption. The primitive types we’ve seen so far implement Display by default because there’s only one way you’d want to show a 1 or any other primitive type to a user. But with structs, the way println! should format the output is less clear because there are more display possibilities: Do you want commas or not? Do you want to print the curly brackets? Should all the fields be shown? Due to this ambiguity, Cairo doesn’t try to guess what we want, and structs don’t have a provided implementation of Display to use with println! and the {} placeholder.

Here is the Display trait to implement:

trait Display<T> {
    fn fmt(self: @T, ref f: Formatter) -> Result<(), Error>;
}

The second parameter f is a Formatter, which is just a struct containing a ByteArray, representing the pending result of formatting:

#[derive(Default, Drop)]
pub struct Formatter {
    /// The pending result of formatting.
    pub buffer: ByteArray,
}

Knowing this, here is an example of how to implement the Display trait for a custom Point struct:

use core::fmt::{Display, Formatter, Error};

#[derive(Copy, Drop)]
struct Point {
    x: u8,
    y: u8
}

impl PointDisplay of Display<Point> {
    fn fmt(self: @Point, ref f: Formatter) -> Result<(), Error> {
        let str: ByteArray = format!("Point ({}, {})", *self.x, *self.y);
        f.buffer.append(@str);
        Result::Ok(())
    }
}

fn main() {
    let p = Point { x: 1, y: 3 };
    println!("{}", p); // Point: (1, 3)
}

Cairo also provides the write! and writeln! macros to write formatted strings in a formatter. Here is a short example using write! macro to concatenate multiple strings on the same line and then print the result:

use core::fmt::Formatter;

fn main() {
    let mut formatter: Formatter = Default::default();
    let a = 10;
    let b = 20;
    write!(formatter, "hello");
    write!(formatter, "world");
    write!(formatter, " {a} {b}");

    println!("{}", formatter.buffer); // helloworld 10 20
}

It is also possible to implement the Display trait for the Point struct using these macros, as shown here:

use core::fmt::{Display, Formatter, Error};

#[derive(Copy, Drop)]
struct Point {
    x: u8,
    y: u8
}

impl PointDisplay of Display<Point> {
    fn fmt(self: @Point, ref f: Formatter) -> Result<(), Error> {
        let x = *self.x;
        let y = *self.y;

        writeln!(f, "Point ({x}, {y})")
    }
}

fn main() {
    let p = Point { x: 1, y: 3 };
    println!("{}", p); // Point: (1, 3)
}

Printing complex data types this way might not be ideal as it requires additional steps to allow the use of print! and println! macros. If you need to print complexe data types, especially when debugging, use the Debug trait described just after instead.

Cairo provides the derivable trait Debug to print the value of variables when debugging. Simply add :? within curly brackets {} placeholders in a print! or println! macro string input.

This trait is very useful and is implemented by default for basic data types. It can also be simply derived on complex data types using the derive(Debug) attribute, as long as all types they contain implement it. It allows to get rid of manually implementing extra-code to print complex data types values.

Note that assert_xx! macros used in tests require the provided values to implement the Debug trait, as they also print the result in case of assertion failure.

Please refer to the Derivable Traits appendix for more detail about the Debug trait and its usage for printing value when debugging.