Skip to content

Commit 337b2a3

Browse files
committed
update original
1 parent badad5f commit 337b2a3

11 files changed

Lines changed: 142 additions & 31 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Spellcheck
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
workflow_dispatch:
8+
9+
jobs:
10+
spellcheck:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Install aspell
17+
run: sudo apt-get update && sudo apt-get install -y aspell aspell-en
18+
19+
- name: Run spellcheck
20+
id: spellcheck
21+
run: ./ci/spellcheck.sh list
22+
23+
- name: Spellcheck Results
24+
if: always()
25+
run: |
26+
if [ "${{ steps.spellcheck.outcome }}" == "success" ]; then
27+
echo "✅ Spellcheck passed!"
28+
else
29+
echo "❌ Spellcheck found unknown words. Add valid words to ci/dictionary.txt."
30+
exit 1
31+
fi

rust-cookbook/ci/dictionary.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,19 @@ zurich
349349
enum
350350
thiserror
351351
tempfile
352+
alloc
353+
handleactormessage
354+
heapless
355+
Heapless
356+
LazyCell
357+
lazycell
358+
LazyLock
359+
lazylock
360+
oncecell
361+
stdcell
362+
stdcelllazycell
363+
stdsynclazylock
364+
tokio
365+
Tokio
366+
Waker
367+
waker

rust-cookbook/crates/concurrency/actor/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ license.workspace = true
77
publish.workspace = true
88

99
[dependencies]
10+
thiserror = "2"
1011
tokio = { version = "1", features = ["full"] }

rust-cookbook/crates/concurrency/actor/src/bin/actor_pattern.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
use thiserror::Error;
12
use tokio::sync::{mpsc, oneshot};
23

4+
#[derive(Debug, Error)]
5+
enum ActorError {
6+
#[error("failed to send message to actor")]
7+
Send(#[from] mpsc::error::SendError<Message>),
8+
#[error("actor dropped response channel")]
9+
Recv(#[from] oneshot::error::RecvError),
10+
#[error("task failed")]
11+
Join(#[from] tokio::task::JoinError),
12+
}
13+
314
// The Message enum represents commands sent to the actor.
415
enum Message {
516
UpdateLocation {
@@ -94,7 +105,7 @@ impl DriverHandle {
94105
driver_id: u32,
95106
lat: f64,
96107
lng: f64,
97-
) -> Result<(), Box<dyn std::error::Error>> {
108+
) -> Result<(), ActorError> {
98109
self.sender
99110
.send(Message::UpdateLocation {
100111
driver_id,
@@ -108,7 +119,7 @@ impl DriverHandle {
108119
async fn get_driver_status(
109120
&self,
110121
driver_id: u32,
111-
) -> Result<Option<DriverStatus>, Box<dyn std::error::Error>> {
122+
) -> Result<Option<DriverStatus>, ActorError> {
112123
let (tx, rx) = oneshot::channel();
113124
self.sender
114125
.send(Message::GetDriverStatus {
@@ -121,33 +132,37 @@ impl DriverHandle {
121132
}
122133

123134
#[tokio::main]
124-
async fn main() -> Result<(), Box<dyn std::error::Error>> {
135+
async fn main() -> Result<(), ActorError> {
125136
let handle = DriverHandle::new();
126137

127138
// Multiple clones can be sent to different tasks.
128139
let h1 = handle.clone();
129140
let h2 = handle.clone();
130141

131142
let task1 = tokio::spawn(async move {
132-
h1.update_location(1, 40.7128, -74.0060).await.unwrap();
133-
h1.update_location(1, 40.7130, -74.0062).await.unwrap();
143+
h1.update_location(1, 40.7128, -74.0060).await?;
144+
h1.update_location(1, 40.7130, -74.0062).await?;
145+
Ok::<(), ActorError>(())
134146
});
135147

136148
let task2 = tokio::spawn(async move {
137-
h2.update_location(2, 34.0522, -118.2437).await.unwrap();
149+
h2.update_location(2, 34.0522, -118.2437).await?;
150+
Ok::<(), ActorError>(())
138151
});
139152

140-
task1.await?;
141-
task2.await?;
153+
task1.await??;
154+
task2.await??;
142155

143-
let status = handle.get_driver_status(1).await?;
144-
println!("Driver 1: {:?}", status);
156+
if let Some(s) = handle.get_driver_status(1).await? {
157+
println!("Driver {}: ({}, {}), updates: {}", s.driver_id, s.lat, s.lng, s.update_count);
158+
}
145159

146-
let status = handle.get_driver_status(2).await?;
147-
println!("Driver 2: {:?}", status);
160+
if let Some(s) = handle.get_driver_status(2).await? {
161+
println!("Driver {}: ({}, {}), updates: {}", s.driver_id, s.lat, s.lng, s.update_count);
162+
}
148163

149-
let status = handle.get_driver_status(99).await?;
150-
println!("Driver 99: {:?}", status);
164+
let missing = handle.get_driver_status(99).await?;
165+
println!("Driver 99: {:?}", missing);
151166

152167
Ok(())
153168
}

rust-cookbook/crates/safety_critical/heapless_alloc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ publish.workspace = true
88

99
[dependencies]
1010
heapless = "0.8"
11+
thiserror = "2"

rust-cookbook/crates/safety_critical/heapless_alloc/src/bin/heapless_alloc.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
use heapless::{String, Vec};
2+
use thiserror::Error;
3+
4+
#[derive(Debug, Error)]
5+
enum LogError {
6+
#[error("log full (capacity exceeded)")]
7+
Full,
8+
#[error("log is empty")]
9+
Empty,
10+
#[error(transparent)]
11+
Fmt(#[from] core::fmt::Error),
12+
}
213

314
/// A fixed-capacity event log that never touches the heap.
415
///
@@ -29,9 +40,9 @@ impl<const N: usize> EventLog<N> {
2940

3041
/// Records an event. Returns `Err` if the log is full instead
3142
/// of panicking or allocating—the caller decides what to do.
32-
fn record(&mut self, timestamp_ms: u32, code: u16) -> Result<(), Event> {
43+
fn record(&mut self, timestamp_ms: u32, code: u16) -> Result<(), LogError> {
3344
let event = Event { timestamp_ms, code };
34-
self.entries.push(event).map_err(|e| e)
45+
self.entries.push(event).map_err(|_| LogError::Full)
3546
}
3647

3748
/// Returns how many events have been recorded.
@@ -40,8 +51,8 @@ impl<const N: usize> EventLog<N> {
4051
}
4152

4253
/// Returns the most recent event, if any.
43-
fn latest(&self) -> Option<&Event> {
44-
self.entries.last()
54+
fn latest(&self) -> Result<&Event, LogError> {
55+
self.entries.last().ok_or(LogError::Empty)
4556
}
4657
}
4758

@@ -50,35 +61,40 @@ impl<const N: usize> EventLog<N> {
5061
/// [`heapless::String<N>`] works like `std::string::String` but
5162
/// stores up to `N` bytes on the stack. `write!` returns `Err` if
5263
/// the formatted text would exceed capacity.
53-
fn format_label(sensor_id: u16, value: f32) -> Result<String<32>, core::fmt::Error> {
64+
fn format_label(sensor_id: u16, value: f32) -> Result<String<32>, LogError> {
5465
use core::fmt::Write;
5566
let mut buf: String<32> = String::new();
5667
write!(buf, "S{sensor_id}={value:.1}")?;
5768
Ok(buf)
5869
}
5970

60-
fn main() {
71+
fn main() -> Result<(), LogError> {
6172
// A log that holds at most 8 events — zero heap allocation.
6273
let mut log: EventLog<8> = EventLog::new();
6374

64-
log.record(100, 0x01).expect("log not full");
65-
log.record(200, 0x02).expect("log not full");
66-
log.record(300, 0xFF).expect("log not full");
75+
log.record(100, 0x01)?;
76+
log.record(200, 0x02)?;
77+
log.record(300, 0xFF)?;
6778

6879
println!("logged {} events", log.len());
69-
println!("latest: {:?}", log.latest().unwrap());
80+
let latest = log.latest()?;
81+
println!(
82+
"latest: timestamp={}ms code=0x{:02X}",
83+
latest.timestamp_ms, latest.code
84+
);
7085

7186
// Stack-allocated string formatting.
72-
let label = format_label(42, 3.14).expect("fits in 32 bytes");
87+
let label = format_label(42, 3.14)?;
7388
println!("label: {label}");
7489

7590
// Demonstrate capacity enforcement — the 9th push returns Err.
7691
let mut full_log: EventLog<2> = EventLog::new();
77-
full_log.record(0, 1).unwrap();
78-
full_log.record(1, 2).unwrap();
79-
let overflow = full_log.record(2, 3);
80-
assert!(overflow.is_err());
92+
full_log.record(0, 1)?;
93+
full_log.record(1, 2)?;
94+
assert!(full_log.record(2, 3).is_err());
8195
println!("overflow correctly rejected");
96+
97+
Ok(())
8298
}
8399

84100
#[cfg(test)]

rust-cookbook/src/concurrency/actor/actor-pattern.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Because only one task ever accesses the data, there is no locking. Request–res
1212
pairs use a [`oneshot`] channel embedded in the message variant.
1313

1414
```rust,edition2021
15-
{{#include ../../../crates/concurrency/actor/src/bin/actor_pattern.rs::153 }}
15+
{{#include ../../../crates/concurrency/actor/src/bin/actor_pattern.rs::168 }}
1616
```
1717

1818
[Alice Ryhl]: https://ryhl.io/blog/actors-with-tokio/

rust-cookbook/src/concurrency/custom_future/custom-future.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ instead of relying on `async`/`await`.
88

99
Every custom future must handle three concepts:
1010

11+
<div class="comparison">
12+
1113
| Concept | Why it matters |
1214
|---------|----------------|
1315
| **[`Pin<&mut Self>`]** | Guarantees the future won't move in memory after the first poll. This is critical for self-referential futures (e.g., those holding borrows across `.await` points). If your struct contains only ordinary fields (no self-references), the compiler auto-implements `Unpin` and pinning is effectively a no-op. |
1416
| **[`Poll::Pending`] / [`Poll::Ready`]** | `Pending` tells the executor "not done yet," while `Ready(value)` completes the future. |
1517
| **[`cx.waker()`]** | A handle the executor gives you. You *must* call `wake()` at some point after returning `Pending`, or the executor will never poll the future again and it will hang. |
1618

19+
</div>
20+
1721
The example below builds a simple `Delay` future that resolves after a
1822
deadline. It has no self-referential fields, so it is `Unpin` automatically—
1923
pinning costs nothing. A production timer would register with a reactor;

rust-cookbook/src/safety_critical/heapless_alloc/heapless-alloc.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@ on the **stack** (or in a `static`) with a capacity fixed at compile time.
1111
Pushing beyond capacity returns `Err` instead of panicking or allocating,
1212
letting the caller decide how to handle it.
1313

14+
<div class="comparison">
15+
1416
| `std` type | `heapless` equivalent | Guarantee |
1517
|---|---|---|
1618
| `Vec<T>` | `heapless::Vec<T, N>` | At most `N` elements, no allocator |
1719
| `String` | `heapless::String<N>` | At most `N` bytes, no allocator |
1820
| `VecDeque<T>` | `heapless::Deque<T, N>` | Fixed-capacity ring buffer |
1921
| `HashMap<K,V>` | `heapless::IndexMap<K,V,S,N>` | Fixed-capacity hash map |
2022

23+
</div>
24+
2125
The example below builds a stack-allocated event log and demonstrates
2226
capacity enforcement—the kind of predictable, constant-memory behavior
2327
required by embedded and real-time systems.
2428

2529
```rust
26-
{{#include ../../../crates/safety_critical/heapless_alloc/src/bin/heapless_alloc.rs::83}}
30+
{{#include ../../../crates/safety_critical/heapless_alloc/src/bin/heapless_alloc.rs::98}}
2731
```
2832

2933
[`heapless`]: https://docs.rs/heapless

rust-cookbook/src/safety_critical/no_panic/no-panic.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ The [`#[no_panic]`][no-panic] attribute macro makes the **compiler prove** at
1010
link time that a function can never reach a panicking code path. If it can't
1111
prove it, the build fails.
1212

13+
<div class="comparison">
14+
1315
| Panicking pattern | Safe alternative |
1416
|-------------------|-----------------|
1517
| `slice[i]` | `slice.get(i)` with exhaustive `match` |
1618
| `for i in 0..len { slice[i] }` | `for &v in slice` (iterator) |
1719
| `value.unwrap()` | `match` / `if let` / `?` |
1820
| `a / b` (integer, `b` might be 0) | Check `b` before dividing |
1921

22+
</div>
23+
2024
The example below shows three panic-free functions—aggregation, lookup, and
2125
sensor normalization—each proven at compile time by `#[no_panic]`.
2226

0 commit comments

Comments
 (0)