You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-06-26 02:31:00 +02:00
Format all Markdown files with dprint
(#1157)
This is the result of running `dprint fmt` after removing `src/` from the list of excluded directories. This also reformats the Rust code: we might want to tweak this a bit in the future since some of the changes removes the hand-formatting. Of course, this formatting can be seen as a mis-feature, so maybe this is good overall. Thanks to mdbook-i18n-helpers 0.2, the POT file is nearly unchanged after this, meaning that all existing translations remain valid! A few messages were changed because of stray whitespace characters: msgid "" "Slices always borrow from another object. In this example, `a` has to remain " -"'alive' (in scope) for at least as long as our slice. " +"'alive' (in scope) for at least as long as our slice." msgstr "" The formatting is enforced in CI and we will have to see how annoying this is in practice for the many contributors. If it becomes annoying, we should look into fixing dprint/check#11 so that `dprint` can annotate the lines that need fixing directly, then I think we can consider more strict formatting checks. I added more customization to `rustfmt.toml`. This is to better emulate the dense style used in the course: - `max_width = 85` allows lines to take up the full width available in our code blocks (when taking margins and the line numbers into account). - `wrap_comments = true` ensures that we don't show very long comments in the code examples. I edited some comments to shorten them and avoid unnecessary line breaks — please trim other unnecessarily long comments when you see them! Remember we're writing code for slides 😄 - `use_small_heuristics = "Max"` allows for things like struct literals and if-statements to take up the full line width configured above. The formatting settings apply to all our Rust code right now — I think we could improve this with https://github.com/dprint/dprint/issues/711 which lets us add per-directory `dprint` configuration files. However, the `inherit: true` setting is not yet implemented (as far as I can tell), so a nested configuration file will have to copy most or all of the top-level file.
This commit is contained in:
@ -5,8 +5,8 @@ edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
thiserror = "*"
|
||||
anyhow = "*"
|
||||
thiserror = "*"
|
||||
|
||||
[[bin]]
|
||||
name = "parser"
|
||||
|
@ -5,8 +5,8 @@ minutes: 5
|
||||
# Dynamic Error Types
|
||||
|
||||
Sometimes we want to allow any type of error to be returned without writing our
|
||||
own enum covering all the different possibilities. The `std::error::Error`
|
||||
trait makes it easy to create a trait object that can contain any error.
|
||||
own enum covering all the different possibilities. The `std::error::Error` trait
|
||||
makes it easy to create a trait object that can contain any error.
|
||||
|
||||
```rust,editable
|
||||
use std::error::Error;
|
||||
@ -34,9 +34,10 @@ fn main() {
|
||||
The `read_count` function can return `std::io::Error` (from file operations) or
|
||||
`std::num::ParseIntError` (from `String::parse`).
|
||||
|
||||
Boxing errors saves on code, but gives up the ability to cleanly handle different error cases differently in
|
||||
the program. As such it's generally not a good idea to use `Box<dyn Error>` in the public API of a
|
||||
library, but it can be a good option in a program where you just want to display the error message
|
||||
Boxing errors saves on code, but gives up the ability to cleanly handle
|
||||
different error cases differently in the program. As such it's generally not a
|
||||
good idea to use `Box<dyn Error>` in the public API of a library, but it can be
|
||||
a good option in a program where you just want to display the error message
|
||||
somewhere.
|
||||
|
||||
Make sure to implement the `std::error::Error` trait when defining a custom
|
||||
|
@ -101,7 +101,9 @@ enum ParserError {
|
||||
fn parse(input: &str) -> Result<Expression, ParserError> {
|
||||
let mut tokens = tokenize(input);
|
||||
|
||||
fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Result<Expression, ParserError> {
|
||||
fn parse_expr<'a>(
|
||||
tokens: &mut Tokenizer<'a>,
|
||||
) -> Result<Expression, ParserError> {
|
||||
let tok = tokens.next().ok_or(ParserError::UnexpectedEOF)??;
|
||||
let expr = match tok {
|
||||
Token::Number(num) => {
|
||||
@ -114,9 +116,11 @@ fn parse(input: &str) -> Result<Expression, ParserError> {
|
||||
// Look ahead to parse a binary operation if present.
|
||||
Ok(match tokens.next() {
|
||||
None => expr,
|
||||
Some(Ok(Token::Operator(op))) => {
|
||||
Expression::Operation(Box::new(expr), op, Box::new(parse_expr(tokens)?))
|
||||
}
|
||||
Some(Ok(Token::Operator(op))) => Expression::Operation(
|
||||
Box::new(expr),
|
||||
op,
|
||||
Box::new(parse_expr(tokens)?),
|
||||
),
|
||||
Some(Err(e)) => return Err(e.into()),
|
||||
Some(Ok(tok)) => return Err(ParserError::UnexpectedToken(tok)),
|
||||
})
|
||||
@ -187,9 +191,11 @@ fn parse(input: &str) -> Expression {
|
||||
// Look ahead to parse a binary operation if present.
|
||||
match tokens.next() {
|
||||
None => expr,
|
||||
Some(Token::Operator(op)) => {
|
||||
Expression::Operation(Box::new(expr), op, Box::new(parse_expr(tokens)))
|
||||
}
|
||||
Some(Token::Operator(op)) => Expression::Operation(
|
||||
Box::new(expr),
|
||||
op,
|
||||
Box::new(parse_expr(tokens)),
|
||||
),
|
||||
Some(tok) => panic!("Unexpected token {tok:?}"),
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,14 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
* Panics are for unrecoverable and unexpected errors.
|
||||
* Panics are symptoms of bugs in the program.
|
||||
* Runtime failures like failed bounds checks can panic
|
||||
* Assertions (such as `assert!`) panic on failure
|
||||
* Purpose-specific panics can use the `panic!` macro.
|
||||
* A panic will "unwind" the stack, dropping values just as if the functions had returned.
|
||||
* Use non-panicking APIs (such as `Vec::get`) if crashing is not acceptable.
|
||||
- Panics are for unrecoverable and unexpected errors.
|
||||
- Panics are symptoms of bugs in the program.
|
||||
- Runtime failures like failed bounds checks can panic
|
||||
- Assertions (such as `assert!`) panic on failure
|
||||
- Purpose-specific panics can use the `panic!` macro.
|
||||
- A panic will "unwind" the stack, dropping values just as if the functions had
|
||||
returned.
|
||||
- Use non-panicking APIs (such as `Vec::get`) if crashing is not acceptable.
|
||||
|
||||
<details>
|
||||
|
||||
@ -31,9 +32,7 @@ By default, a panic will cause the stack to unwind. The unwinding can be caught:
|
||||
use std::panic;
|
||||
|
||||
fn main() {
|
||||
let result = panic::catch_unwind(|| {
|
||||
"No problem here!"
|
||||
});
|
||||
let result = panic::catch_unwind(|| "No problem here!");
|
||||
println!("{result:?}");
|
||||
|
||||
let result = panic::catch_unwind(|| {
|
||||
@ -43,7 +42,8 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
- Catching is unusual; do not attempt to implement exceptions with `catch_unwind`!
|
||||
- Catching is unusual; do not attempt to implement exceptions with
|
||||
`catch_unwind`!
|
||||
- This can be useful in servers which should keep running even if a single
|
||||
request crashes.
|
||||
- This does not work if `panic = 'abort'` is set in your `Cargo.toml`.
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Solution
|
||||
|
||||
<!-- compile_fail because `mdbook test` does not allow use of `thiserror` -->
|
||||
|
||||
```rust,editable,compile_fail
|
||||
{{#include exercise.rs:solution}}
|
||||
```
|
||||
|
@ -2,16 +2,18 @@
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
# `thiserror` and `anyhow`
|
||||
# `thiserror` and `anyhow`
|
||||
|
||||
The [`thiserror`](https://docs.rs/thiserror/) and [`anyhow`](https://docs.rs/anyhow/)
|
||||
crates are widely used to simplify error handling. `thiserror` helps
|
||||
create custom error types that implement `From<T>`. `anyhow` helps with error
|
||||
handling in functions, including adding contextual information to your errors.
|
||||
The [`thiserror`](https://docs.rs/thiserror/) and
|
||||
[`anyhow`](https://docs.rs/anyhow/) crates are widely used to simplify error
|
||||
handling. `thiserror` helps create custom error types that implement `From<T>`.
|
||||
`anyhow` helps with error handling in functions, including adding contextual
|
||||
information to your errors.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::{fs, io::Read};
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
||||
@ -34,7 +36,7 @@ fn main() {
|
||||
//fs::write("config.dat", "").unwrap();
|
||||
match read_username("config.dat") {
|
||||
Ok(username) => println!("Username: {username}"),
|
||||
Err(err) => println!("Error: {err:?}"),
|
||||
Err(err) => println!("Error: {err:?}"),
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -43,20 +45,23 @@ fn main() {
|
||||
|
||||
## `thiserror`
|
||||
|
||||
* The `Error` derive macro is provided by `thiserror`, and has lots of useful
|
||||
- The `Error` derive macro is provided by `thiserror`, and has lots of useful
|
||||
attributes to help define error types in a compact way.
|
||||
* The `std::error::Error` trait is derived automatically.
|
||||
* The message from `#[error]` is used to derive the `Display` trait.
|
||||
- The `std::error::Error` trait is derived automatically.
|
||||
- The message from `#[error]` is used to derive the `Display` trait.
|
||||
|
||||
## `anyhow`
|
||||
|
||||
* `anyhow::Error` is essentially a wrapper around `Box<dyn Error>`. As such it's again generally not
|
||||
a good choice for the public API of a library, but is widely used in applications.
|
||||
* `anyhow::Result<V>` is a type alias for `Result<V, anyhow::Error>`.
|
||||
* Actual error type inside of it can be extracted for examination if necessary.
|
||||
* Functionality provided by `anyhow::Result<T>` may be familiar to Go developers, as it provides
|
||||
similar usage patterns and ergonomics to `(T, error)` from Go.
|
||||
* `anyhow::Context` is a trait implemented for the standard `Result` and `Option` types.
|
||||
`use anyhow::Context` is necessary to enable `.context()` and `.with_context()` on those types.
|
||||
- `anyhow::Error` is essentially a wrapper around `Box<dyn Error>`. As such it's
|
||||
again generally not a good choice for the public API of a library, but is
|
||||
widely used in applications.
|
||||
- `anyhow::Result<V>` is a type alias for `Result<V, anyhow::Error>`.
|
||||
- Actual error type inside of it can be extracted for examination if necessary.
|
||||
- Functionality provided by `anyhow::Result<T>` may be familiar to Go
|
||||
developers, as it provides similar usage patterns and ergonomics to
|
||||
`(T, error)` from Go.
|
||||
- `anyhow::Context` is a trait implemented for the standard `Result` and
|
||||
`Option` types. `use anyhow::Context` is necessary to enable `.context()` and
|
||||
`.with_context()` on those types.
|
||||
|
||||
</details>
|
||||
|
@ -4,7 +4,8 @@ minutes: 5
|
||||
|
||||
# Try Conversions
|
||||
|
||||
The effective expansion of `?` is a little more complicated than previously indicated:
|
||||
The effective expansion of `?` is a little more complicated than previously
|
||||
indicated:
|
||||
|
||||
```rust,ignore
|
||||
expression?
|
||||
@ -43,7 +44,7 @@ impl Display for ReadUsernameError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::IoError(e) => write!(f, "IO error: {e}"),
|
||||
Self::EmptyUsername(filename) => write!(f, "Found no username in {filename}"),
|
||||
Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,13 +74,13 @@ fn main() {
|
||||
<details>
|
||||
|
||||
The `?` operator must return a value compatible with the return type of the
|
||||
function. For `Result`, it means that the error types have to be compatible.
|
||||
A function that returns `Result<T, ErrorOuter>` can only use `?` on a value of
|
||||
function. For `Result`, it means that the error types have to be compatible. A
|
||||
function that returns `Result<T, ErrorOuter>` can only use `?` on a value of
|
||||
type `Result<U, ErrorInner>` if `ErrorOuter` and `ErrorInner` are the same type
|
||||
or if `ErrorOuter` implements `From<ErrorInner>`.
|
||||
|
||||
A common alternative to a `From` implementation is `Result::map_err`,
|
||||
especially when the conversion only happens in one place.
|
||||
A common alternative to a `From` implementation is `Result::map_err`, especially
|
||||
when the conversion only happens in one place.
|
||||
|
||||
There is no compatibility requirement for `Option`. A function returning
|
||||
`Option<T>` can use the `?` operator on `Option<U>` for arbitrary `T` and `U`
|
||||
|
@ -5,7 +5,7 @@ minutes: 5
|
||||
# Try Operator
|
||||
|
||||
Runtime errors like connection-refused or file-not-found are handled with the
|
||||
`Result` type, but matching this type on every call can be cumbersome. The
|
||||
`Result` type, but matching this type on every call can be cumbersome. The
|
||||
try-operator `?` is used to return errors to the caller. It lets you turn the
|
||||
common
|
||||
|
||||
@ -25,8 +25,8 @@ some_expression?
|
||||
We can use this to simplify our error handling code:
|
||||
|
||||
```rust,editable
|
||||
use std::{fs, io};
|
||||
use std::io::Read;
|
||||
use std::{fs, io};
|
||||
|
||||
fn read_username(path: &str) -> Result<String, io::Error> {
|
||||
let username_file_result = fs::File::open(path);
|
||||
@ -55,8 +55,12 @@ Simplify the `read_username` function to use `?`.
|
||||
|
||||
Key points:
|
||||
|
||||
* The `username` variable can be either `Ok(string)` or `Err(error)`.
|
||||
* Use the `fs::write` call to test out the different scenarios: no file, empty file, file with username.
|
||||
* Note that `main` can return a `Result<(), E>` as long as it implements `std::process:Termination`. In practice, this means that `E` implements `Debug`. The executable will print the `Err` variant and return a nonzero exit status on error.
|
||||
- The `username` variable can be either `Ok(string)` or `Err(error)`.
|
||||
- Use the `fs::write` call to test out the different scenarios: no file, empty
|
||||
file, file with username.
|
||||
- Note that `main` can return a `Result<(), E>` as long as it implements
|
||||
`std::process:Termination`. In practice, this means that `E` implements
|
||||
`Debug`. The executable will print the `Err` variant and return a nonzero exit
|
||||
status on error.
|
||||
|
||||
</details>
|
||||
|
Reference in New Issue
Block a user