Lesson 7.4 · 11 min
Saving and loading
Close Chatterbox and the whole conversation is gone. A file on disk fixes that, and reading and writing it is one more thing that can fail, which is exactly what Result and ? are for.
Adds to ChatterboxHistory survives a restart.
Two functions and a file
The standard library's std::fs module has the two functions we need. fs::write(path, text) puts text in a file, replacing whatever was there. fs::read_to_string(path) gives the whole file back as a String. Both return a Result with an io::Error as the reason, because disks fill up, folders go missing and permissions say no.
Files hold text, but our history is a list of Message values. So we need two small translators: one that turns the list into lines of text, and one that turns lines back into the list. Each line looks like user: hello: the author, a colon and a space, then the message.
These two functions touch no files, so you can test them anywhere. That is deliberate: keep the part that can be checked separate from the part that talks to the disk.
Write to_lines (one line per message, like user: hello, each ending in a newline) and from_lines (the reverse). A line with no colon-and-space is skipped. split_once(": ") splits at the first colon-and-space only, so a message that itself contains a colon survives.
Write to_lines (one line per message, like user: hello, each ending in a newline) and from_lines (the reverse). A line with no colon-and-space is skipped. split_once(": ") splits at the first colon-and-space only, so a message that itself contains a colon survives.
Hidden tests (4): colon_inside_the_text_survives unknown_lines_are_skipped round_trip writes_one_line_per_message
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
Talking to the disk
This listing is trimmed to the part this lesson is about; the commands from earlier chapters (/say, /history, /stats, /long) and the per-author counts are still in your project.
Now the thin layer on top. save is one line. load has one subtlety: the very first time Chatterbox runs there is no log file, and that is not an error, it just means "no history yet". So we look at the kind of error. A NotFound becomes an empty history; any other problem is passed up with Err. The if after Err(e) is a guard: that arm only matches when its condition is true.
Finally main returns Result<(), io::Error> so ? works in it (lesson 7.2). A failed load or save stops the program with the error instead of being ignored.
This one reads from the keyboard and writes a file, so it needs a real terminal. Here it is run twice in a project folder. The second run starts by loading what the first one saved.
$ printf 'hello\nwhat is a token?\n/quit\n' | cargo run -q Chatterbox v0.7: 0 saved messages saved 4 messages to chatterbox.log $ cat chatterbox.log user: hello assistant: you said: hello user: what is a token? assistant: you said: what is a token? $ printf 'again\n/quit\n' | cargo run -q Chatterbox v0.7: 4 saved messages saved 6 messages to chatterbox.log
What you have now
Chatterbox remembers. Start it, talk, quit, start it again: the history is still there. Along the way you used Result to say what can fail, ? to pass failures up, a module-sized habit of keeping testable code apart from disk code, and a real file. Chapter 8 gives the bot something to say back: a real model.
Say it out loud, then reveal
Why is a missing log file handled differently from other io errors?Reveal
Why does main return Result<(), io::Error>?Reveal
Why keep to_lines and from_lines separate from the file functions?Reveal
That is lesson 7.4. Tick it off so the course remembers where you are.