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 theprint!
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!
andprintln!
macros use theDisplay
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 theDisplay
trait for your type, or use theDebug
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!
andprintln!
macros. If you need to print complexe data types, especially when debugging, use theDebug
trait described just after instead.
Print Debug Traces
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.