Render the book in PDF using pandoc and LaTeX. (#126)

* Render the book in PDF using `pandoc` and LaTeX.

* Fix installs.

* Go the apt-get route

* Another attempt

* Avoid installing twice.

* Re-order.

* Add more packages.

* Minimise deps. Fix link checker.

* Missing package.

* Missing package.

* Missing package.

* More packages.

* Missing package.

* Missing package.

* More packages...

* Remove.

* Fix link checker.

* Fix link checker.

* Fix path.

* Add subtitle.

* Avoid running over the right margin.

* Avoid running over the right margin.

* Formatting
This commit is contained in:
Luca Palmieri
2024-08-05 17:52:15 +02:00
committed by GitHub
parent e732ea82e4
commit 96f06708b0
25 changed files with 401 additions and 213 deletions

View File

@@ -72,7 +72,8 @@ error: literal out of range for `i8`
4 | let a = 255 as i8;
| ^^^
|
= note: the literal `255` does not fit into the type `i8` whose range is `-128..=127`
= note: the literal `255` does not fit into the type `i8`
whose range is `-128..=127`
= help: consider using the type `u8` instead
= note: `#[deny(overflowing_literals)]` on by default
```

View File

@@ -48,7 +48,7 @@ You can create an instance of a struct by specifying the values for each field:
// Syntax: <StructName> { <field_name>: <value>, ... }
let ticket = Ticket {
title: "Build a ticket system".into(),
description: "Create a system that can manage tickets across a Kanban board".into(),
description: "A Kanban board".into(),
status: "Open".into()
};
```
@@ -130,7 +130,8 @@ let default_config = Configuration::default();
You can use the function call syntax even for methods that take `self` as their first parameter:
```rust
// Function call syntax: <StructName>::<method_name>(<instance>, <parameters>)
// Function call syntax:
// <StructName>::<method_name>(<instance>, <parameters>)
let is_open = Ticket::is_open(ticket);
```

View File

@@ -81,7 +81,7 @@ You have to use a **path** pointing to the entity you want to access.
You can compose the path in various ways:
- starting from the root of the current crate, e.g. `crate::module_1::module_2::MyStruct`
- starting from the root of the current crate, e.g. `crate::module_1::MyStruct`
- starting from the parent module, e.g. `super::my_function`
- starting from the current module, e.g. `sub_module_1::MyStruct`

View File

@@ -30,7 +30,7 @@ instantiation syntax:
// This won't work!
let ticket = Ticket {
title: "Build a ticket system".into(),
description: "Create a system that can manage tickets across a Kanban board".into(),
description: "A Kanban board".into(),
status: "Open".into()
};
```

View File

@@ -44,9 +44,11 @@ error[E0382]: use of moved value: `ticket`
| -------- `ticket` moved due to this method call
...
30 | println!("Your next task is: {}", ticket.title());
| ^^^^^^ value used here after move
| ^^^^^^
| value used here after move
|
note: `Ticket::status` takes ownership of the receiver `self`, which moves `ticket`
note: `Ticket::status` takes ownership of the receiver `self`,
which moves `ticket`
--> src/main.rs:12:23
|
12 | pub fn status(self) -> String {
@@ -130,9 +132,11 @@ error[E0382]: use of moved value: `ticket`
| -------- `ticket` moved due to this method call
...
30 | println!("Your next task is: {}", ticket.title());
| ^^^^^^ value used here after move
| ^^^^^^
| value used here after move
|
note: `Ticket::status` takes ownership of the receiver `self`, which moves `ticket`
note: `Ticket::status` takes ownership of the receiver `self`,
which moves `ticket`
--> src/main.rs:12:23
|
12 | pub fn status(self) -> String {
@@ -199,8 +203,10 @@ fn main() {
active: true,
};
// `b` is a reference to the `version` field of `config`.
// The type of `b` is `&u32`, since it contains a reference to a `u32` value.
// We create a reference by borrowing `config.version`, using the `&` operator.
// The type of `b` is `&u32`, since it contains a reference to
// a `u32` value.
// We create a reference by borrowing `config.version`, using
// the `&` operator.
// Same symbol (`&`), different meaning depending on the context!
let b: &u32 = &config.version;
// ^ The type annotation is not necessary,

View File

@@ -50,7 +50,11 @@ It takes ownership of `self`, changes the title, and returns the modified `Ticke
This is how you'd use it:
```rust
let ticket = Ticket::new("Title".into(), "Description".into(), "To-Do".into());
let ticket = Ticket::new(
"Title".into(),
"Description".into(),
"To-Do".into()
);
let ticket = ticket.set_title("New title".into());
```
@@ -88,7 +92,11 @@ Nothing is returned.
You'd use it like this:
```rust
let mut ticket = Ticket::new("Title".into(), "Description".into(), "To-Do".into());
let mut ticket = Ticket::new(
"Title".into(),
"Description".into(),
"To-Do".into()
);
ticket.set_title("New title".into());
// Use the modified ticket

View File

@@ -18,11 +18,25 @@ the function's arguments, local variables and a few "bookkeeping" values.\
When the function returns, the stack frame is popped off the stack[^stack-overflow].
```text
+-----------------+
func2 | frame for func2 | func2
+-----------------+ is called +-----------------+ returns +-----------------+
| frame for func1 | -----------> | frame for func1 | ---------> | frame for func1 |
+-----------------+ +-----------------+ +-----------------+
+-----------------+
| frame for func1 |
+-----------------+
|
| func2 is
| called
v
+-----------------+
| frame for func2 |
+-----------------+
| frame for func1 |
+-----------------+
|
| func2
| returns
v
+-----------------+
| frame for func1 |
+-----------------+
```
From an operational point of view, stack allocation/de-allocation is **very fast**.\

View File

@@ -94,7 +94,8 @@ fn print_if_even<T>(n: T) {
This code won't compile:
```text
error[E0599]: no method named `is_even` found for type parameter `T` in the current scope
error[E0599]: no method named `is_even` found for type parameter `T`
in the current scope
--> src/lib.rs:2:10
|
1 | fn print_if_even<T>(n: T) {
@@ -106,7 +107,9 @@ error[E0277]: `T` doesn't implement `Debug`
--> src/lib.rs:3:19
|
3 | println!("{n:?} is even");
| ^^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `Debug`
| ^^^^^
| `T` cannot be formatted using `{:?}` because
| it doesn't implement `Debug`
|
help: consider restricting type parameter `T`
|

View File

@@ -72,7 +72,8 @@ You can, for example, create a `&str` from a `String` like this:
```rust
let mut s = String::with_capacity(5);
s.push_str("Hello");
// Create a string slice reference from the `String`, skipping the first byte.
// Create a string slice reference from the `String`,
// skipping the first byte.
let slice: &str = &s[1..];
```

View File

@@ -11,7 +11,8 @@ to the pointer: the length of the slice it points to. Going back to the example
```rust
let mut s = String::with_capacity(5);
s.push_str("Hello");
// Create a string slice reference from the `String`, skipping the first byte.
// Create a string slice reference from the `String`,
// skipping the first byte.
let slice: &str = &s[1..];
```

View File

@@ -3,7 +3,11 @@
Let's go back to where our string journey started:
```rust
let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into());
let ticket = Ticket::new(
"A title".into(),
"A description".into(),
"To-Do".into()
);
```
We now know enough to start unpacking what `.into()` is doing here.
@@ -14,7 +18,11 @@ This is the signature of the `new` method:
```rust
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Self {
pub fn new(
title: String,
description: String,
status: String
) -> Self {
// [...]
}
}

View File

@@ -44,7 +44,8 @@ impl Drop for MyType {
The compiler will complain with this error message:
```text
error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
error[E0184]: the trait `Copy` cannot be implemented for this type;
the type has a destructor
--> src/lib.rs:2:17
|
2 | #[derive(Clone, Copy)]

View File

@@ -14,7 +14,11 @@ pub struct Ticket {
}
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Self {
pub fn new(
title: String,
description: String,
status: String
) -> Self {
// [...]
}
}

View File

@@ -8,7 +8,10 @@ impl Ticket {
match &self.status {
Status::InProgress { assigned_to } => assigned_to,
Status::Done | Status::ToDo => {
panic!("Only `In-Progress` tickets can be assigned to someone")
panic!(
"Only `In-Progress` tickets can be \
assigned to someone"
)
}
}
}
@@ -33,7 +36,9 @@ impl Ticket {
if let Status::InProgress { assigned_to } = &self.status {
assigned_to
} else {
panic!("Only `In-Progress` tickets can be assigned to someone");
panic!(
"Only `In-Progress` tickets can be assigned to someone"
);
}
}
}
@@ -48,7 +53,9 @@ you can use the `let/else` construct:
impl Ticket {
pub fn assigned_to(&self) -> &str {
let Status::InProgress { assigned_to } = &self.status else {
panic!("Only `In-Progress` tickets can be assigned to someone");
panic!(
"Only `In-Progress` tickets can be assigned to someone"
);
};
assigned_to
}

View File

@@ -4,7 +4,11 @@ Let's revisit the `Ticket::new` function from the previous exercise:
```rust
impl Ticket {
pub fn new(title: String, description: String, status: Status) -> Ticket {
pub fn new(
title: String,
description: String,
status: Status
) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
@@ -70,8 +74,9 @@ Rust, with `Result`, forces you to **encode fallibility in the function's signat
If a function can fail (and you want the caller to have a shot at handling the error), it must return a `Result`.
```rust
// Just by looking at the signature, you know that this function can fail.
// You can also inspect `ParseIntError` to see what kind of failures to expect.
// Just by looking at the signature, you know that this function
// can fail. You can also inspect `ParseIntError` to see what
// kind of failures to expect.
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
// ...
}

View File

@@ -14,8 +14,8 @@ fn parse_int(s: &str) -> Result<i32, ParseIntError> {
}
// This won't compile: we're not handling the error case.
// We must either use `match` or one of the combinators provided by `Result`
// to "unwrap" the success value or handle the error.
// We must either use `match` or one of the combinators provided by
// `Result` to "unwrap" the success value or handle the error.
let number = parse_int("42") + 2;
```

View File

@@ -11,9 +11,9 @@ error[E0597]: `v` does not live long enough
...
15 | let right = &v[split_point..];
| ^ borrowed value does not live long enough
16 | let left_handle = thread::spawn(move || left.iter().sum::<i32>());
| ------------------------------------------------
argument requires that `v` is borrowed for `'static`
16 | let left_handle = spawn(move || left.iter().sum::<i32>());
| --------------------------------
argument requires that `v` is borrowed for `'static`
19 | }
| - `v` dropped here while still borrowed
```

View File

@@ -122,8 +122,10 @@ error[E0277]: `MutexGuard<'_, i32>` cannot be sent between threads safely
12 | | });
| |_^ `MutexGuard<'_, i32>` cannot be sent between threads safely
|
= help: the trait `Send` is not implemented for `MutexGuard<'_, i32>`, which is required by `{closure@src/main.rs:10:7: 10:14}: Send`
= note: required for `std::sync::mpsc::Receiver<MutexGuard<'_, i32>>` to implement `Send`
= help: the trait `Send` is not implemented for `MutexGuard<'_, i32>`,
which is required by `{closure@src/main.rs:10:7: 10:14}: Send`
= note: required for `std::sync::mpsc::Receiver<MutexGuard<'_, i32>>`
to implement `Send`
note: required because it's used within this closure
```

View File

@@ -55,7 +55,8 @@ note: future is not `Send` as this value is used across an await
| -------- has type `Rc<i32>` which is not `Send`
12 | // A `.await` point
13 | yield_now().await;
| ^^^^^ await occurs here, with `non_send` maybe used later
| ^^^^^
| await occurs here, with `non_send` maybe used later
note: required by a bound in `tokio::spawn`
|
164 | pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
@@ -84,7 +85,10 @@ trait Future {
type Output;
// Ignore `Pin` and `Context` for now
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<Self::Output>;
}
```

View File

@@ -105,5 +105,5 @@ async fn run() {
Check out [`select!`'s documentation](https://tokio.rs/tokio/tutorial/select) for more details.\
If you need to interleave two asynchronous streams of data (e.g. a socket and a channel), prefer using
[`StreamExt::merge`](https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge) instead.
- Rather than "abrupt" cancellation, it can be preferable to rely
on [`CancellationToken`](https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html).
- A [`CancellationToken`](https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html) may be
preferable to `JoinHandle::abort` in some cases.

View File

@@ -48,5 +48,5 @@ check out the [Embedded Rust book](https://docs.rust-embedded.org/book/).
You can then find resources on key topics that cut across domains.\
For testing, check out
["Advanced testing, going beyond the basics"](https://github.com/mainmatter/rust-advanced-testing-workshop).\
For telemetry, check out ["You can't fix what you can't see"](https://github.com/mainmatter/rust-telemetry-workshop).
["Advanced testing, going beyond the basics"](https://rust-exercises.com/advanced-testing/).\
For telemetry, check out ["You can't fix what you can't see"](https://rust-exercises.com/telemetry/).