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 lineprint!
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 complex ones. If you try to print complex data type values with these macros, e.g. for debugging purposes, 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 string 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 we want commas or not? Do we 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 of type 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 use the
print!
andprintln!
macros. If you need to print complex data types, especially when debugging, use theDebug
trait described below instead.
Print Debug Traces
Cairo provides the Debug
trait, which can be derived to print the value of variables when debugging. Simply add :?
within the curly brackets {}
placeholders in a print!
or println!
macro string.
This trait is very useful and is implemented by default for basic data types. It can also be simply derived for complex data types using the #[derive(Debug)]
attribute, as long as all types they contain implement it. This eliminates the need to manually implement extra code to print complex data types.
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.
For more details about the Debug
trait and its usage for printing values when debugging, please refer to the Derivable Traits appendix.