first exercises

This commit is contained in:
lilymonade 2025-03-11 12:33:21 +01:00
parent 452417beaa
commit 6473f90590
Signed by: lilymonade
GPG Key ID: F8967EC454DBDCB6
6 changed files with 122 additions and 0 deletions

View File

@ -0,0 +1 @@
pub mod option;

View File

@ -0,0 +1,20 @@
pub fn is_some(opt: &Option<i32>) -> bool {
match opt {
None => false,
Some(_) => true,
}
}
pub fn get_or_default(opt: Option<i32>, default: i32) -> i32 {
match opt {
None => default,
Some(v) => v,
}
}
pub fn get_or_panic(opt: Option<i32>) -> i32 {
match opt {
None => panic!("try to get value of a None"),
Some(v) => v,
}
}

View File

@ -0,0 +1 @@
pub mod errors;

View File

@ -0,0 +1,25 @@
use subject_source::errors::option as opt;
#[test]
pub fn is_some() {
assert!(!opt::is_some(&None));
assert!(opt::is_some(&Some(0)));
}
#[test]
pub fn get_or_default() {
assert_eq!(opt::get_or_default(None, 1), 1);
assert_eq!(opt::get_or_default(Some(1), 1), 1);
assert_eq!(opt::get_or_default(Some(2), 1), 2);
}
#[test]
#[should_panic]
pub fn get_or_panic_none() {
let _ = std::hint::black_box(opt::get_or_panic(None));
}
#[test]
pub fn get_or_panic_some() {
assert_eq!(opt::get_or_panic(Some(1)), 1);
}

View File

@ -0,0 +1,57 @@
---
name = "Options"
difficulty = 1
exercises = [matching]
---
Sometimes, a function can fail to compute a value simply because the value you asked simply does not exist. For example, when you try to access a collection at a wrong index, or when you want to divide by zero.
To check for the existence or absence of value, we use the `Option` type. `Option` by itself is not a type, but a generic type, meaning it needs to be annotated with an other type, like this: `Option<Type>`. This allow us to have `Option<i32>`, `Option<String>`, `Option<&str>` ... And even `Option<Option<i32>>` if you want (but this one is a bit weird and you won't encounter it often.
`Option` comes in two flavors (named variants):
- `None`, to encode the absence of value.
- `Some(v)`, to encode the presence of value. Note the `v` after `Some` to name the wrapped value.
In order to create values of type `Option<T>`, we just name the variant we want, and if needed, we give the variant a value:
```example
let some_one = Some(1);
let nothing: Option<i32> = None;
```
To match against an `Option` value, you can use pattern matching:
```example
let array = [1, 2, 3];
let element: Option<&i32> = array.first();
match element {
None => println!("array has no first value"),
Some(v) => println!("the first value is {}", v),
}
```
In this part we will try to implement usual functions of `Option`:
*src/errors/option.rs*
```prototype
/// Returns `true` if `opt` is `Some`
/// and `false` otherwise.
pub fn is_some(opt: &Option<i32>) -> bool {
unimplemented!()
}
/// Returns the value wrapped in `opt` if it is `Some`,
/// and `default` otherwise.
pub fn get_or_default(opt: Option<i32>, default: i32) -> i32 {
unimplemented!()
}
/// Returns the value wrapped in `opt` if there is any,
/// and panic!() otherwise.
pub fn get_or_panic(opt: Option<i32>) -> i32 {
unimplemented!()
}
```

18
subject_text/index.md Normal file
View File

@ -0,0 +1,18 @@
---
revision = 0.1.0
parts = [errors]
---
When it comes to programming, it's all fun and games until the real world comes in and sends weird unexpected inputs to your little protege. So you better handle those cases as best as you can. There are 3 main ways of handling errors.
- Returning some value (usualy an integer) alongside the data to notify there was an error (C, Go)
- Throwing exceptions, effectively hijacking the program's control-flow (C++, Java, C#, Python...)
- Encode good and bad paths in the type
The first approach is obviously a thing of the past considering how evolved compilers have become and the need of safety in our apps. You want to give humans as little responsibility as possible when it comes to error checking.
The second approach is more common in mainstream languages, and the third in functional languages, more precisely languages with so called ["Algebraic Data Types"](https://en.wikipedia.org/wiki/Algebraic_data_type) (aka ["tagged unions"](https://en.wikipedia.org/wiki/Tagged_union)). The second approach is usualy less verbose because it handles error propagation for you, but this approach has many downsides that make languages slowly adopt the third approach ([C++](https://en.cppreference.com/w/cpp/utility/optional), [Python](https://docs.python.org/3/library/typing.html#typing.Optional)), mainly because of control-flow.
Rust took the third approach, as it is simple to reason about for the compiler and of course makes it easy to prove the soundness of you program. It even helps modeling your application such that ["no invalid state can be represented"](https://geeklaunch.io/blog/make-invalid-states-unrepresentable/) but it's a topic we won't cover in this course.
This training session will cover the two main types of error (`Option` and `Result`) in Rust, and use them in the context of collections and strings.