Skip to content

Commit b6be943

Browse files
authored
Add Concept Exercise: magical-measurements (#374)
1 parent ca64024 commit b6be943

File tree

8 files changed

+300
-2
lines changed

8 files changed

+300
-2
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Hints
2+
3+
## General
4+
5+
- [The Cairo Book - Type Conversions][tcb-conversions]
6+
- [The Cairo Book - Custom Type Conversion][tcb-ctc]
7+
8+
## 1. Converting Signed to Unsigned
9+
10+
- Check if the signed value is negative - if so, return `Option::None`
11+
- Check if the signed value exceeds `u16::MAX` (65535) - if so, return `Option::None`
12+
- For valid values, cast using `.try_into()` or direct conversion
13+
- Remember that `u16` can hold values from 0 to 65535
14+
15+
## 2. Converting Unsigned to Signed
16+
17+
- Since `u16::MAX` (65535) fits comfortably in `i32`, this conversion always succeeds
18+
- Use `.into()` for safe conversion from `u16` to `i32`
19+
20+
## 3. Custom Type Conversion
21+
22+
- Implement the `Into` trait with the syntax: `impl IntoImplName of Into<SourceType, TargetType>`
23+
- In the `into` method, create a new instance of the target type
24+
- Map the `potency` field from `Essence` to the `strength` field in `Elixir`
25+
- Once the trait is implemented, you can use `.into()` on `Essence` instances
26+
27+
## 4. Using the Into Trait
28+
29+
- After implementing the `Into` trait, call `.into()` on the `Essence` parameter
30+
- The compiler will automatically use your trait implementation
31+
- The return type annotation helps Cairo know which conversion to use
32+
33+
[tcb-conversions]: https://www.starknet.io/cairo-book/ch02-02-data-types.html#type-conversion
34+
[tcb-ctc]: https://www.starknet.io/cairo-book/ch05-02-an-example-program-using-structs.html#conversions-of-custom-types
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Introduction
2+
3+
Type conversions in Cairo allow you to transform data from one type to another, enabling flexible and safe data manipulation.
4+
5+
## Basics
6+
7+
Cairo provides several mechanisms for type conversion:
8+
9+
### Direct Casting
10+
11+
For compatible numeric types, you can use direct casting:
12+
13+
```rust
14+
let small_number: u8 = 42;
15+
let big_number: u32 = small_number.into();
16+
```
17+
18+
### The Into Trait
19+
20+
The `Into` trait enables conversion from one type to another:
21+
22+
```rust
23+
let value: u16 = 100;
24+
let converted: u32 = value.into();
25+
```
26+
27+
### The TryInto Trait
28+
29+
For conversions that might fail, use `TryInto` which returns an `Option`:
30+
31+
```rust
32+
let large_value: u32 = 70000;
33+
let small_value: Option<u16> = large_value.try_into();
34+
35+
match small_value {
36+
Option::Some(v) => println!("Converted: {}", v),
37+
Option::None => println!("Conversion failed"),
38+
}
39+
```
40+
41+
## Custom Type Conversions
42+
43+
You can implement `Into` for your own types:
44+
45+
```rust
46+
#[derive(Drop)]
47+
struct Celsius {
48+
value: i32,
49+
}
50+
51+
#[derive(Drop)]
52+
struct Fahrenheit {
53+
value: i32,
54+
}
55+
56+
impl CelsiusIntoFahrenheit of Into<Celsius, Fahrenheit> {
57+
fn into(self: Celsius) -> Fahrenheit {
58+
Fahrenheit { value: (self.value * 9) / 5 + 32 }
59+
}
60+
}
61+
```
62+
63+
## Safety Considerations
64+
65+
- Use `TryInto` when conversion might fail (e.g., large to small types)
66+
- Use `Into` when conversion always succeeds
67+
- Always handle `Option::None` cases from `TryInto`
68+
- Consider overflow and underflow when converting between signed and unsigned types

exercises/concept/magical-measurements/.meta/config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"authors": [
3-
"<your_gh_username>"
3+
"0xNeshi"
44
],
55
"files": {
66
"solution": [
@@ -16,5 +16,5 @@
1616
"Scarb.toml"
1717
]
1818
},
19-
"blurb": "<blurb>"
19+
"blurb": "Learn Cairo's type conversion traits while helping Astrid the alchemist standardize magical measurements."
2020
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Design
2+
3+
## Learning objectives
4+
5+
- Know what type conversions are.
6+
- Know how to convert between signed and unsigned integers safely.
7+
- Know how to implement the `Into` trait for custom types.
8+
- Understand when conversions can fail and how to handle them with `Option`.
9+
10+
## Out of scope
11+
12+
- `TryFrom` trait implementations.
13+
- Complex generic trait bounds.
14+
- Advanced trait derivation.
15+
- Performance implications of conversions.
16+
17+
## Concepts
18+
19+
- Type conversions
20+
21+
## Prerequisites
22+
23+
- Enums (Option)
24+
- Pattern matching
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#[derive(Drop)]
2+
pub struct Essence {
3+
pub potency: u16,
4+
}
5+
6+
#[derive(Drop)]
7+
pub struct Elixir {
8+
pub strength: u16,
9+
}
10+
11+
pub fn convert_signed_to_unsigned(value: i32) -> Option<u16> {
12+
if value < 0 {
13+
Option::None
14+
} else {
15+
match value.try_into() {
16+
Option::Some(v) => Option::Some(v),
17+
Option::None => Option::None,
18+
}
19+
}
20+
}
21+
22+
pub fn convert_unsigned_to_signed(value: u16) -> i32 {
23+
value.into()
24+
}
25+
26+
pub fn convert_essence_to_elixir(essence: Essence) -> Elixir {
27+
essence.into()
28+
}
29+
30+
impl EssenceIntoElixir of Into<Essence, Elixir> {
31+
fn into(self: Essence) -> Elixir {
32+
Elixir { strength: self.potency }
33+
}
34+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "magical_measurements"
3+
version = "0.1.0"
4+
edition = "2024_07"
5+
6+
[dev-dependencies]
7+
cairo_test = "2.9.2"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/// Essence - a rare magical ingredient with potency
2+
#[derive(Drop)]
3+
pub struct Essence {
4+
pub potency: u16,
5+
}
6+
7+
/// Elixir - a refined magical ingredient with strength
8+
#[derive(Drop)]
9+
pub struct Elixir {
10+
pub strength: u16,
11+
}
12+
13+
/// Convert signed integer to unsigned integer (if possible)
14+
pub fn convert_signed_to_unsigned(value: i32) -> Option<u16> {
15+
// Convert i32 to u16 only if the value is positive and fits in u16
16+
panic!("implement `convert_signed_to_unsigned`")
17+
}
18+
19+
/// Convert unsigned integer to signed integer
20+
pub fn convert_unsigned_to_signed(value: u16) -> i32 {
21+
// Convert u16 to i32 (should always succeed)
22+
panic!("implement `convert_unsigned_to_signed`")
23+
}
24+
25+
/// Convert Essence to Elixir using the Into trait
26+
pub fn convert_essence_to_elixir(essence: Essence) -> Elixir {
27+
// Use the Into trait to convert Essence to Elixir
28+
panic!("implement `convert_essence_to_elixir`")
29+
}
30+
31+
// TODO: Implement the Into trait for Essence -> Elixir conversion
32+
impl EssenceIntoElixir of Into<Essence, Elixir> {
33+
fn into(self: Essence) -> Elixir {
34+
panic!("implement `Into::<Essence, Elixir>::into`")
35+
}
36+
}
37+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use magical_measurements::{
2+
Essence, Elixir, convert_signed_to_unsigned, convert_unsigned_to_signed,
3+
convert_essence_to_elixir,
4+
};
5+
6+
#[test]
7+
fn test_convert_positive_signed_to_unsigned() {
8+
let result = convert_signed_to_unsigned(42);
9+
assert!(result.is_some());
10+
assert_eq!(result.unwrap(), 42);
11+
}
12+
13+
#[test]
14+
#[ignore]
15+
fn test_convert_negative_signed_to_unsigned() {
16+
let result = convert_signed_to_unsigned(-42);
17+
assert!(result.is_none());
18+
}
19+
20+
#[test]
21+
#[ignore]
22+
fn test_convert_zero_signed_to_unsigned() {
23+
let result = convert_signed_to_unsigned(0);
24+
assert!(result.is_some());
25+
assert_eq!(result.unwrap(), 0);
26+
}
27+
28+
#[test]
29+
#[ignore]
30+
fn test_convert_large_signed_to_unsigned() {
31+
let result = convert_signed_to_unsigned(100000); // Exceeds u16::MAX (65535)
32+
assert!(result.is_none());
33+
}
34+
35+
#[test]
36+
#[ignore]
37+
fn test_convert_max_u16_signed_to_unsigned() {
38+
let result = convert_signed_to_unsigned(65535); // u16::MAX
39+
assert!(result.is_some());
40+
assert_eq!(result.unwrap(), 65535);
41+
}
42+
43+
#[test]
44+
#[ignore]
45+
fn test_convert_small_unsigned_to_signed() {
46+
let result = convert_unsigned_to_signed(42);
47+
assert_eq!(result, 42);
48+
}
49+
50+
#[test]
51+
#[ignore]
52+
fn test_convert_zero_unsigned_to_signed() {
53+
let result = convert_unsigned_to_signed(0);
54+
assert_eq!(result, 0);
55+
}
56+
57+
#[test]
58+
#[ignore]
59+
fn test_convert_max_unsigned_to_signed() {
60+
let result = convert_unsigned_to_signed(65535); // u16::MAX
61+
assert_eq!(result, 65535);
62+
}
63+
64+
#[test]
65+
#[ignore]
66+
fn test_convert_essence_to_elixir() {
67+
let essence = Essence { potency: 100 };
68+
let elixir = convert_essence_to_elixir(essence);
69+
assert_eq!(elixir.strength, 100);
70+
}
71+
72+
#[test]
73+
#[ignore]
74+
fn test_convert_essence_to_elixir_direct() {
75+
let essence = Essence { potency: 250 };
76+
let elixir: Elixir = essence.into();
77+
assert_eq!(elixir.strength, 250);
78+
}
79+
80+
#[test]
81+
#[ignore]
82+
fn test_convert_essence_to_elixir_zero() {
83+
let essence = Essence { potency: 0 };
84+
let elixir = convert_essence_to_elixir(essence);
85+
assert_eq!(elixir.strength, 0);
86+
}
87+
88+
#[test]
89+
#[ignore]
90+
fn test_convert_essence_to_elixir_max() {
91+
let essence = Essence { potency: 65535 };
92+
let elixir = convert_essence_to_elixir(essence);
93+
assert_eq!(elixir.strength, 65535);
94+
}

0 commit comments

Comments
 (0)