Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write the "Smart Pointers" concept + Related chrono-realms-chrono-chain Exercise #322

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion concepts/control-flow/.meta/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"blurb": "Control flow in Cairo uses `if` expressions to conditionally execute code and loops to run code repeatedly.",
"blurb": "Control flow in Cairo uses `if` expressions to conditionally execute code, and loops to run code repeatedly.",
"authors": ["Falilah"],
"contributors": ["0xNeshi"]
}
2 changes: 1 addition & 1 deletion concepts/control-flow/links.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
"url": "https://book.cairo-lang.org/ch02-05-control-flow.html",
"description": "Control flow concept in cairo book"
"description": "Control Flow Concept in The Cairo Book"
}
]
4 changes: 2 additions & 2 deletions concepts/smart-pointers/.meta/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"blurb": "<todo>",
"blurb": "Smart pointers in Cairo are special data structures that manage memory safely by ensuring that memory is accessed in a controlled way.",
"authors": [
"<your_gh_username>"
"0xNeshi"
],
"contributors": []
}
126 changes: 126 additions & 0 deletions concepts/smart-pointers/about.md
Original file line number Diff line number Diff line change
@@ -1 +1,127 @@
# Smart Pointers

Smart pointers in Cairo are a powerful tool that provide safe and efficient management of memory.

A smart pointer is a data structure that acts like a regular pointer, but with added safety features to avoid common memory management issues like dereferencing null pointers or accessing uninitialized memory.

## What is a Smart Pointer?

In general, a pointer is a variable that stores a memory address, typically pointing to a value stored at that location.

However, raw pointers can be dangerous: if the pointer doesn't point to valid memory or is incorrectly dereferenced, it can lead to crashes or unpredictable behavior.

Smart pointers solve this issue by enforcing strict rules on memory management, ensuring that memory is accessed in a safe and controlled manner.

Cairo, like many modern languages such as Rust, uses smart pointers to prevent unsafe memory access.

A smart pointer in Cairo not only stores a memory address but also tracks ownership and ensures memory safety.

## Types of Smart Pointers in Cairo

Cairo provides several types of smart pointers, including `Box<T>` and `Nullable<T>`, each serving a different purpose.

Let's take a closer look at how these types work and when you might use them.

### `Box<T>`

The `Box<T>` type is the principal smart pointer in Cairo.

It allows you to store data in a special memory segment called the "boxed segment." A `Box<T>` is a pointer that points to this segment, and when you create a box, you allocate space for the data in this segment.

Boxes are ideal in situations where:

- You need to store a value of a type whose size cannot be determined at compile time.
- You have a large amount of data and want to transfer ownership without copying it.

By using a box, you can store large data structures more efficiently.

When passing a `Box<T>` to a function, only the pointer is passed, avoiding the need to copy the entire data, which improves performance, especially with large structures.

### `Nullable<T>`

The `Nullable<T>` type is another important smart pointer in Cairo.

It can either point to a valid value of type `T` or be `null` if there is no value.

This type is useful in cases where you need to store values that may not always exist, such as in a dictionary that can contain optional elements.

In Cairo, `Nullable<T>` is typically used in dictionaries to handle cases where no default value can be applied.

It allows you to store values conditionally, making it easier to handle the absence of data safely.

## Memory Safety with Smart Pointers

One of the primary advantages of using smart pointers is that they help ensure memory safety.

By managing ownership and access rules, Cairo prevents common issues such as dereferencing null or dangling pointers.

Smart pointers track when data is no longer needed and can automatically deallocate memory when it goes out of scope, reducing the risk of memory leaks.

### Example: Using a `Box<T>` for Recursive Types

A common challenge in many programming languages is handling recursive types.

Recursive types refer to types that contain references to themselves.

Without proper memory management, defining a recursive type can lead to issues such as infinite recursion.

In Cairo, `Box<T>` makes it possible to define recursive types safely.

For instance, you can use `Box<T>` to implement a binary tree, where each node contains a reference to another node.

By storing references in boxes, Cairo ensures that memory usage is finite, and the compiler can determine the required memory size for the structure.

```rust
use core::box::{BoxTrait};

#[derive(Copy, Drop)]
enum BinaryTree {
Leaf: u32,
Node: (u32, Box<BinaryTree>, Box<BinaryTree>),
}

fn main() {
let leaf1 = BinaryTree::Leaf(1);
let leaf2 = BinaryTree::Leaf(2);
let node = BinaryTree::Node((3, BoxTrait::new(leaf1), BoxTrait::new(leaf2)));
println!("{:?}", node);
}
```

### Performance Benefits of Smart Pointers

Smart pointers also improve the performance of your programs.

When you use `Box<T>`, only the pointer to the data is passed around, instead of copying the entire data structure.

This is especially useful when dealing with large datasets, as it significantly reduces the overhead of memory operations.

In the following example, the data is passed by reference using a `Box<T>` to avoid the cost of copying the entire structure:

```rust
#[derive(Drop)]
struct Cart {
paid: bool,
items: u256,
buyer: ByteArray,
}

fn pass_data(cart: Cart) {
println!("{} is shopping today and bought {} items", cart.buyer, cart.items);
}

fn pass_pointer(cart: Box<Cart>) {
let cart = cart.unbox();
println!("{} is shopping today and bought {} items", cart.buyer, cart.items);
}

fn main() {
let new_cart = Cart { paid: true, items: 1, buyer: "John" };
pass_data(new_cart);

let new_box = BoxTrait::new(Cart { paid: false, items: 3, buyer: "Jane" });
pass_pointer(new_box);
}
```

In this example, `pass_pointer` takes a `Box<Cart>` instead of a `Cart`, reducing the amount of memory copied during the function call.
6 changes: 6 additions & 0 deletions concepts/smart-pointers/introduction.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Introduction

Smart pointers in Cairo are special data structures that manage memory safely by ensuring that memory is accessed in a controlled way.

They act like regular pointers but include additional features like ownership rules and metadata to prevent common errors, such as dereferencing uninitialized memory.

This makes them essential for working with complex data structures and improving program safety and performance.
11 changes: 10 additions & 1 deletion concepts/smart-pointers/links.json
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
[]
[
{
"url": "https://book.cairo-lang.org/ch11-02-smart-pointers.html",
"description": "Smart Pointers in The Cairo Book"
},
{
"url": "https://book.cairo-lang.org/ch03-02-dictionaries.html#dictionaries-of-types-not-supported-natively",
"description": "Nullable<T> Use Case with Dictionaries"
}
]
9 changes: 5 additions & 4 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,15 @@
"status": "wip"
},
{
"slug": "chrono-realms-time-tree",
"name": "Chrono Realms Time Tree",
"uuid": "a72139a1-825e-4349-a6b5-400b665fbe07",
"slug": "chrono-realms-chrono-chain",
"name": "Chrono Realms Chrono Chain",
"uuid": "e04f808a-25dc-4448-bc45-d3da0db9819a",
"concepts": [
"smart-pointers"
],
"prerequisites": [
"structs"
"traits",
"enums"
],
"status": "wip"
}
Expand Down
18 changes: 18 additions & 0 deletions exercises/concept/chrono-realms-chrono-chain/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Hints

## General

- [The Cairo Book: Smart Pointers][smart-pointers]

## 1. Build the ChronoChain from an array of `u32` values

- We can iterate through the array and construct a chain in reverse, starting from the `End` and linking each value with the `Link` variant.
- Consider using `span.pop_back()` to iterate through the array in reverse order, this will help us create the chain from the end to the start.

## 2. Sum the values in the ChronoChain

- Use pattern matching to handle the two variants of `ChronoChain`.
- When you reach `End`, the sum is 0, and when you reach `Link`, you add the value and recursively sum the rest of the chain.
- This approach is similar to a recursive function that traverses the structure until it reaches the base case (`End`).

[smart-pointers]: https://book.cairo-lang.org/ch11-02-smart-pointers.html
46 changes: 46 additions & 0 deletions exercises/concept/chrono-realms-chrono-chain/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Instructions

In **Chrono Realms**, Time Keepers often deal with not just trees of timelines, but **Chrono Chains**-sequences of linked **TimeNodes**, each representing a specific moment in time.
A **Chrono Chain** is a straight path of sequential moments, where each **TimeNode** connects to the next.
These **Chrono Chains** are useful when traveling through a series of specific events, as they allow Time Keepers to follow a single timeline.

However, to handle these potentially long **Chrono Chains**, Time Keepers use **Smart Pointers (Box<T>)** to safely manage and traverse these lists of moments without causing unnecessary memory duplication or overflow.
Each **TimeNode** holds a reference to the next node, forming a recursive structure.

Your task as an apprentice is to implement a **Chrono Chain** as a recursive list structure using smart pointers.

In this exercise, you will:

1. Create a recursive `ChronoChain` enum, representing a list of moments.
2. Use the `Box<T>` smart pointer to store the recursive nodes.
3. Implement a function to create a `ChronoChain` from an array of `u32` values.
4. Implement a function to traverse the `ChronoChain` and sum up the values stored in the list.

## 1. Define the Recursive `ChronoChain` Enum

Create a recursive enum `ChronoChain` with two variants:

- `End`: Represents the end of the list.
- `Link`: Contains a `u32` value and a boxed reference to the next node in the chain.

## 2. Create a Function to Build a ChronoChain

Write a function `ChronoChain::build` that takes an array of `u32` values and returns a `ChronoChain`, linking the values sequentially using smart pointers.

## 3. Implement the Sum Function

Write a function `ChronoChain::sum` to recursively traverse the `ChronoChain` and sum the values of all nodes.

## Example Usage

```rust
fn main() {
// Create a ChronoChain from an array of values
let chrono_chain = ChronoChain::build(array![10, 20, 30]);

// Sum the values in the ChronoChain
let total_sum = chrono_chain.sum();

println!("Total Time Power: {}", total_sum);
}
```
52 changes: 52 additions & 0 deletions exercises/concept/chrono-realms-chrono-chain/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Introduction

Smart pointers in Cairo are advanced data structures that ensure safe and efficient memory management by adding safety features to regular pointers, preventing common issues like dereferencing null pointers or accessing uninitialized memory.

## What is a Smart Pointer?

A smart pointer behaves like a regular pointer but tracks ownership and ensures safe memory access, preventing issues like null or dangling pointer dereferencing.

## Types of Smart Pointers

Cairo provides several smart pointer types, such as `Box<T>` and `Nullable<T>`:

- **`Box<T>`**: Stores data in a special memory segment, ideal for large or dynamically-sized data.
It allows transferring ownership without copying the data.
- **`Nullable<T>`**: Points to either a valid value of type `T` or `null`, useful for handling optional values.

## Memory Safety

Smart pointers help prevent unsafe memory access, ensuring memory is automatically deallocated when no longer needed, thus reducing the risk of memory leaks.

### Example: Using `Box<T>` for Recursive Types

Smart pointers like `Box<T>` allow for safe handling of recursive types, such as in a binary tree, by allocating memory efficiently and avoiding infinite recursion.

```rust
use core::box::{BoxTrait};

#[derive(Copy, Drop)]
enum BinaryTree {
Leaf: u32,
Node: (u32, Box<BinaryTree>, Box<BinaryTree>),
}

fn main() {
let leaf1 = BinaryTree::Leaf(1);
let leaf2 = BinaryTree::Leaf(2);
let node = BinaryTree::Node((3, BoxTrait::new(leaf1), BoxTrait::new(leaf2)));
println!("{:?}", node);
}
```

## Performance Benefits

Smart pointers improve performance by passing references to data instead of copying large structures, reducing memory overhead.

```rust
// `Cart` is a large struct that contains a lot of information
fn pass_pointer(cart: Box<Cart>) {
let cart = cart.unbox();
println!("{} is shopping today and bought {} items", cart.buyer, cart.items);
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"src/lib.cairo"
],
"test": [
"tests/chrono_realms_time_tree.cairo"
"tests/chrono_realms_chrono_chain.cairo"
],
"exemplar": [
".meta/exemplar.cairo"
Expand All @@ -16,5 +16,5 @@
"Scarb.toml"
]
},
"blurb": "<blurb>"
"blurb": "Learn smart pointers by building the magical Chrono Chain"
}
36 changes: 36 additions & 0 deletions exercises/concept/chrono-realms-chrono-chain/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Design

## Goal

Introduce the student to working with recursive types and smart pointers in Cairo.

## Learning objectives

- Understand how to define and use recursive types with enums in Cairo.
- Learn how to use smart pointers (`Box<T>`) for handling recursive data structures.
- Practice working with recursive functions to traverse and manipulate linked data.

## Out of scope

- Advanced memory management concepts related to smart pointers beyond `Box<T>`.
- Deep dive into optimization of recursive data structures.

## Concepts

- Enums
- Recursive types
- Smart pointers (`Box<T>`)

## Prerequisites

- Traits
- Basic understanding of enums and data types in Cairo.
- Familiarity with smart pointers in Cairo.

## Resources to refer to

- [Cairo Book - The Box Type][box]
- [Cairo Book - Enums][enums]

[box]: https://book.cairo-lang.org/ch11-02-smart-pointers.html#the-boxt-type-to-manipulate-pointers
[enums]: https://book.cairo-lang.org/ch06-01-enums.html
30 changes: 30 additions & 0 deletions exercises/concept/chrono-realms-chrono-chain/.meta/exemplar.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Define the recursive ChronoChain enum
#[derive(Copy, Drop)]
pub enum ChronoChain {
End,
Link: (u32, Box<ChronoChain>),
}

#[generate_trait]
pub impl ChronoChainImpl of ChronoChainTrait {
// Function to build a ChronoChain from an array of u32 values
fn build(arr: Array<u32>) -> ChronoChain {
let mut chain = ChronoChain::End;

// Iterate in reverse to build the chain from the end to the beginning
let mut span = arr.span();
while let Option::Some(value) = span.pop_back() {
chain = ChronoChain::Link((*value, BoxTrait::new(chain)));
};

chain
}

// Function to sum the values in the ChronoChain
fn sum(self: ChronoChain) -> u64 {
match self {
ChronoChain::End => 0,
ChronoChain::Link((value, next)) => value.into() + next.unbox().sum(),
}
}
}
Loading
Loading