mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-01-09 20:03:24 +03:00
Compare commits
18 commits
09c94bef2d
...
2f8fa469ac
Author | SHA1 | Date | |
---|---|---|---|
2f8fa469ac | |||
d3a0c26999 | |||
95f10c8068 | |||
9bb174e96e | |||
4c5573b09f | |||
43eb014026 | |||
2d792651ea | |||
6cf75d569b | |||
67ce9b9e56 | |||
bcebbb9df6 | |||
bdd76cdf77 | |||
825637f32c | |||
8ef5d10da2 | |||
5217cdc5e2 | |||
e3c8c457ba | |||
cddaf4881e | |||
428d64ffa0 | |||
42a3503906 |
68
CHANGELOG.md
68
CHANGELOG.md
|
@ -1,3 +1,69 @@
|
||||||
|
<a name="6.0.0"></a>
|
||||||
|
## 6.0.0 (2024-07-02)
|
||||||
|
|
||||||
|
This release is the result of a complete rewrite to deliver a ton of new features and improvements ✨
|
||||||
|
The most important changes are highlighted below.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
The installation has been simplified a lot!
|
||||||
|
To install Rustlings after installing Rust, all what you need to do now is running the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install rustlings
|
||||||
|
```
|
||||||
|
|
||||||
|
Yes, this means that Rustlings is now on [crates.io](https://crates.io/crates/rustlings) 🎉
|
||||||
|
|
||||||
|
You can read about the motivations of this change in [this issue](https://github.com/rust-lang/rustlings/issues/1919).
|
||||||
|
|
||||||
|
### UI/UX
|
||||||
|
|
||||||
|
- The UI is now responsive when the terminal is resized.
|
||||||
|
- The progress bar was moved to the bottom so that you can always see your progress and the current exercise to work on.
|
||||||
|
- The current exercise path is now a terminal link. It will open the exercise file in your default editor when you click on it.
|
||||||
|
- A small prompt is now always shown at the bottom. It allows you to choose an action by entering a character. For example, entering `h` will show you the hint of the current exercise.
|
||||||
|
- The comment "I AM NOT DONE!" doesn't exist anymore. Instead of needing to remove it to go to the next exercise, you need to enter `n` in the terminal.
|
||||||
|
|
||||||
|
### List mode
|
||||||
|
|
||||||
|
A list mode was added using [`Ratatui`](https://ratatui.rs).
|
||||||
|
You can enter it by entering `l` in the watch mode.
|
||||||
|
It offers the following features:
|
||||||
|
|
||||||
|
- Browse all exercises and see their state (pending/done).
|
||||||
|
- Filter exercises based on their state (pending/done).
|
||||||
|
- Continue at another exercise. This allows you to skip some exercises or go back to previous ones.
|
||||||
|
- Reset an exercise so you can start over and revert your changes.
|
||||||
|
|
||||||
|
### Solutions
|
||||||
|
|
||||||
|
After finishing an exercise, a solution file will be available and Rustlings will show you its path in green.
|
||||||
|
This allows you to compare your solution with an idiomatic solution and maybe learn about other ways to solve a problem.
|
||||||
|
|
||||||
|
While writing the solutions, all exercises have been polished 🌟
|
||||||
|
For example, every exercise now contains `TODO` comments to highlight what the user needs to change and where.
|
||||||
|
|
||||||
|
### LSP support out of the box
|
||||||
|
|
||||||
|
Instead of creating a `project.json` file using `rustlings lsp`, Rustlings now works with a `Cargo.toml` file out of the box.
|
||||||
|
No actions are needed to activate the language server `rust-analyzer`.
|
||||||
|
|
||||||
|
This should avoid issues related to the language server or to running exercises, especially the ones with Clippy.
|
||||||
|
|
||||||
|
### Clippy
|
||||||
|
|
||||||
|
Clippy lints are now shown on all exercises, not only the Clippy exercises 📎
|
||||||
|
Make Clippy your friend from early on 🥰
|
||||||
|
|
||||||
|
### Third party exercises
|
||||||
|
|
||||||
|
Rustlings now supports third-party exercises!
|
||||||
|
|
||||||
|
Do you want to create your own set of Rustlings exercises to focus on some specific topic?
|
||||||
|
Or do you want to translate the original Rustlings exercises?
|
||||||
|
Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXERCISES.md)!
|
||||||
|
|
||||||
<a name="5.6.1"></a>
|
<a name="5.6.1"></a>
|
||||||
## 5.6.1 (2023-09-18)
|
## 5.6.1 (2023-09-18)
|
||||||
|
|
||||||
|
@ -30,7 +96,7 @@
|
||||||
- Swapped the order of threads and smart pointer exercises.
|
- Swapped the order of threads and smart pointer exercises.
|
||||||
- Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :)
|
- Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :)
|
||||||
- `structs3`: Switched from i32 to u32.
|
- `structs3`: Switched from i32 to u32.
|
||||||
- `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same
|
- `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same
|
||||||
concepts.
|
concepts.
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
|
|
@ -32,11 +32,14 @@ If you need any help with it or face any Git related problems, don't hesitate to
|
||||||
It may take time to review your pull request.
|
It may take time to review your pull request.
|
||||||
Please be patient 😇
|
Please be patient 😇
|
||||||
|
|
||||||
|
When updating an exercise, check if its solution needs to be updated.
|
||||||
|
|
||||||
## Adding An Exercise
|
## Adding An Exercise
|
||||||
|
|
||||||
- Name the file `exercises/yourTopic/yourTopicN.rs`.
|
- Name the file `exercises/yourTopic/yourTopicN.rs`.
|
||||||
- Make sure to put in some helpful links, and link to sections of the book in `exercises/yourTopic/README.md`.
|
- Make sure to put in some helpful links, and link to sections of The Book in `exercises/yourTopic/README.md`.
|
||||||
- Add a (possible) solution at `solutions/yourTopic/yourTopicN.rs` with comments and links explaining it.
|
- In the exercise, add a `// TODO: …` comment where user changes are required.
|
||||||
|
- Add a solution at `solutions/yourTopic/yourTopicN.rs` with comments explaining it.
|
||||||
- Add the [metadata for your exercise](#exercise-metadata) in the `rustlings-macros/info.toml` file.
|
- Add the [metadata for your exercise](#exercise-metadata) in the `rustlings-macros/info.toml` file.
|
||||||
- Make sure your exercise runs with `rustlings run yourTopicN`.
|
- Make sure your exercise runs with `rustlings run yourTopicN`.
|
||||||
- [Open a pull request](#pull-requests).
|
- [Open a pull request](#pull-requests).
|
||||||
|
@ -49,7 +52,9 @@ The exercise metadata should contain the following:
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "yourTopicN"
|
name = "yourTopicN"
|
||||||
dir = "yourTopic"
|
dir = "yourTopic"
|
||||||
hint = """A useful (multi-line) hint for your exercise."""
|
hint = """
|
||||||
|
A useful (multi-line) hint for your exercise.
|
||||||
|
Include links to a section in The Book or a documentation page."""
|
||||||
```
|
```
|
||||||
|
|
||||||
If your exercise doesn't contain any test, add `test = false` to the exercise metadata.
|
If your exercise doesn't contain any test, add `test = false` to the exercise metadata.
|
||||||
|
|
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -151,9 +151,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.7"
|
version = "4.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
@ -161,9 +161,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.7"
|
version = "4.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
@ -173,9 +173,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.5"
|
version = "4.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -416,9 +416,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.21"
|
version = "0.4.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
|
@ -654,7 +654,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustlings"
|
name = "rustlings"
|
||||||
version = "6.0.0-beta.9"
|
version = "6.0.0-beta.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
|
@ -673,7 +673,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustlings-macros"
|
name = "rustlings-macros"
|
||||||
version = "6.0.0-beta.9"
|
version = "6.0.0-beta.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -729,9 +729,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.118"
|
version = "1.0.120"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
|
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
|
@ -8,7 +8,7 @@ exclude = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "6.0.0-beta.9"
|
version = "6.0.0-beta.10"
|
||||||
authors = [
|
authors = [
|
||||||
"Liv <mokou@fastmail.com>",
|
"Liv <mokou@fastmail.com>",
|
||||||
"Mo Bitar <mo8it@proton.me>",
|
"Mo Bitar <mo8it@proton.me>",
|
||||||
|
@ -47,14 +47,14 @@ include = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
clap = { version = "4.5.7", features = ["derive"] }
|
clap = { version = "4.5.8", features = ["derive"] }
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
hashbrown = "0.14.5"
|
hashbrown = "0.14.5"
|
||||||
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
notify-debouncer-mini = { version = "0.4.1", default-features = false }
|
||||||
os_pipe = "1.2.0"
|
os_pipe = "1.2.0"
|
||||||
ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] }
|
ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] }
|
||||||
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.9" }
|
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.10" }
|
||||||
serde_json = "1.0.118"
|
serde_json = "1.0.120"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
toml_edit.workspace = true
|
toml_edit.workspace = true
|
||||||
|
|
||||||
|
|
19
README.md
19
README.md
|
@ -32,19 +32,15 @@ This'll also install _Cargo_, Rust's package/project manager.
|
||||||
|
|
||||||
The following command will download and compile Rustlings:
|
The following command will download and compile Rustlings:
|
||||||
|
|
||||||
<!-- TODO: Remove @6.0.0-beta.x -->
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install rustlings@6.0.0-beta.9
|
cargo install rustlings
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>If the installation fails…</strong> (<em>click to expand</em>)</summary>
|
<summary><strong>If the installation fails…</strong> (<em>click to expand</em>)</summary>
|
||||||
|
|
||||||
<!-- TODO: Remove @6.0.0-beta.x -->
|
|
||||||
|
|
||||||
- Make sure you have the latest Rust version by running `rustup update`
|
- Make sure you have the latest Rust version by running `rustup update`
|
||||||
- Try adding the `--locked` flag: `cargo install rustlings@6.0.0-beta.9 --locked`
|
- Try adding the `--locked` flag: `cargo install rustlings --locked`
|
||||||
- Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new)
|
- Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
@ -88,6 +84,9 @@ We highly recommend that you have a look at them before you start 📚️
|
||||||
Most exercises contain an error that keeps them from compiling, and it's up to you to fix it!
|
Most exercises contain an error that keeps them from compiling, and it's up to you to fix it!
|
||||||
Some exercises contain tests that need to pass for the exercise to be done ✅
|
Some exercises contain tests that need to pass for the exercise to be done ✅
|
||||||
|
|
||||||
|
Search for `TODO` and `todo!()` to find out what you need to change.
|
||||||
|
Ask for hints by entering `h` in the _watch mode_ 💡
|
||||||
|
|
||||||
### Watch Mode
|
### Watch Mode
|
||||||
|
|
||||||
After [initialization](#initialization), Rustlings can be launched by simply running the command `rustlings`.
|
After [initialization](#initialization), Rustlings can be launched by simply running the command `rustlings`.
|
||||||
|
@ -118,11 +117,15 @@ See the footer of the list for all possible keys.
|
||||||
|
|
||||||
## Continuing On
|
## Continuing On
|
||||||
|
|
||||||
<!-- TODO: Mention third-party exercises -->
|
|
||||||
|
|
||||||
Once you've completed Rustlings, put your new knowledge to good use!
|
Once you've completed Rustlings, put your new knowledge to good use!
|
||||||
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
|
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
|
||||||
|
|
||||||
|
## Third-Party Exercises
|
||||||
|
|
||||||
|
Do you want to create your own set of Rustlings exercises to focus on some specific topic?
|
||||||
|
Or do you want to translate the original Rustlings exercises?
|
||||||
|
Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXERCISES.md)!
|
||||||
|
|
||||||
## Uninstalling Rustlings
|
## Uninstalling Rustlings
|
||||||
|
|
||||||
If you want to remove Rustlings from your system, run the following command:
|
If you want to remove Rustlings from your system, run the following command:
|
||||||
|
|
53
THIRD_PARTY_EXERCISES.md
Normal file
53
THIRD_PARTY_EXERCISES.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Third-Party Exercises
|
||||||
|
|
||||||
|
The support of Rustlings for third-party exercises allows you to create your own set of Rustlings exercises to focus on some specific topic.
|
||||||
|
You could also offer a translatation of the original Rustlings exercises as third-party exercises.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
To create third-party exercises, install Rustlings and run `rustlings dev new PROJECT_NAME`.
|
||||||
|
This command will, similar to `cargo new PROJECT_NAME`, create a template directory called `PROJECT_NAME` with all what you need to get started.
|
||||||
|
|
||||||
|
Read the comments in the generated `info.toml` file to understand its format.
|
||||||
|
It allows you to set a custom welcome and final message and specify the metadata of every exercise.
|
||||||
|
|
||||||
|
## Create an exercise
|
||||||
|
|
||||||
|
Here is an example of the metadata of one file:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[exercises]]
|
||||||
|
name = "intro1"
|
||||||
|
hint = """
|
||||||
|
To finish this exercise, you need to …
|
||||||
|
This link might help you …"""
|
||||||
|
```
|
||||||
|
|
||||||
|
After entering this in `info.toml`, create the file `intro1.rs` in the `exercises/` directory.
|
||||||
|
The exercise needs to contain a `main` function, but it can be empty.
|
||||||
|
Adding tests is recommended.
|
||||||
|
Look at the official Rustlings exercises for inspiration.
|
||||||
|
|
||||||
|
You can optionally add a solution file `intro1.rs` to the `solutions/` directory.
|
||||||
|
|
||||||
|
Now, run `rustlings dev check`.
|
||||||
|
It will tell you about any issues with your exercises.
|
||||||
|
For example, it will tell you to run `rustlings dev update` to update the `Cargo.toml` file to include the new exercise `intro1`.
|
||||||
|
|
||||||
|
`rustlings dev check` will also run your solutions (if you have any) to make sure that they run successfully.
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
You finished your first exercise 🎉
|
||||||
|
|
||||||
|
## Publish
|
||||||
|
|
||||||
|
Now, add more exercises and publish them as a Git repository.
|
||||||
|
|
||||||
|
Users just have to clone that repository and run `rustlings` in it to start working on your set of exercises just like the official ones.
|
||||||
|
|
||||||
|
One difference to the official exercises is that the solution files will not be hidden until the user finishes an exercise.
|
||||||
|
But you can trust the users to not look at the solution too early 😉
|
||||||
|
|
||||||
|
## Share
|
||||||
|
|
||||||
|
After publishing your set of exercises, open an issue or a pull request in the official Rustlings repository to link to your project in the README 😃
|
|
@ -1,5 +1,5 @@
|
||||||
// We sometimes encourage you to keep trying things on a given exercise, even
|
// TODO: We sometimes encourage you to keep trying things on a given exercise,
|
||||||
// after you already figured it out. If you got everything working and feel
|
// even after you already figured it out. If you got everything working and feel
|
||||||
// ready for the next exercise, enter `n` in the terminal.
|
// ready for the next exercise, enter `n` in the terminal.
|
||||||
//
|
//
|
||||||
// The exercise file will be reloaded when you change one of the lines below!
|
// The exercise file will be reloaded when you change one of the lines below!
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// TODO: Fix the code to print "Hello world!".
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
// TODO: Fix the code to print "Hello world!".
|
||||||
printline!("Hello world!");
|
printline!("Hello world!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// TODO: Fix the compiler error.
|
// TODO: Fix the compiler error.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 3;
|
let x = 3;
|
||||||
println!("Number {x}");
|
println!("Number {x}");
|
||||||
|
|
|
@ -3,7 +3,7 @@ trait AppendBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement the trait `AppendBar` for a vector of strings.
|
// TODO: Implement the trait `AppendBar` for a vector of strings.
|
||||||
// `appned_bar` should push the string "Bar" into the vector.
|
// `append_bar` should push the string "Bar" into the vector.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// You can optionally experiment here.
|
// You can optionally experiment here.
|
||||||
|
|
|
@ -18,7 +18,7 @@ impl Queue {
|
||||||
|
|
||||||
fn send_tx(q: Queue, tx: mpsc::Sender<u32>) {
|
fn send_tx(q: Queue, tx: mpsc::Sender<u32>) {
|
||||||
// TODO: We want to send `tx` to both threads. But currently, it is moved
|
// TODO: We want to send `tx` to both threads. But currently, it is moved
|
||||||
// into the frist thread. How could you solve this problem?
|
// into the first thread. How could you solve this problem?
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for val in q.first_half {
|
for val in q.first_half {
|
||||||
println!("Sending {val:?}");
|
println!("Sending {val:?}");
|
||||||
|
|
|
@ -3,22 +3,21 @@
|
||||||
// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively.
|
// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively.
|
||||||
|
|
||||||
// Obtain the number of bytes (not characters) in the given argument.
|
// Obtain the number of bytes (not characters) in the given argument.
|
||||||
// TODO: Add the AsRef trait appropriately as a trait bound.
|
// TODO: Add the `AsRef` trait appropriately as a trait bound.
|
||||||
fn byte_counter<T>(arg: T) -> usize {
|
fn byte_counter<T>(arg: T) -> usize {
|
||||||
arg.as_ref().as_bytes().len()
|
arg.as_ref().as_bytes().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain the number of characters (not bytes) in the given argument.
|
// Obtain the number of characters (not bytes) in the given argument.
|
||||||
// TODO: Add the AsRef trait appropriately as a trait bound.
|
// TODO: Add the `AsRef` trait appropriately as a trait bound.
|
||||||
fn char_counter<T>(arg: T) -> usize {
|
fn char_counter<T>(arg: T) -> usize {
|
||||||
arg.as_ref().chars().count()
|
arg.as_ref().chars().count()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Squares a number using as_mut().
|
// Squares a number using `as_mut()`.
|
||||||
// TODO: Add the appropriate trait bound.
|
// TODO: Add the appropriate trait bound.
|
||||||
fn num_sq<T>(arg: &mut T) {
|
fn num_sq<T>(arg: &mut T) {
|
||||||
// TODO: Implement the function body.
|
// TODO: Implement the function body.
|
||||||
???
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,81 +1,79 @@
|
||||||
// The From trait is used for value-to-value conversions. If From is implemented
|
// The `From` trait is used for value-to-value conversions. If `From` is
|
||||||
// correctly for a type, the Into trait should work conversely. You can read
|
// implemented, an implementation of `Into` is automatically provided.
|
||||||
// more about it at https://doc.rust-lang.org/std/convert/trait.From.html
|
// You can read more about it in the documentation:
|
||||||
|
// https://doc.rust-lang.org/std/convert/trait.From.html
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Person {
|
struct Person {
|
||||||
name: String,
|
name: String,
|
||||||
age: usize,
|
age: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We implement the Default trait to use it as a fallback
|
// We implement the Default trait to use it as a fallback when the provided
|
||||||
// when the provided string is not convertible into a Person object
|
// string is not convertible into a `Person` object.
|
||||||
impl Default for Person {
|
impl Default for Person {
|
||||||
fn default() -> Person {
|
fn default() -> Self {
|
||||||
Person {
|
Self {
|
||||||
name: String::from("John"),
|
name: String::from("John"),
|
||||||
age: 30,
|
age: 30,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your task is to complete this implementation in order for the line `let p1 =
|
// TODO: Complete this `From` implementation to be able to parse a `Person`
|
||||||
// Person::from("Mark,20")` to compile. Please note that you'll need to parse the
|
// out of a string in the form of "Mark,20".
|
||||||
// age component into a `usize` with something like `"4".parse::<usize>()`. The
|
// Note that you'll need to parse the age component into a `u8` with something
|
||||||
// outcome of this needs to be handled appropriately.
|
// like `"4".parse::<u8>()`.
|
||||||
//
|
//
|
||||||
// Steps:
|
// Steps:
|
||||||
// 1. If the length of the provided string is 0, then return the default of
|
// 1. Split the given string on the commas present in it.
|
||||||
// Person.
|
// 2. If the split operation returns less or more than 2 elements, return the
|
||||||
// 2. Split the given string on the commas present in it.
|
// default of `Person`.
|
||||||
// 3. Extract the first element from the split operation and use it as the name.
|
// 3. Use the first element from the split operation as the name.
|
||||||
// 4. If the name is empty, then return the default of Person.
|
// 4. If the name is empty, return the default of `Person`.
|
||||||
// 5. Extract the other element from the split operation and parse it into a
|
// 5. Parse the second element from the split operation into a `u8` as the age.
|
||||||
// `usize` as the age.
|
// 6. If parsing the age fails, return the default of `Person`.
|
||||||
// If while parsing the age, something goes wrong, then return the default of
|
|
||||||
// Person. Otherwise, then return an instantiated Person object with the results
|
|
||||||
|
|
||||||
impl From<&str> for Person {
|
impl From<&str> for Person {
|
||||||
fn from(s: &str) -> Person {}
|
fn from(s: &str) -> Self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Use the `from` function
|
// Use the `from` function.
|
||||||
let p1 = Person::from("Mark,20");
|
let p1 = Person::from("Mark,20");
|
||||||
// Since From is implemented for Person, we should be able to use Into
|
println!("{p1:?}");
|
||||||
|
|
||||||
|
// Since `From` is implemented for Person, we are able to use `Into`.
|
||||||
let p2: Person = "Gerald,70".into();
|
let p2: Person = "Gerald,70".into();
|
||||||
println!("{:?}", p1);
|
println!("{p2:?}");
|
||||||
println!("{:?}", p2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default() {
|
fn test_default() {
|
||||||
// Test that the default person is 30 year old John
|
|
||||||
let dp = Person::default();
|
let dp = Person::default();
|
||||||
assert_eq!(dp.name, "John");
|
assert_eq!(dp.name, "John");
|
||||||
assert_eq!(dp.age, 30);
|
assert_eq!(dp.age, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bad_convert() {
|
fn test_bad_convert() {
|
||||||
// Test that John is returned when bad string is provided
|
|
||||||
let p = Person::from("");
|
let p = Person::from("");
|
||||||
assert_eq!(p.name, "John");
|
assert_eq!(p.name, "John");
|
||||||
assert_eq!(p.age, 30);
|
assert_eq!(p.age, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_good_convert() {
|
fn test_good_convert() {
|
||||||
// Test that "Mark,20" works
|
|
||||||
let p = Person::from("Mark,20");
|
let p = Person::from("Mark,20");
|
||||||
assert_eq!(p.name, "Mark");
|
assert_eq!(p.name, "Mark");
|
||||||
assert_eq!(p.age, 20);
|
assert_eq!(p.age, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bad_age() {
|
fn test_bad_age() {
|
||||||
// Test that "Mark,twenty" will return the default person due to an
|
|
||||||
// error in parsing age
|
|
||||||
let p = Person::from("Mark,twenty");
|
let p = Person::from("Mark,twenty");
|
||||||
assert_eq!(p.name, "John");
|
assert_eq!(p.name, "John");
|
||||||
assert_eq!(p.age, 30);
|
assert_eq!(p.age, 30);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// This is similar to from_into.rs, but this time we'll implement `FromStr` and
|
// This is similar to the previous `from_into` exercise. But this time, we'll
|
||||||
// return errors instead of falling back to a default value. Additionally, upon
|
// implement `FromStr` and return errors instead of falling back to a default
|
||||||
// implementing FromStr, you can use the `parse` method on strings to generate
|
// value. Additionally, upon implementing `FromStr`, you can use the `parse`
|
||||||
// an object of the implementor type. You can read more about it at
|
// method on strings to generate an object of the implementor type. You can read
|
||||||
|
// more about it in the documentation:
|
||||||
// https://doc.rust-lang.org/std/str/trait.FromStr.html
|
// https://doc.rust-lang.org/std/str/trait.FromStr.html
|
||||||
|
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
|
@ -10,53 +11,54 @@ use std::str::FromStr;
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
struct Person {
|
struct Person {
|
||||||
name: String,
|
name: String,
|
||||||
age: usize,
|
age: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will use this error type for the `FromStr` implementation.
|
// We will use this error type for the `FromStr` implementation.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum ParsePersonError {
|
enum ParsePersonError {
|
||||||
// Empty input string
|
|
||||||
Empty,
|
|
||||||
// Incorrect number of fields
|
// Incorrect number of fields
|
||||||
BadLen,
|
BadLen,
|
||||||
// Empty name field
|
// Empty name field
|
||||||
NoName,
|
NoName,
|
||||||
// Wrapped error from parse::<usize>()
|
// Wrapped error from parse::<u8>()
|
||||||
ParseInt(ParseIntError),
|
ParseInt(ParseIntError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Complete this `From` implementation to be able to parse a `Person`
|
||||||
|
// out of a string in the form of "Mark,20".
|
||||||
|
// Note that you'll need to parse the age component into a `u8` with something
|
||||||
|
// like `"4".parse::<u8>()`.
|
||||||
|
//
|
||||||
// Steps:
|
// Steps:
|
||||||
// 1. If the length of the provided string is 0, an error should be returned
|
// 1. Split the given string on the commas present in it.
|
||||||
// 2. Split the given string on the commas present in it
|
// 2. If the split operation returns less or more than 2 elements, return the
|
||||||
// 3. Only 2 elements should be returned from the split, otherwise return an
|
// error `ParsePersonError::BadLen`.
|
||||||
// error
|
// 3. Use the first element from the split operation as the name.
|
||||||
// 4. Extract the first element from the split operation and use it as the name
|
// 4. If the name is empty, return the error `ParsePersonError::NoName`.
|
||||||
// 5. Extract the other element from the split operation and parse it into a
|
// 5. Parse the second element from the split operation into a `u8` as the age.
|
||||||
// `usize` as the age with something like `"4".parse::<usize>()`
|
// 6. If parsing the age fails, return the error `ParsePersonError::ParseInt`.
|
||||||
// 6. If while extracting the name and the age something goes wrong, an error
|
|
||||||
// should be returned
|
|
||||||
// If everything goes well, then return a Result of a Person object
|
|
||||||
|
|
||||||
impl FromStr for Person {
|
impl FromStr for Person {
|
||||||
type Err = ParsePersonError;
|
type Err = ParsePersonError;
|
||||||
fn from_str(s: &str) -> Result<Person, Self::Err> {
|
|
||||||
}
|
fn from_str(s: &str) -> Result<Self, Self::Err> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let p = "Mark,20".parse::<Person>().unwrap();
|
let p = "Mark,20".parse::<Person>();
|
||||||
println!("{:?}", p);
|
println!("{p:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use ParsePersonError::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_input() {
|
fn empty_input() {
|
||||||
assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty));
|
assert_eq!("".parse::<Person>(), Err(BadLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn good_input() {
|
fn good_input() {
|
||||||
let p = "John,32".parse::<Person>();
|
let p = "John,32".parse::<Person>();
|
||||||
|
@ -65,58 +67,47 @@ mod tests {
|
||||||
assert_eq!(p.name, "John");
|
assert_eq!(p.name, "John");
|
||||||
assert_eq!(p.age, 32);
|
assert_eq!(p.age, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_age() {
|
fn missing_age() {
|
||||||
assert!(matches!(
|
assert!(matches!("John,".parse::<Person>(), Err(ParseInt(_))));
|
||||||
"John,".parse::<Person>(),
|
|
||||||
Err(ParsePersonError::ParseInt(_))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_age() {
|
fn invalid_age() {
|
||||||
assert!(matches!(
|
assert!(matches!("John,twenty".parse::<Person>(), Err(ParseInt(_))));
|
||||||
"John,twenty".parse::<Person>(),
|
|
||||||
Err(ParsePersonError::ParseInt(_))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_comma_and_age() {
|
fn missing_comma_and_age() {
|
||||||
assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen));
|
assert_eq!("John".parse::<Person>(), Err(BadLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_name() {
|
fn missing_name() {
|
||||||
assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName));
|
assert_eq!(",1".parse::<Person>(), Err(NoName));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_name_and_age() {
|
fn missing_name_and_age() {
|
||||||
assert!(matches!(
|
assert!(matches!(",".parse::<Person>(), Err(NoName | ParseInt(_))));
|
||||||
",".parse::<Person>(),
|
|
||||||
Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_name_and_invalid_age() {
|
fn missing_name_and_invalid_age() {
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
",one".parse::<Person>(),
|
",one".parse::<Person>(),
|
||||||
Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_))
|
Err(NoName | ParseInt(_)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn trailing_comma() {
|
fn trailing_comma() {
|
||||||
assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen));
|
assert_eq!("John,32,".parse::<Person>(), Err(BadLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn trailing_comma_and_some_string() {
|
fn trailing_comma_and_some_string() {
|
||||||
assert_eq!(
|
assert_eq!("John,32,man".parse::<Person>(), Err(BadLen));
|
||||||
"John,32,man".parse::<Person>(),
|
|
||||||
Err(ParsePersonError::BadLen)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// TryFrom is a simple and safe type conversion that may fail in a controlled
|
// `TryFrom` is a simple and safe type conversion that may fail in a controlled
|
||||||
// way under some circumstances. Basically, this is the same as From. The main
|
// way under some circumstances. Basically, this is the same as `From`. The main
|
||||||
// difference is that this should return a Result type instead of the target
|
// difference is that this should return a `Result` type instead of the target
|
||||||
// type itself. You can read more about it at
|
// type itself. You can read more about it in the documentation:
|
||||||
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
||||||
|
|
||||||
|
#![allow(clippy::useless_vec)]
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -13,7 +14,7 @@ struct Color {
|
||||||
blue: u8,
|
blue: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will use this error type for these `TryFrom` conversions.
|
// We will use this error type for the `TryFrom` conversions.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum IntoColorError {
|
enum IntoColorError {
|
||||||
// Incorrect length of slice
|
// Incorrect length of slice
|
||||||
|
@ -22,78 +23,67 @@ enum IntoColorError {
|
||||||
IntConversion,
|
IntConversion,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your task is to complete this implementation and return an Ok result of inner
|
// TODO: Tuple implementation.
|
||||||
// type Color. You need to create an implementation for a tuple of three
|
// Correct RGB color values must be integers in the 0..=255 range.
|
||||||
// integers, an array of three integers, and a slice of integers.
|
|
||||||
//
|
|
||||||
// Note that the implementation for tuple and array will be checked at compile
|
|
||||||
// time, but the slice implementation needs to check the slice length! Also note
|
|
||||||
// that correct RGB color values must be integers in the 0..=255 range.
|
|
||||||
|
|
||||||
// Tuple implementation
|
|
||||||
impl TryFrom<(i16, i16, i16)> for Color {
|
impl TryFrom<(i16, i16, i16)> for Color {
|
||||||
type Error = IntoColorError;
|
type Error = IntoColorError;
|
||||||
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
|
|
||||||
}
|
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array implementation
|
// TODO: Array implementation.
|
||||||
impl TryFrom<[i16; 3]> for Color {
|
impl TryFrom<[i16; 3]> for Color {
|
||||||
type Error = IntoColorError;
|
type Error = IntoColorError;
|
||||||
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
|
|
||||||
}
|
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slice implementation
|
// TODO: Slice implementation.
|
||||||
|
// This implementation needs to check the slice length.
|
||||||
impl TryFrom<&[i16]> for Color {
|
impl TryFrom<&[i16]> for Color {
|
||||||
type Error = IntoColorError;
|
type Error = IntoColorError;
|
||||||
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
|
|
||||||
}
|
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Use the `try_from` function
|
// Using the `try_from` function.
|
||||||
let c1 = Color::try_from((183, 65, 14));
|
let c1 = Color::try_from((183, 65, 14));
|
||||||
println!("{:?}", c1);
|
println!("{c1:?}");
|
||||||
|
|
||||||
// Since TryFrom is implemented for Color, we should be able to use TryInto
|
// Since `TryFrom` is implemented for `Color`, we can use `TryInto`.
|
||||||
let c2: Result<Color, _> = [183, 65, 14].try_into();
|
let c2: Result<Color, _> = [183, 65, 14].try_into();
|
||||||
println!("{:?}", c2);
|
println!("{c2:?}");
|
||||||
|
|
||||||
let v = vec![183, 65, 14];
|
let v = vec![183, 65, 14];
|
||||||
// With slice we should use `try_from` function
|
// With slice we should use the `try_from` function
|
||||||
let c3 = Color::try_from(&v[..]);
|
let c3 = Color::try_from(&v[..]);
|
||||||
println!("{:?}", c3);
|
println!("{c3:?}");
|
||||||
// or take slice within round brackets and use TryInto
|
// or put the slice within round brackets and use `try_into`.
|
||||||
let c4: Result<Color, _> = (&v[..]).try_into();
|
let c4: Result<Color, _> = (&v[..]).try_into();
|
||||||
println!("{:?}", c4);
|
println!("{c4:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use IntoColorError::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tuple_out_of_range_positive() {
|
fn test_tuple_out_of_range_positive() {
|
||||||
assert_eq!(
|
assert_eq!(Color::try_from((256, 1000, 10000)), Err(IntConversion));
|
||||||
Color::try_from((256, 1000, 10000)),
|
|
||||||
Err(IntoColorError::IntConversion)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tuple_out_of_range_negative() {
|
fn test_tuple_out_of_range_negative() {
|
||||||
assert_eq!(
|
assert_eq!(Color::try_from((-1, -10, -256)), Err(IntConversion));
|
||||||
Color::try_from((-1, -10, -256)),
|
|
||||||
Err(IntoColorError::IntConversion)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tuple_sum() {
|
fn test_tuple_sum() {
|
||||||
assert_eq!(
|
assert_eq!(Color::try_from((-1, 255, 255)), Err(IntConversion));
|
||||||
Color::try_from((-1, 255, 255)),
|
|
||||||
Err(IntoColorError::IntConversion)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tuple_correct() {
|
fn test_tuple_correct() {
|
||||||
let c: Result<Color, _> = (183, 65, 14).try_into();
|
let c: Result<Color, _> = (183, 65, 14).try_into();
|
||||||
|
@ -103,25 +93,29 @@ mod tests {
|
||||||
Color {
|
Color {
|
||||||
red: 183,
|
red: 183,
|
||||||
green: 65,
|
green: 65,
|
||||||
blue: 14
|
blue: 14,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_array_out_of_range_positive() {
|
fn test_array_out_of_range_positive() {
|
||||||
let c: Result<Color, _> = [1000, 10000, 256].try_into();
|
let c: Result<Color, _> = [1000, 10000, 256].try_into();
|
||||||
assert_eq!(c, Err(IntoColorError::IntConversion));
|
assert_eq!(c, Err(IntConversion));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_array_out_of_range_negative() {
|
fn test_array_out_of_range_negative() {
|
||||||
let c: Result<Color, _> = [-10, -256, -1].try_into();
|
let c: Result<Color, _> = [-10, -256, -1].try_into();
|
||||||
assert_eq!(c, Err(IntoColorError::IntConversion));
|
assert_eq!(c, Err(IntConversion));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_array_sum() {
|
fn test_array_sum() {
|
||||||
let c: Result<Color, _> = [-1, 255, 255].try_into();
|
let c: Result<Color, _> = [-1, 255, 255].try_into();
|
||||||
assert_eq!(c, Err(IntoColorError::IntConversion));
|
assert_eq!(c, Err(IntConversion));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_array_correct() {
|
fn test_array_correct() {
|
||||||
let c: Result<Color, _> = [183, 65, 14].try_into();
|
let c: Result<Color, _> = [183, 65, 14].try_into();
|
||||||
|
@ -135,30 +129,25 @@ mod tests {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_out_of_range_positive() {
|
fn test_slice_out_of_range_positive() {
|
||||||
let arr = [10000, 256, 1000];
|
let arr = [10000, 256, 1000];
|
||||||
assert_eq!(
|
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
|
||||||
Color::try_from(&arr[..]),
|
|
||||||
Err(IntoColorError::IntConversion)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_out_of_range_negative() {
|
fn test_slice_out_of_range_negative() {
|
||||||
let arr = [-256, -1, -10];
|
let arr = [-256, -1, -10];
|
||||||
assert_eq!(
|
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
|
||||||
Color::try_from(&arr[..]),
|
|
||||||
Err(IntoColorError::IntConversion)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_sum() {
|
fn test_slice_sum() {
|
||||||
let arr = [-1, 255, 255];
|
let arr = [-1, 255, 255];
|
||||||
assert_eq!(
|
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
|
||||||
Color::try_from(&arr[..]),
|
|
||||||
Err(IntoColorError::IntConversion)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_correct() {
|
fn test_slice_correct() {
|
||||||
let v = vec![183, 65, 14];
|
let v = vec![183, 65, 14];
|
||||||
|
@ -169,18 +158,20 @@ mod tests {
|
||||||
Color {
|
Color {
|
||||||
red: 183,
|
red: 183,
|
||||||
green: 65,
|
green: 65,
|
||||||
blue: 14
|
blue: 14,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_excess_length() {
|
fn test_slice_excess_length() {
|
||||||
let v = vec![0, 0, 0, 0];
|
let v = vec![0, 0, 0, 0];
|
||||||
assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen));
|
assert_eq!(Color::try_from(&v[..]), Err(BadLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slice_insufficient_length() {
|
fn test_slice_insufficient_length() {
|
||||||
let v = vec![0, 0];
|
let v = vec![0, 0];
|
||||||
assert_eq!(Color::try_from(&v[..]), Err(IntoColorError::BadLen));
|
assert_eq!(Color::try_from(&v[..]), Err(BadLen));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
// Type casting in Rust is done via the usage of the `as` operator. Please note
|
// Type casting in Rust is done via the usage of the `as` operator.
|
||||||
// that the `as` operator is not only used when type casting. It also helps with
|
// Note that the `as` operator is not only used when type casting. It also helps
|
||||||
// renaming imports.
|
// with renaming imports.
|
||||||
//
|
|
||||||
// The goal is to make sure that the division does not fail to compile and
|
|
||||||
// returns the proper type.
|
|
||||||
|
|
||||||
fn average(values: &[f64]) -> f64 {
|
fn average(values: &[f64]) -> f64 {
|
||||||
let total = values.iter().sum::<f64>();
|
let total = values.iter().sum::<f64>();
|
||||||
|
// TODO: Make a conversion before dividing.
|
||||||
total / values.len()
|
total / values.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1174,7 +1174,7 @@ Use the `as` operator to cast one of the operands in the last line of the
|
||||||
name = "from_into"
|
name = "from_into"
|
||||||
dir = "23_conversions"
|
dir = "23_conversions"
|
||||||
hint = """
|
hint = """
|
||||||
Follow the steps provided right before the `From` implementation"""
|
Follow the steps provided right before the `From` implementation."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "from_str"
|
name = "from_str"
|
||||||
|
@ -1183,13 +1183,11 @@ hint = """
|
||||||
The implementation of `FromStr` should return an `Ok` with a `Person` object,
|
The implementation of `FromStr` should return an `Ok` with a `Person` object,
|
||||||
or an `Err` with an error if the string is not valid.
|
or an `Err` with an error if the string is not valid.
|
||||||
|
|
||||||
This is almost like the `from_into` exercise, but returning errors instead
|
This is almost like the previous `from_into` exercise, but returning errors
|
||||||
of falling back to a default value.
|
instead of falling back to a default value.
|
||||||
|
|
||||||
Look at the test cases to see which error variants to return.
|
Another hint: You can use the `map_err` method of `Result` with a function or a
|
||||||
|
closure to wrap the error from `parse::<u8>`.
|
||||||
Another hint: You can use the `map_err` method of `Result` with a function
|
|
||||||
or a closure to wrap the error from `parse::<usize>`.
|
|
||||||
|
|
||||||
Yet another hint: If you would like to propagate errors by using the `?`
|
Yet another hint: If you would like to propagate errors by using the `?`
|
||||||
operator in your solution, you might want to look at
|
operator in your solution, you might want to look at
|
||||||
|
@ -1199,21 +1197,8 @@ https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reen
|
||||||
name = "try_from_into"
|
name = "try_from_into"
|
||||||
dir = "23_conversions"
|
dir = "23_conversions"
|
||||||
hint = """
|
hint = """
|
||||||
Follow the steps provided right before the `TryFrom` implementation.
|
Is there an implementation of `TryFrom` in the standard library that can both do
|
||||||
You can also use the example at
|
the required integer conversion and check the range of the input?
|
||||||
https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
|
||||||
|
|
||||||
Is there an implementation of `TryFrom` in the standard library that
|
|
||||||
can both do the required integer conversion and check the range of the input?
|
|
||||||
|
|
||||||
Another hint: Look at the test cases to see which error variants to return.
|
|
||||||
|
|
||||||
Yet another hint: You can use the `map_err` or `or` methods of `Result` to
|
|
||||||
convert errors.
|
|
||||||
|
|
||||||
Yet another hint: If you would like to propagate errors by using the `?`
|
|
||||||
operator in your solution, you might want to look at
|
|
||||||
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html
|
|
||||||
|
|
||||||
Challenge: Can you make the `TryFrom` implementations generic over many integer types?"""
|
Challenge: Can you make the `TryFrom` implementations generic over many integer types?"""
|
||||||
|
|
||||||
|
|
|
@ -1 +1,59 @@
|
||||||
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰
|
// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more
|
||||||
|
// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and
|
||||||
|
// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively.
|
||||||
|
|
||||||
|
// Obtain the number of bytes (not characters) in the given argument.
|
||||||
|
fn byte_counter<T: AsRef<str>>(arg: T) -> usize {
|
||||||
|
arg.as_ref().as_bytes().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain the number of characters (not bytes) in the given argument.
|
||||||
|
fn char_counter<T: AsRef<str>>(arg: T) -> usize {
|
||||||
|
arg.as_ref().chars().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Squares a number using `as_mut()`.
|
||||||
|
fn num_sq<T: AsMut<u32>>(arg: &mut T) {
|
||||||
|
let arg = arg.as_mut();
|
||||||
|
*arg = *arg * *arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// You can optionally experiment here.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn different_counts() {
|
||||||
|
let s = "Café au lait";
|
||||||
|
assert_ne!(char_counter(s), byte_counter(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_counts() {
|
||||||
|
let s = "Cafe au lait";
|
||||||
|
assert_eq!(char_counter(s), byte_counter(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn different_counts_using_string() {
|
||||||
|
let s = String::from("Café au lait");
|
||||||
|
assert_ne!(char_counter(s.clone()), byte_counter(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_counts_using_string() {
|
||||||
|
let s = String::from("Cafe au lait");
|
||||||
|
assert_eq!(char_counter(s.clone()), byte_counter(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mut_box() {
|
||||||
|
let mut num: Box<u32> = Box::new(3);
|
||||||
|
num_sq(&mut num);
|
||||||
|
assert_eq!(*num, 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,136 @@
|
||||||
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰
|
// The `From` trait is used for value-to-value conversions. If `From` is
|
||||||
|
// implemented, an implementation of `Into` is automatically provided.
|
||||||
|
// You can read more about it in the documentation:
|
||||||
|
// https://doc.rust-lang.org/std/convert/trait.From.html
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We implement the Default trait to use it as a fallback when the provided
|
||||||
|
// string is not convertible into a `Person` object.
|
||||||
|
impl Default for Person {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: String::from("John"),
|
||||||
|
age: 30,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Person {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
let mut split = s.split(',');
|
||||||
|
let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else {
|
||||||
|
// ^^^^ there should be no third element
|
||||||
|
return Self::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
if name.is_empty() {
|
||||||
|
return Self::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(age) = age.parse() else {
|
||||||
|
return Self::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
age,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Use the `from` function.
|
||||||
|
let p1 = Person::from("Mark,20");
|
||||||
|
println!("{p1:?}");
|
||||||
|
|
||||||
|
// Since `From` is implemented for Person, we are able to use `Into`.
|
||||||
|
let p2: Person = "Gerald,70".into();
|
||||||
|
println!("{p2:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default() {
|
||||||
|
let dp = Person::default();
|
||||||
|
assert_eq!(dp.name, "John");
|
||||||
|
assert_eq!(dp.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_convert() {
|
||||||
|
let p = Person::from("");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_good_convert() {
|
||||||
|
let p = Person::from("Mark,20");
|
||||||
|
assert_eq!(p.name, "Mark");
|
||||||
|
assert_eq!(p.age, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_age() {
|
||||||
|
let p = Person::from("Mark,twenty");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_comma_and_age() {
|
||||||
|
let p: Person = Person::from("Mark");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_age() {
|
||||||
|
let p: Person = Person::from("Mark,");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name() {
|
||||||
|
let p: Person = Person::from(",1");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name_and_age() {
|
||||||
|
let p: Person = Person::from(",");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_name_and_invalid_age() {
|
||||||
|
let p: Person = Person::from(",one");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_comma() {
|
||||||
|
let p: Person = Person::from("Mike,32,");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_comma_and_some_string() {
|
||||||
|
let p: Person = Person::from("Mike,32,dog");
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,117 @@
|
||||||
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰
|
// This is similar to the previous `from_into` exercise. But this time, we'll
|
||||||
|
// implement `FromStr` and return errors instead of falling back to a default
|
||||||
|
// value. Additionally, upon implementing `FromStr`, you can use the `parse`
|
||||||
|
// method on strings to generate an object of the implementor type. You can read
|
||||||
|
// more about it in the documentation:
|
||||||
|
// https://doc.rust-lang.org/std/str/trait.FromStr.html
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Person {
|
||||||
|
name: String,
|
||||||
|
age: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will use this error type for the `FromStr` implementation.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ParsePersonError {
|
||||||
|
// Incorrect number of fields
|
||||||
|
BadLen,
|
||||||
|
// Empty name field
|
||||||
|
NoName,
|
||||||
|
// Wrapped error from parse::<u8>()
|
||||||
|
ParseInt(ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Person {
|
||||||
|
type Err = ParsePersonError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut split = s.split(',');
|
||||||
|
let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else {
|
||||||
|
// ^^^^ there should be no third element
|
||||||
|
return Err(ParsePersonError::BadLen);
|
||||||
|
};
|
||||||
|
|
||||||
|
if name.is_empty() {
|
||||||
|
return Err(ParsePersonError::NoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
let age = age.parse().map_err(ParsePersonError::ParseInt)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
age,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let p = "Mark,20".parse::<Person>();
|
||||||
|
println!("{p:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ParsePersonError::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_input() {
|
||||||
|
assert_eq!("".parse::<Person>(), Err(BadLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn good_input() {
|
||||||
|
let p = "John,32".parse::<Person>();
|
||||||
|
assert!(p.is_ok());
|
||||||
|
let p = p.unwrap();
|
||||||
|
assert_eq!(p.name, "John");
|
||||||
|
assert_eq!(p.age, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_age() {
|
||||||
|
assert!(matches!("John,".parse::<Person>(), Err(ParseInt(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_age() {
|
||||||
|
assert!(matches!("John,twenty".parse::<Person>(), Err(ParseInt(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_comma_and_age() {
|
||||||
|
assert_eq!("John".parse::<Person>(), Err(BadLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name() {
|
||||||
|
assert_eq!(",1".parse::<Person>(), Err(NoName));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name_and_age() {
|
||||||
|
assert!(matches!(",".parse::<Person>(), Err(NoName | ParseInt(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_name_and_invalid_age() {
|
||||||
|
assert!(matches!(
|
||||||
|
",one".parse::<Person>(),
|
||||||
|
Err(NoName | ParseInt(_)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trailing_comma() {
|
||||||
|
assert_eq!("John,32,".parse::<Person>(), Err(BadLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trailing_comma_and_some_string() {
|
||||||
|
assert_eq!("John,32,man".parse::<Person>(), Err(BadLen));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,192 @@
|
||||||
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰
|
// `TryFrom` is a simple and safe type conversion that may fail in a controlled
|
||||||
|
// way under some circumstances. Basically, this is the same as `From`. The main
|
||||||
|
// difference is that this should return a `Result` type instead of the target
|
||||||
|
// type itself. You can read more about it in the documentation:
|
||||||
|
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
||||||
|
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Color {
|
||||||
|
red: u8,
|
||||||
|
green: u8,
|
||||||
|
blue: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will use this error type for the `TryFrom` conversions.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum IntoColorError {
|
||||||
|
// Incorrect length of slice
|
||||||
|
BadLen,
|
||||||
|
// Integer conversion error
|
||||||
|
IntConversion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(i16, i16, i16)> for Color {
|
||||||
|
type Error = IntoColorError;
|
||||||
|
|
||||||
|
fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
|
||||||
|
let (Ok(red), Ok(green), Ok(blue)) = (
|
||||||
|
u8::try_from(tuple.0),
|
||||||
|
u8::try_from(tuple.1),
|
||||||
|
u8::try_from(tuple.2),
|
||||||
|
) else {
|
||||||
|
return Err(IntoColorError::IntConversion);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { red, green, blue })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<[i16; 3]> for Color {
|
||||||
|
type Error = IntoColorError;
|
||||||
|
|
||||||
|
fn try_from(arr: [i16; 3]) -> Result<Self, Self::Error> {
|
||||||
|
// Reuse the implementation for a tuple.
|
||||||
|
Self::try_from((arr[0], arr[1], arr[2]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[i16]> for Color {
|
||||||
|
type Error = IntoColorError;
|
||||||
|
|
||||||
|
fn try_from(slice: &[i16]) -> Result<Self, Self::Error> {
|
||||||
|
// Check the length.
|
||||||
|
if slice.len() != 3 {
|
||||||
|
return Err(IntoColorError::BadLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse the implementation for a tuple.
|
||||||
|
Self::try_from((slice[0], slice[1], slice[2]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Using the `try_from` function.
|
||||||
|
let c1 = Color::try_from((183, 65, 14));
|
||||||
|
println!("{c1:?}");
|
||||||
|
|
||||||
|
// Since `TryFrom` is implemented for `Color`, we can use `TryInto`.
|
||||||
|
let c2: Result<Color, _> = [183, 65, 14].try_into();
|
||||||
|
println!("{c2:?}");
|
||||||
|
|
||||||
|
let v = vec![183, 65, 14];
|
||||||
|
// With slice we should use the `try_from` function
|
||||||
|
let c3 = Color::try_from(&v[..]);
|
||||||
|
println!("{c3:?}");
|
||||||
|
// or put the slice within round brackets and use `try_into`.
|
||||||
|
let c4: Result<Color, _> = (&v[..]).try_into();
|
||||||
|
println!("{c4:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use IntoColorError::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_out_of_range_positive() {
|
||||||
|
assert_eq!(Color::try_from((256, 1000, 10000)), Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_out_of_range_negative() {
|
||||||
|
assert_eq!(Color::try_from((-1, -10, -256)), Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_sum() {
|
||||||
|
assert_eq!(Color::try_from((-1, 255, 255)), Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_correct() {
|
||||||
|
let c: Result<Color, _> = (183, 65, 14).try_into();
|
||||||
|
assert!(c.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
c.unwrap(),
|
||||||
|
Color {
|
||||||
|
red: 183,
|
||||||
|
green: 65,
|
||||||
|
blue: 14,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_out_of_range_positive() {
|
||||||
|
let c: Result<Color, _> = [1000, 10000, 256].try_into();
|
||||||
|
assert_eq!(c, Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_out_of_range_negative() {
|
||||||
|
let c: Result<Color, _> = [-10, -256, -1].try_into();
|
||||||
|
assert_eq!(c, Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_sum() {
|
||||||
|
let c: Result<Color, _> = [-1, 255, 255].try_into();
|
||||||
|
assert_eq!(c, Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_array_correct() {
|
||||||
|
let c: Result<Color, _> = [183, 65, 14].try_into();
|
||||||
|
assert!(c.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
c.unwrap(),
|
||||||
|
Color {
|
||||||
|
red: 183,
|
||||||
|
green: 65,
|
||||||
|
blue: 14
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_out_of_range_positive() {
|
||||||
|
let arr = [10000, 256, 1000];
|
||||||
|
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_out_of_range_negative() {
|
||||||
|
let arr = [-256, -1, -10];
|
||||||
|
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_sum() {
|
||||||
|
let arr = [-1, 255, 255];
|
||||||
|
assert_eq!(Color::try_from(&arr[..]), Err(IntConversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_correct() {
|
||||||
|
let v = vec![183, 65, 14];
|
||||||
|
let c: Result<Color, _> = Color::try_from(&v[..]);
|
||||||
|
assert!(c.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
c.unwrap(),
|
||||||
|
Color {
|
||||||
|
red: 183,
|
||||||
|
green: 65,
|
||||||
|
blue: 14,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_excess_length() {
|
||||||
|
let v = vec![0, 0, 0, 0];
|
||||||
|
assert_eq!(Color::try_from(&v[..]), Err(BadLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_insufficient_length() {
|
||||||
|
let v = vec![0, 0];
|
||||||
|
assert_eq!(Color::try_from(&v[..]), Err(BadLen));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,24 @@
|
||||||
// Solutions will be available before the stable release. Thank you for testing the beta version 🥰
|
// Type casting in Rust is done via the usage of the `as` operator.
|
||||||
|
// Note that the `as` operator is not only used when type casting. It also helps
|
||||||
|
// with renaming imports.
|
||||||
|
|
||||||
|
fn average(values: &[f64]) -> f64 {
|
||||||
|
let total = values.iter().sum::<f64>();
|
||||||
|
total / values.len() as f64
|
||||||
|
// ^^^^^^
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let values = [3.5, 0.3, 13.0, 11.7];
|
||||||
|
println!("{}", average(&values));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn returns_proper_type_and_value() {
|
||||||
|
assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,11 @@ use std::{
|
||||||
fs::{self, read_dir, OpenOptions},
|
fs::{self, read_dir, OpenOptions},
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::{
|
||||||
|
atomic::{self, AtomicBool},
|
||||||
|
Mutex,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -167,36 +172,52 @@ fn check_exercises(info_file: &InfoFile) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_solutions(require_solutions: bool, info_file: &InfoFile) -> Result<()> {
|
fn check_solutions(require_solutions: bool, info_file: &InfoFile) -> Result<()> {
|
||||||
let mut paths = hashbrown::HashSet::with_capacity(info_file.exercises.len());
|
|
||||||
let target_dir = parse_target_dir()?;
|
let target_dir = parse_target_dir()?;
|
||||||
let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
|
let paths = Mutex::new(hashbrown::HashSet::with_capacity(info_file.exercises.len()));
|
||||||
|
let error_occurred = AtomicBool::new(false);
|
||||||
|
|
||||||
for exercise_info in &info_file.exercises {
|
println!("Running all solutions. This may take a while...\n");
|
||||||
let path = exercise_info.sol_path();
|
thread::scope(|s| {
|
||||||
if !Path::new(&path).exists() {
|
for exercise_info in &info_file.exercises {
|
||||||
if require_solutions {
|
s.spawn(|| {
|
||||||
bail!("Exercise {} is missing a solution", exercise_info.name);
|
let error = |e| {
|
||||||
}
|
let mut stderr = io::stderr().lock();
|
||||||
|
stderr.write_all(e).unwrap();
|
||||||
|
stderr
|
||||||
|
.write_all(b"\nFailed to run the solution of the exercise ")
|
||||||
|
.unwrap();
|
||||||
|
stderr.write_all(exercise_info.name.as_bytes()).unwrap();
|
||||||
|
stderr.write_all(SEPARATOR).unwrap();
|
||||||
|
error_occurred.store(true, atomic::Ordering::Relaxed);
|
||||||
|
};
|
||||||
|
|
||||||
// No solution to check.
|
let path = exercise_info.sol_path();
|
||||||
continue;
|
if !Path::new(&path).exists() {
|
||||||
|
if require_solutions {
|
||||||
|
error(b"Solution missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No solution to check.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
|
||||||
|
match exercise_info.run_solution(&mut output, &target_dir) {
|
||||||
|
Ok(true) => {
|
||||||
|
paths.lock().unwrap().insert(PathBuf::from(path));
|
||||||
|
}
|
||||||
|
Ok(false) => error(&output),
|
||||||
|
Err(e) => error(e.to_string().as_bytes()),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
println!("Running the solution of {}", exercise_info.name);
|
if error_occurred.load(atomic::Ordering::Relaxed) {
|
||||||
let success = exercise_info.run_solution(&mut output, &target_dir)?;
|
bail!("At least one solution failed. See the output above.");
|
||||||
if !success {
|
|
||||||
io::stderr().write_all(&output)?;
|
|
||||||
|
|
||||||
bail!(
|
|
||||||
"Failed to run the solution of the exercise {}",
|
|
||||||
exercise_info.name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
paths.insert(PathBuf::from(path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check_unexpected_files("solutions", &paths)?;
|
check_unexpected_files("solutions", &paths.into_inner().unwrap())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -224,3 +245,6 @@ pub fn check(require_solutions: bool) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SEPARATOR: &[u8] =
|
||||||
|
b"\n========================================================================================\n";
|
||||||
|
|
|
@ -101,7 +101,7 @@ impl<'a> WatchState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.done_status != DoneStatus::Pending {
|
if self.done_status != DoneStatus::Pending {
|
||||||
write!(self.writer, "{}:next / ", 'n'.bold())?;
|
write!(self.writer, "{}:{} / ", 'n'.bold(), "next".underlined())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.show_hint {
|
if !self.show_hint {
|
||||||
|
|
Loading…
Reference in a new issue