Starklings5: Functions in Cairo
We’re continuing our journey with Starklings Cairo, and this time, we’re diving into functions. Functions are a fundamental part of any programming language, and Cairo is no different. They allow us to encapsulate logic, make our code reusable, and keep it organized.
If you’re just joining us, feel free to check out our previous posts on Variables in Cairo , Primitive Types in Cairo.
functions1.cairo
In this exercise, we’re tasked with defining and using a simple function in Cairo. Here’s the starting code:
// functions1.cairo
// Execute `starklings hint functions1` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
fn main() {
call_me();
}
The main
function attempts to call another function named call_me
, but there's a problem—the call_me
function hasn't been defined yet. Let's take a look at the error message:
functions1.cairo
Errors
🟡 Running exercises/functions/functions1.cairo exercise...
Compiling exercise_crate v0.1.0 (/Users/desmo/repos/starklings-cairo1/runner-crate/Scarb.toml)
error: Function not found.
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo:7:5
call_me();
^*****^
could not compile `exercise_crate` due to previous error
⚠️ Failed to run exercises/functions/functions1.cairo! Please try again.
The error clearly indicates that the compiler can’t find the call_me
function. This makes sense because we haven't defined it yet. Let's fix that by defining the function.
functions1.cairo
Solution
To solve this, we need to define the call_me
function before it's used in main
. Here's the updated code:
fn call_me() {
println!("ring, ring");
}
fn main() {
call_me();
}
Explanation
- Defining the Function:
- We define a function named
call_me
using thefn
keyword. This function doesn't take any parameters and doesn't return a value. - Inside the function, we use
println!("ring, ring");
to print a simple message to the console. This is just a placeholder for any functionality you might want to add later.
2. Calling the Function:
- In the
main
function, we callcall_me();
, which triggers the execution of thecall_me
function. - This structure is common in programming, where the
main
function acts as the entry point for the program and other functions are called from there.
functions2.cairo
In the previous exercise, we learned how to define a simple function and call it within our main
function. Now, we’re taking it a step further by introducing parameters to our functions. Parameters allow us to pass data into a function, making it more dynamic and reusable. Let’s dive into this exercise.
// functions2.cairo
// Execute `starklings hint functions2` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
fn main() {
call_me(3);
}
fn call_me(num:) {
println!("num is {}", num);
}
functions2.cairo
Errors
As usual, let’s start by examining the errors that occur when we try to compile this code.
Running exercises/functions/functions2.cairo exercise...
Compiling exercise_crate v0.1.0 (/Users/desmo/repos/starklings-cairo1/runner-crate/Scarb.toml)
error: Missing tokens. Expected a type expression.
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo:10:16
fn call_me(num:) {
^
error: Unknown type.
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo:10:16
fn call_me(num:) {
^
error: Type annotations needed. Failed to infer ?3
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo[println_macro][writeln_macro]:4:22
match core::fmt::Display::fmt(__write_macro_arg0__, ref __formatter_for_print_macros__) {
^*****^
could not compile `exercise_crate` due to previous error
⚠️ Failed to run exercises/functions/functions2.cairo! Please try again.
The errors indicate that the call_me
function is missing a type annotation for its num
parameter. In Cairo, as in many statically-typed languages, every parameter must have an explicit type. Let’s fix that by adding the necessary type annotation.
functions2.cairo
Solution
Here’s how we can solve this issue:
fn main() {
call_me(3);
}
fn call_me(num: u8) {
println!("num is {}", num);
}
Explanation
- Type Annotation:
- In the
call_me
function, we add a type annotation to thenum
parameter, specifying that it is of typeu8
(an unsigned 8-bit integer). This tells the compiler exactly what type of datanum
should hold.
2. Printing with println!
:
- The
println!
macro is used to print the value ofnum
to the console. The{}
in the string is a placeholder that gets replaced by the value ofnum
at runtime.
Note on i32
in Cairo
You might have tried using an i32 (as it’s the default type in Rust) and noticed that the code didn’t compile. But why can’t we use an i32 type in Cairo as we would in Rust? Here’s what happens if we try:
🟡 Running exercises/functions/functions2.cairo exercise...
Compiling exercise_crate v0.1.0 (/Users/desmo/repos/starklings-cairo1/runner-crate/Scarb.toml)
error: Trait has no implementation in context: core::fmt::Display::<core::integer::i32>
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo[println_macro][writeln_macro]:4:31
match core::fmt::Display::fmt(__write_macro_arg0__, ref __formatter_for_print_macros__) {
^*^
could not compile `exercise_crate` due to previous error
This error occurs because Cairo’s standard library doesn’t yet have an implementation for the Display
trait for the i32
type in the println!
macro. In Rust, this wouldn’t be an issue because i32
is fully supported and can be easily formatted using println!
.
functions3.cairo
In the previous exercises, we explored how to define functions with and without parameters. Now, let’s tackle a situation where our function requires a parameter, but the function call is missing it. This is a common mistake that helps us understand the importance of correctly matching function signatures with their calls.
// functions3.cairo
// Execute `starklings hint functions3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
fn main() {
call_me();
}
fn call_me(num: u64) {
println!("num is {}", num);
}
functions3.cairo
Errors
Let’s start by examining the error that occurs when we try to compile this code:
🟡 Running exercises/functions/functions3.cairo exercise...
Compiling exercise_crate v0.1.0 (/Users/desmo/repos/starklings-cairo1/runner-crate/Scarb.toml)
error: Wrong number of arguments. Expected 1, found: 0
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo:7:5
call_me();
^*******^
could not compile `exercise_crate` due to previous error
⚠️ Failed to run exercises/functions/functions3.cairo! Please try again.
The error message indicates that the call_me
function expects one argument, but none was provided in the function call. This mismatch causes the code to fail at compile time.
functions3.cairo
Solution
To resolve this issue, we need to provide the required argument when calling the call_me
function. Here’s the corrected code:
fn main() {
call_me(42);
}
fn call_me(num: u64) {
println!("num is {}", num);
}
Explanation
- Function Parameter:
- The
call_me
function is defined to accept a single parameter,num
, of typeu64
. This means every time we callcall_me
, we must provide au64
value.
2. Providing the Argument:
- In the
main
function, we correct the function call by passing the argument42
tocall_me
. This value will be passed to thenum
parameter, allowing the function to execute correctly.
3. Output:
- The
println!
macro will now outputnum is 42
, demonstrating that the function has received and correctly handled the argument.
By ensuring that the function call matches the function’s signature, we avoid errors and ensure that our code behaves as expected.
functions4.cairo
In this exercise, we explore function return types and how they are crucial for making our functions work correctly in Cairo. The scenario is a simple one: a store is having a sale where you get a discount based on whether the price is even or odd. This exercise will help us understand how to define functions with return types and why it’s important to ensure those types are correctly specified.
// functions4.cairo
// Execute `starklings hint functions4` or use the `hint` watch subcommand for a hint.
// This store is having a sale where if the price is an even number, you get
// 10 Cairobucks off, but if it's an odd number, it's 3 Cairobucks off.
// (Don't worry about the function bodies themselves, we're only interested
// in the signatures for now. If anything, this is a good way to peek ahead
// to future exercises!)
// I AM NOT DONE
fn main() {
let original_price = 51;
println!("sale_price is {}", sale_price(original_price));
}
fn sale_price(price: u32) -> {
if is_even(price) {
price - 10
} else {
price - 3
}
}
fn is_even(num: u32) -> bool {
num % 2 == 0
}
functions4.cairo
Errors
Let’s take a look at the errors that occur when we attempt to compile this code:
🟡 Running exercises/functions/functions4.cairo exercise...
Compiling exercise_crate v0.1.0 (/Users/desmo/repos/starklings-cairo1/runner-crate/Scarb.toml)
error: Missing tokens. Expected a type expression.
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo:17:29
fn sale_price(price: u32) -> {
^
error: Type annotations needed. Failed to infer ?4
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo[println_macro][writeln_macro]:4:22
match core::fmt::Display::fmt(__write_macro_arg0__, ref __formatter_for_print_macros__) {
^*****^
error: Unknown type.
--> /Users/desmo/repos/starklings-cairo1/runner-crate/src/lib.cairo:17:30
fn sale_price(price: u32) -> {
^
could not compile `exercise_crate` due to previous error
The main issue here is that the sale_price
function is missing its return type. The error message indicates that the compiler expects a type to be specified after the ->
symbol in the function signature.
functions4.cairo
Solution
To fix this, we need to explicitly declare the return type of the sale_price
function. Since the function is returning a modified price
, which is of type u32
, the return type should also be u32
. Here's the corrected code:
fn main() {
let original_price = 51;
println!("sale_price is {}", sale_price(original_price));
}
fn sale_price(price: u32) -> u32 {
if is_even(price) {
price - 10
} else {
price - 3
}
}
fn is_even(num: u32) -> bool {
num % 2 == 0
}
Explanation
- Return Type Declaration:
- The function
sale_price
now correctly declares that it returns au32
type. This matches the type of theprice
parameter, ensuring that the function signature aligns with what the function actually returns.
2. Function Logic:
- The
sale_price
function calculates the sale price based on whether the original price is even or odd. It uses theis_even
helper function to determine this and applies the appropriate discount.
3. Helper Function:
- The
is_even
function remains unchanged and returns abool
value indicating whether a number is even. This function is crucial for the logic insidesale_price
.
By specifying the correct return type, we ensure that the function works as intended and that the compiler can correctly infer types throughout the program. This is a fundamental aspect of writing robust and error-free code in statically-typed languages like Cairo.
Conclusion
In this blog, we walked through a series of exercises that highlighted the importance of functions in the Cairo programming language. We started with the basics of defining and calling simple functions and gradually introduced more complexity by adding parameters and return types.
Through these exercises, we learned several key concepts:
- Function Signatures: Understanding how to properly define a function’s parameters and return types is crucial for writing clear and maintainable code. Cairo, like many statically-typed languages, requires explicit type annotations, which help prevent errors and make the code more predictable.
- Parameter Handling: We saw how functions can become more flexible and reusable when they accept parameters, allowing us to pass in data that the function can process. This is fundamental to writing dynamic code that can handle a variety of inputs.
- Return Types: Specifying return types ensures that functions not only perform their tasks but also provide clear outputs that can be used elsewhere in the program. We saw how missing or incorrect return types lead to compilation errors and how fixing these ensures our code behaves as expected.
Stay tuned for more exercises and tutorials as we continue to explore the Cairo programming language and its applications in the world of blockchain and zero-knowledge proofs. Happy coding!