Lesson 9.4 · 8 min
Chapter review: the real model
Chatterbox now saves its history as JSON, sends the conversation to a real model and comes back with an answer, and it is installed as v1.0. Before chapter 10 makes the answers stream, this page mixes the whole chapter together: eight quick predictions, then one exercise that walks the path from the saved file to the model's answer.
Adds to ChatterboxNothing new: a mixed quiz on the whole chapter, and a bigger exercise that uses all of it.
What this chapter gave Chatterbox
Three lessons took Chatterbox from a program that talks to itself to one that talks to a model, and then out of its project folder. The ideas, one line each:
- Lesson 9.1:
#[derive(Serialize, Deserialize)]asks serde to write the code that turns a struct into JSON text and back. Writing practically never fails; reading text from outside can, soserde_json::from_strreturns aResult. - Lesson 9.2: a request and a reply are just structs. You name only the fields you read, and serde skips the rest.
- Also in lesson 9.2: settings and secrets come from environment variables, which
env::varhands back as aResult, and oneimpl Fromblock lets?turn someone else's error into your own error type. - Lesson 9.3:
cargo testbefore you hand it over,cargo build --releasefor the fast binary,cargo install --path . --lockedto run it from anywhere, and a README so someone else can.
Nothing new appears on this page. If a question stumps you, its answer names the lesson to reread.
Mixed quiz
Eight small programs, each run through the real compiler when the course was built. Guess first, then open the answer. They jump between the lessons on purpose: picking the right idea is half the work.
What does this program print?
[{"author":"you","text":"hi"},{"author":"bot","text":"you said: hi"}]{"author":"you","text":"hi"}{"author":"bot","text":"you said: hi"}{"history":[{"author":"you","text":"hi"},{"author":"bot","text":"you said: hi"}]}
Reveal the answer
The program prints: [{"author":"you","text":"hi"},{"author":"bot","text":"you said: hi"}] A Vec of things that can be written out can itself be written out, as a JSON list in square brackets with one record per message. The name history belongs to the program, not to the data, so it never shows up in the JSON. This is exactly what chatterbox.json holds (lesson 9.1).
Does it compile, and if it does, does it run?
- It compiles and runs
- It compiles, then panics
- Compile error E0308
Reveal the answer
It does not compile: E0308. It never gets as far as running. env::var returns a Result, because the setting may not be there, and a Result is not a String, so the compiler reports mismatched types. Lesson 9.2 handled it with map_err and ?; a match works too.
error[E0308]: mismatched types --> src/main.rs:4:23 | 4 | let key: String = env::var("CHATTERBOX_API_KEY"); | ------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Result<String, VarError>` | | | expected due to this | = note: expected struct `String` found enum `Result<String, VarError>` help: consider using `Result::expect` to unwrap the `Result<String, VarError>` value, panicking if the value is a `Result::Err` | 4 | let key: String = env::var("CHATTERBOX_API_KEY").expect("REASON"); | +++++++++++++++++
The reply has an id, a model name and token counts that the structs never mention. What happens?
- It compiles and runs
- It compiles, then panics
- Compile error E0063
Reveal the answer
It compiles and runs. It runs and prints bot: Hi Sam!. serde quietly skips any field your struct does not name, which is why the structs in lesson 9.2 list only the path choices, message, content. Extra fields are fine; a missing one is not, as a later question shows.
This is a normal build, not cargo test. Does it compile?
- It compiles and runs
- Compile error E0433
- Compile error E0425
Reveal the answer
It does not compile: E0433. #[cfg(test)] means "only build this for cargo test". In a normal build the whole tests module does not exist, so tests:: leads nowhere and the compiler cannot resolve the path. That is the point lesson 9.3 made: none of your tests ends up in the program you ship, so nothing outside them may lean on them.
error[E0433]: cannot find module or crate `tests` in this scope --> src/main.rs:2:20 | 2 | println!("{}", tests::sample_reply()); | ^^^^^ use of unresolved module or unlinked crate `tests` | = help: you might be missing a crate named `tests`
Does this compile?
- It compiles and runs
- Compile error E0277
- Compile error E0599
Reveal the answer
It does not compile: E0277. Deserialize only promises "I can be read in". to_string accepts a value only if its type keeps the other promise, Serialize, so the trait bound is not met: E0277, the missing-promise error from lesson 8.4. Each direction has its own derive (lesson 9.1).
error[E0277]: the trait bound `Message: serde::Serialize` is not satisfied --> src/main.rs:11:42 | 11 | println!("{}", serde_json::to_string(&m).unwrap()); | --------------------- ^^ unsatisfied trait bound | | | required by a bound introduced by this call | help: the trait `Serialize` is not implemented for `Message` --> src/main.rs:4:1 | 4 | struct Message { | ^^^^^^^^^^^^^^ = note: for local types consider adding `#[derive(serde::Serialize)]` to your `Message` type = note: for types from other crates check whether the crate offers a `serde` feature flag = help: the following other types implement trait `Serialize`: &'a T &'a mut T () (T,) (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) (T0, T1, T2, T3, T4) and 129 others note: required by a bound in `serde_json::to_string` --> ~/.cargo/registry/src/index.crates.io/serde_json-1.0.154/src/ser.rs:2247:17 | 2245 | pub fn to_string<T>(value: &T) -> Result<String> | --------- required by a bound in this function 2246 | where 2247 | T: ?Sized + Serialize, | ^^^^^^^^^ required by this bound in `to_string`
Nobody has set CHATTERBOX_MODEL. What does this print?
(that went wrong: CHATTERBOX_MODEL is not set)model: CHATTERBOX_MODELmodel:
Reveal the answer
The program prints: (that went wrong: CHATTERBOX_MODEL is not set) The setting is missing, so env::var gives an Err, and map_err swaps that error for a sentence that names the setting. An Ok would have passed through untouched. It is the same shape as Model::from_env in lesson 9.2, which is why a missing key is a message and never a crash.
The saved file has a message with no text. What happens?
- It compiles and runs
- It compiles, then panics
- Compile error E0308
Reveal the answer
It compiles, then panics at run time. Every Message needs a text, and the second record has none, so from_str returns an Err and unwrap() stops the program. serde does not hand back a half-filled history; the text is checked at the door (lesson 9.1). Chatterbox's load uses ? instead, so the problem is reported, not a crash.
Does this compile?
- It compiles and runs
- Compile error E0277
- Compile error E0308
Reveal the answer
It does not compile: E0277. from_str fails with a serde_json::Error, but first_text promises a ChatError. ? converts between the two only if someone wrote impl From<serde_json::Error> for ChatError, and nobody did, so the compiler says ? could not convert the error. Lesson 9.2 wrote that block for reqwest's error.
error[E0277]: `?` couldn't convert the error to `ChatError` --> src/main.rs:14:60 | 13 | fn first_text(saved: &str) -> Result<String, ChatError> { | ------------------------- expected `ChatError` because of this 14 | let history: Vec<Message> = serde_json::from_str(saved)?; | ---------------------------^ the trait `From<serde_json::Error>` is not implemented for `ChatError` | | | this can't be annotated with `?` because it has type `Result<_, serde_json::Error>` | note: `ChatError` needs to implement `From<serde_json::Error>` --> src/main.rs:9:1 | 9 | enum ChatError { | ^^^^^^^^^^^^^^ = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
Milestone: from the saved file to the answer
When Chatterbox starts with a real model and an old chatterbox.json, two jobs sit either side of the HTTP call. Before it: read the saved history and build the request body. After it: dig the answer out of the reply. The call itself needs the network, which this page does not have, but both jobs are plain serde, so they run right here.
This time they return a Result with one error type, ChatError, so every from_str can end in ?. That needs the From block first, and it is yours to finish too.
Fill in three bodies. from wraps a serde_json error in the right ChatError variant. resume reads the saved history, refuses an empty one, translates you to user and anyone else to assistant, and writes the request as JSON. answer reads a reply and returns the first choice's content. The hidden tests try a real history, an empty one, a file in the old you: hi format, a message without text, a reply full of extra fields, two choices, no choices and an error page.
Fill in three bodies. from wraps a serde_json error in the right ChatError variant. resume reads the saved history, refuses an empty one, translates you to user and anyone else to assistant, and writes the request as JSON. answer reads a reply and returns the first choice's content. The hidden tests try a real history, an empty one, a file in the old you: hi format, a message without text, a reply full of extra fields, two choices, no choices and an error page.
Hidden tests (8): an_empty_history_is_nothing_to_send no_choices_is_empty an_error_page_is_a_json_error a_message_without_text_does_not_fit resume_sends_every_message_with_its_role the_answer_ignores_fields_it_does_not_name the_first_choice_wins the_old_log_format_is_a_json_error
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
Before chapter 10
If the exercise went through, you have every piece of the model path in your hands: shapes as structs, text checked at the door, failures as values, and a binary someone else can install. Chapter 10 keeps all of it and changes one thing: instead of waiting for the whole answer, Chatterbox prints it as the model writes it.
Say it out loud, then reveal
Why does <code>serde_json::from_str</code> return a Result while writing a struct out practically never fails?Reveal
A reply has dozens of fields and you need one. What do your structs have to name?Reveal
What has to exist for <code>?</code> to turn a <code>serde_json::Error</code> into your own <code>ChatError</code>?Reveal
impl From<serde_json::Error> for ChatError block. ? looks for exactly that conversion; without it the compiler stops with E0277.Why does the API key come from an environment variable, and what does <code>env::var</code> give back?Reveal
env::var returns a Result: Ok with the value, or Err if the setting is not there.Name the three cargo commands that take Chatterbox from your folder to someone else.Reveal
cargo test to check it, cargo build --release for the fast binary, and cargo install --path . --locked to put it where it runs from any folder.Your Chatterbox so far: v1.0 (after lesson 9.3)
Download this version (zip)Unzip, then cargo run inside the folder.
That is lesson 9.4. Tick it off so the course remembers where you are. That is also the end of chapter 9: a few minutes with the course flashcards now makes it stick, and the chapter’s cheat sheet has every idea on one screen.
Go deeper
Questions people ask
Does serde fail if the JSON has extra fields?
No, not by default. serde ignores every field in the JSON that your struct does not name, so you only declare the ones you read. It does fail when a field your struct names is missing.
Why does the ? operator say it cannot convert the error?
The function returns one error type and the failing call produces another. Write an impl From block that builds your error type out of the other one, and ? will use it automatically.
Can I test the HTTP call on the Rust Playground?
No. The Playground has no network access. Test the parts on either side of the call, building the request JSON and reading the reply JSON, which are plain functions, and run the call itself on your own machine.
Do I need both Serialize and Deserialize on every struct?
Only on the ones you both write and read, such as a saved message. A request that is only sent needs Serialize, and a reply that is only read needs Deserialize.
