Skip to content

Add Concept Exercise: magical-measurements #374

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions exercises/concept/magical-measurements/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Hints

## General

- [The Cairo Book - Type Conversions][tcb-conversions]
- [The Cairo Book - Custom Type Conversion][tcb-ctc]

## 1. Converting Signed to Unsigned

- Check if the signed value is negative - if so, return `Option::None`
- Check if the signed value exceeds `u16::MAX` (65535) - if so, return `Option::None`
- For valid values, cast using `.try_into()` or direct conversion
- Remember that `u16` can hold values from 0 to 65535

## 2. Converting Unsigned to Signed

- Since `u16::MAX` (65535) fits comfortably in `i32`, this conversion always succeeds
- Use `.into()` for safe conversion from `u16` to `i32`

## 3. Custom Type Conversion

- Implement the `Into` trait with the syntax: `impl IntoImplName of Into<SourceType, TargetType>`
- In the `into` method, create a new instance of the target type
- Map the `potency` field from `Essence` to the `strength` field in `Elixir`
- Once the trait is implemented, you can use `.into()` on `Essence` instances

## 4. Using the Into Trait

- After implementing the `Into` trait, call `.into()` on the `Essence` parameter
- The compiler will automatically use your trait implementation
- The return type annotation helps Cairo know which conversion to use

[tcb-conversions]: https://www.starknet.io/cairo-book/ch02-02-data-types.html#type-conversion
[tcb-ctc]: https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html#conversions-of-custom-types
68 changes: 68 additions & 0 deletions exercises/concept/magical-measurements/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Introduction

Type conversions in Cairo allow you to transform data from one type to another, enabling flexible and safe data manipulation.

## Basics

Cairo provides several mechanisms for type conversion:

### Direct Casting

For compatible numeric types, you can use direct casting:

```rust
let small_number: u8 = 42;
let big_number: u32 = small_number.into();
```

### The Into Trait

The `Into` trait enables conversion from one type to another:

```rust
let value: u16 = 100;
let converted: u32 = value.into();
```

### The TryInto Trait

For conversions that might fail, use `TryInto` which returns an `Option`:

```rust
let large_value: u32 = 70000;
let small_value: Option<u16> = large_value.try_into();

match small_value {
Option::Some(v) => println!("Converted: {}", v),
Option::None => println!("Conversion failed"),
}
```

## Custom Type Conversions

You can implement `Into` for your own types:

```rust
#[derive(Drop)]
struct Celsius {
value: i32,
}

#[derive(Drop)]
struct Fahrenheit {
value: i32,
}

impl CelsiusIntoFahrenheit of Into<Celsius, Fahrenheit> {
fn into(self: Celsius) -> Fahrenheit {
Fahrenheit { value: (self.value * 9) / 5 + 32 }
}
}
```

## Safety Considerations

- Use `TryInto` when conversion might fail (e.g., large to small types)
- Use `Into` when conversion always succeeds
- Always handle `Option::None` cases from `TryInto`
- Consider overflow and underflow when converting between signed and unsigned types
4 changes: 2 additions & 2 deletions exercises/concept/magical-measurements/.meta/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"authors": [
"<your_gh_username>"
"0xNeshi"
],
"files": {
"solution": [
Expand All @@ -16,5 +16,5 @@
"Scarb.toml"
]
},
"blurb": "<blurb>"
"blurb": "Learn Cairo's type conversion traits while helping Astrid the alchemist standardize magical measurements."
}
24 changes: 24 additions & 0 deletions exercises/concept/magical-measurements/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Design

## Learning objectives

- Know what type conversions are.
- Know how to convert between signed and unsigned integers safely.
- Know how to implement the `Into` trait for custom types.
- Understand when conversions can fail and how to handle them with `Option`.

## Out of scope

- `TryFrom` trait implementations.
- Complex generic trait bounds.
- Advanced trait derivation.
- Performance implications of conversions.

## Concepts

- Type conversions

## Prerequisites

- Enums (Option)
- Pattern matching
34 changes: 34 additions & 0 deletions exercises/concept/magical-measurements/.meta/exemplar.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[derive(Drop)]
pub struct Essence {
pub potency: u16,
}

#[derive(Drop)]
pub struct Elixir {
pub strength: u16,
}

pub fn convert_signed_to_unsigned(value: i32) -> Option<u16> {
if value < 0 {
Option::None
} else {
match value.try_into() {
Option::Some(v) => Option::Some(v),
Option::None => Option::None,
}
}
}

pub fn convert_unsigned_to_signed(value: u16) -> i32 {
value.into()
}

pub fn convert_essence_to_elixir(essence: Essence) -> Elixir {
essence.into()
}

impl EssenceIntoElixir of Into<Essence, Elixir> {
fn into(self: Essence) -> Elixir {
Elixir { strength: self.potency }
}
}
7 changes: 7 additions & 0 deletions exercises/concept/magical-measurements/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "magical_measurements"
version = "0.1.0"
edition = "2024_07"

[dev-dependencies]
cairo_test = "2.9.2"
37 changes: 37 additions & 0 deletions exercises/concept/magical-measurements/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// Essence - a rare magical ingredient with potency
#[derive(Drop)]
pub struct Essence {
pub potency: u16,
}

/// Elixir - a refined magical ingredient with strength
#[derive(Drop)]
pub struct Elixir {
pub strength: u16,
}

/// Convert signed integer to unsigned integer (if possible)
pub fn convert_signed_to_unsigned(value: i32) -> Option<u16> {
// Convert i32 to u16 only if the value is positive and fits in u16
panic!("implement `convert_signed_to_unsigned`")
}

/// Convert unsigned integer to signed integer
pub fn convert_unsigned_to_signed(value: u16) -> i32 {
// Convert u16 to i32 (should always succeed)
panic!("implement `convert_unsigned_to_signed`")
}

/// Convert Essence to Elixir using the Into trait
pub fn convert_essence_to_elixir(essence: Essence) -> Elixir {
// Use the Into trait to convert Essence to Elixir
panic!("implement `convert_essence_to_elixir`")
}

// TODO: Implement the Into trait for Essence -> Elixir conversion
impl EssenceIntoElixir of Into<Essence, Elixir> {
fn into(self: Essence) -> Elixir {
panic!("implement `Into::<Essence, Elixir>::into`")
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use magical_measurements::{
Essence, Elixir, convert_signed_to_unsigned, convert_unsigned_to_signed,
convert_essence_to_elixir,
};

#[test]
fn test_convert_positive_signed_to_unsigned() {
let result = convert_signed_to_unsigned(42);
assert!(result.is_some());
assert_eq!(result.unwrap(), 42);
}

#[test]
#[ignore]
fn test_convert_negative_signed_to_unsigned() {
let result = convert_signed_to_unsigned(-42);
assert!(result.is_none());
}

#[test]
#[ignore]
fn test_convert_zero_signed_to_unsigned() {
let result = convert_signed_to_unsigned(0);
assert!(result.is_some());
assert_eq!(result.unwrap(), 0);
}

#[test]
#[ignore]
fn test_convert_large_signed_to_unsigned() {
let result = convert_signed_to_unsigned(100000); // Exceeds u16::MAX (65535)
assert!(result.is_none());
}

#[test]
#[ignore]
fn test_convert_max_u16_signed_to_unsigned() {
let result = convert_signed_to_unsigned(65535); // u16::MAX
assert!(result.is_some());
assert_eq!(result.unwrap(), 65535);
}

#[test]
#[ignore]
fn test_convert_small_unsigned_to_signed() {
let result = convert_unsigned_to_signed(42);
assert_eq!(result, 42);
}

#[test]
#[ignore]
fn test_convert_zero_unsigned_to_signed() {
let result = convert_unsigned_to_signed(0);
assert_eq!(result, 0);
}

#[test]
#[ignore]
fn test_convert_max_unsigned_to_signed() {
let result = convert_unsigned_to_signed(65535); // u16::MAX
assert_eq!(result, 65535);
}

#[test]
#[ignore]
fn test_convert_essence_to_elixir() {
let essence = Essence { potency: 100 };
let elixir = convert_essence_to_elixir(essence);
assert_eq!(elixir.strength, 100);
}

#[test]
#[ignore]
fn test_convert_essence_to_elixir_direct() {
let essence = Essence { potency: 250 };
let elixir: Elixir = essence.into();
assert_eq!(elixir.strength, 250);
}

#[test]
#[ignore]
fn test_convert_essence_to_elixir_zero() {
let essence = Essence { potency: 0 };
let elixir = convert_essence_to_elixir(essence);
assert_eq!(elixir.strength, 0);
}

#[test]
#[ignore]
fn test_convert_essence_to_elixir_max() {
let essence = Essence { potency: 65535 };
let elixir = convert_essence_to_elixir(essence);
assert_eq!(elixir.strength, 65535);
}
Loading