Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

Learn RustChapter 7: Errors and tests

Lesson 7.5 · 8 min

Chapter review: Errors and tests

This chapter taught Chatterbox to say what went wrong instead of crashing, gave it tests that check its sentences for you, and ran the tools that tidy its code. Before the program splits into several files, check that it has all stuck: a mixed quiz, then one bigger exercise that uses the whole chapter at once.

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

Chatterbox v0.7 can read /last 3, and when you type /last two it tells you what is wrong in a plain sentence and keeps listening. Every one of those sentences lives in one place, seven tests check them each time the code changes, and clippy has tidied every line. Here is the chapter, one line per lesson. If a line feels shaky, the link takes you back.

  • 7.1 Operations that can fail: Result: a function that can fail returns Ok(value) or Err(reason), and match makes you say what happens in both cases. Rust has no exceptions.
  • 7.2 The ? operator: ? takes the value out of an Ok, or returns the Err from the function right away. Your own error enum, with a Display block, turns each kind of problem into a sentence.
  • 7.3 Writing tests: a #[test] function passes if it finishes and fails if it panics; assert_eq!, assert! and matches! are tidy ways to panic. cargo test runs them all; cargo run never does.
  • 7.4 Tools that check your code: cargo fmt lays code out one standard way, cargo clippy suggests the usual way to write something, and dbg! shows a value and hands it straight back.

Mixed quiz

Eight short programs, all new, with the ideas shuffled on purpose: a Result, then a ?, then a test, then a tool. Mixing them is harder than eight of the same kind in a row, and that is what makes it stick. For each one, decide first: does it compile? If it does, what exactly does it print, or does it stop with a panic? Every answer was produced by the real compiler.

PredictA count with a typo
fn main() {
let text = "12x";
match text.parse::<u32>() {
Ok(n) => println!("showing the last {n}"),
Err(_) => println!("bot: '{text}' is not a count"),
}
}

What does this print?

  • showing the last 12
  • bot: '12x' is not a count
  • showing the last 0
Reveal the answer

The program prints: bot: '12x' is not a count parse does not pick out the digits it likes and ignore the rest: the whole text has to be a number, and 12x is not, so you get an Err. The match runs the Err arm and nothing crashes. Revisit lesson 7.1 if you expected 12.

PredictA ? with nowhere to go
fn count_of(text: &str) -> usize {
let n = text.parse::<usize>()?;
n
}
 
fn main() {
println!("{}", count_of("3"));
}

What happens when this program is compiled?

  • It compiles and runs
  • Compile error E0277
  • Compile error E0308
Reveal the answer

It does not compile: E0277. On an Err, ? has to return that error from the function, but count_of promises a plain usize, so there is nowhere to send it. The compiler refuses with E0277 and says ? can only be used in a function that returns a Result or an Option. Lesson 7.2 has the rule.

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
 --> src/main.rs:2:34
  |
1 | fn count_of(text: &str) -> usize {
  | -------------------------------- this function should return `Result` or `Option` to accept `?`
2 |     let n = text.parse::<usize>()?;
  |                                  ^ cannot use the `?` operator in a function that returns `usize`
PredictA wrong test, then cargo run
fn banner() -> String {
String::from("Chatterbox v0.7")
}
 
fn main() {
println!("{}", banner());
}
 
#[cfg(test)]
mod tests {
use super::*;
 
#[test]
fn banner_names_the_version() {
assert_eq!(banner(), "Chatterbox v9.9");
}
}

The test below is wrong. What does running the program (not testing it) print?

  • Chatterbox v0.7
  • Chatterbox v9.9
  • test failed
Reveal the answer

The program prints: Chatterbox v0.7 #[cfg(test)] means the tests module is only built by cargo test. Running the program leaves it out completely, so the broken promise is never even checked and main prints the banner. Run cargo test and that test would fail. Lesson 7.3 explains both lines.

PredictWhich sentence does the bot say?
use std::fmt;
 
enum SendError {
Empty,
TooLong(usize),
}
 
impl fmt::Display for SendError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SendError::Empty => write!(f, "say something first"),
SendError::TooLong(n) => write!(f, "too long: {n} bytes"),
}
}
}
 
fn check(message: &str) -> Result<usize, SendError> {
if message.is_empty() {
return Err(SendError::Empty);
}
if message.len() > 5 {
return Err(SendError::TooLong(message.len()));
}
Ok(message.len())
}
 
fn main() {
match check("hello there") {
Ok(n) => println!("sent {n} bytes"),
Err(e) => println!("bot: {e}"),
}
}

What does this print?

  • sent 11 bytes
  • bot: too long: 11 bytes
  • bot: TooLong(11)
Reveal the answer

The program prints: bot: too long: 11 bytes hello there is 11 bytes, more than 5, so check returns Err(SendError::TooLong(11)). Printing it with {} uses the Display block, which picks the sentence for that variant. TooLong(11) is what the debug form {:?} would show. Lesson 7.2 builds a block like this.

Predictdbg! in the middle of a line
fn main() {
let history = vec!["hi", "/help", "bye"];
let last = dbg!(history.len()) - 1;
println!("{}", history[last]);
}

What does this program print on its normal output?

  • bye
  • 3
  • [src/main.rs:3:16] history.len() = 3
Reveal the answer

The program prints: bye dbg! hands its value straight back, so last is 3 minus 1, which is 2, the position of bye. Its report about history.len() goes to standard error, a separate channel, not to the normal output. Lesson 7.4 shows both.

PredictWhere does the function stop?
use std::num::ParseIntError;
 
fn read_count(text: &str) -> Result<u32, ParseIntError> {
println!("reading {text}");
let n = text.parse::<u32>()?;
println!("got {n}");
Ok(n)
}
 
fn main() {
let r = read_count("two");
println!("{}", r.is_err());
}

What is the second line this program prints?

  • got 0
  • true
  • false
Reveal the answer

The program prints: true The first line is reading two. Then ? meets an Err and returns it from read_count on the spot, so got is never printed. Back in main, r is that Err, so is_err() prints true. Revisit lesson 7.2 if you expected a got line.

PredictAsking a good count for its reason
use std::num::ParseIntError;
 
fn parse_count(text: &str) -> Result<usize, ParseIntError> {
text.trim().parse::<usize>()
}
 
fn main() {
let reason = parse_count("7").unwrap_err();
println!("bot: {reason}");
}

What happens when this program runs?

  • It compiles and runs
  • It compiles, then panics
  • Compile error E0599
Reveal the answer

It compiles, then panics at run time. unwrap_err() is the mirror image of unwrap(): it gives you the reason inside an Err, and panics if the value was an Ok. "7" is a perfectly good count, so it panics. Inside a test that panic is exactly what makes the test fail. Lesson 7.3 has the table.

PredictA count without its wrapper
use std::num::ParseIntError;
 
fn parse_count(text: &str) -> Result<usize, ParseIntError> {
let n = text.trim().parse::<usize>()?;
n
}
 
fn main() {
println!("{:?}", parse_count("3"));
}

What happens when this program is compiled?

  • It compiles and runs
  • Compile error E0308
  • Compile error E0277
Reveal the answer

It does not compile: E0308. The function promises a Result, and its last line hands back a plain usize. That is E0308, mismatched types. The ? took the number out of the Ok, so it has to go back into one at the end: Ok(n). Lesson 7.2 states this second rule of ?.

error[E0308]: mismatched types
 --> src/main.rs:5:5
  |
3 | fn parse_count(text: &str) -> Result<usize, ParseIntError> {
  |                               ---------------------------- expected `Result<usize, ParseIntError>` because of return type
4 |     let n = text.trim().parse::<usize>()?;
5 |     n
  |     ^ expected `Result<usize, ParseIntError>`, found `usize`
  |
  = note: expected enum `Result<usize, ParseIntError>`
             found type `usize`
help: try wrapping the expression in `Ok`
  |
5 |     Ok(n)
  |     +++ +

Milestone: a /last that explains itself

Now use the whole chapter at once. Chatterbox's /last should refuse a silly request, like /last 99999, as politely as it refuses /last two. Below is a small program, separate from Chatterbox, with its own error enum, CountError, and its Display block already written. You write two functions and finish one test:

  • to_count turns text into a number, and turns the standard library's reason into your own NotACount, holding the text that was wrong. That is a match on a Result, as in lesson 7.1.
  • last_count checks the text after /last in order: nothing there, then not a number, then too many. Use to_count with a ? after it, so a bad number leaves the function in one character (lesson 7.2).
  • The test too_many_is_an_error says todo!(). Make it check its comment's promise, the way the tests in lesson 7.3 did. The hidden tests check your two functions, and your own tests have to pass too.

If a value is not what you expect, wrap it in dbg! and run the program: the report shows up beside the output.

Try itMilestone: check a /last count

Write to_count and last_count so that "3" gives Ok(3), an empty or blank text gives Missing, "two" gives NotACount("two"), and anything above MAX_LAST gives TooMany. Then finish the second test.

Write to_count and last_count so that "3" gives Ok(3), an empty or blank text gives Missing, "two" gives NotACount("two"), and anything above MAX_LAST gives TooMany. Then finish the second test.

use std::fmt;
 
/// The most messages /last will show at once.
const MAX_LAST: usize = 20;
 
/// Every way the text after /last can be wrong.
#[derive(Debug)]
enum CountError {
Missing,
NotACount(String),
TooMany(usize),
}
 
impl fmt::Display for CountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CountError::Missing => write!(f, "last what? Try /last 3"),
CountError::NotACount(text) => write!(f, "'{text}' is not a count. Try /last 3"),
CountError::TooMany(n) => write!(f, "{n} is too many. The most is {MAX_LAST}"),
}
}
}
 
/// Turns text into a whole number, or NotACount carrying that text.
fn to_count(text: &str) -> Result<usize, CountError> {
todo!()
}
 
/// The count asked for by "/last N", given the text after the command word.
/// Nothing there (or only spaces): Missing. Not a whole number: NotACount.
/// More than MAX_LAST: TooMany. Spaces around the number are fine.
fn last_count(arg: &str) -> Result<usize, CountError> {
todo!()
}
 
fn main() {
let tries = vec!["3", "", "two", "99"];
for arg in tries {
match last_count(arg) {
Ok(n) => println!("bot: showing the last {n}"),
Err(e) => println!("bot: {e}"),
}
}
}
 
#[cfg(test)]
mod tests {
use super::*;
 
#[test]
fn a_small_number_is_fine() {
assert_eq!(last_count("3").unwrap(), 3);
}
 
#[test]
fn too_many_is_an_error() {
todo!() // check that "99" gives a TooMany error holding 99
}
}

Hidden tests (10): check_a_number_with_spaces_around_it check_a_negative_number_is_not_a_count check_a_word_is_not_a_count check_one_too_many check_nothing_is_missing check_only_spaces_is_missing_too check_the_most_allowed_is_fine check_to_count_keeps_the_text a_small_number_is_fine too_many_is_an_error

This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.

What you have now

If the quiz and the milestone went well, you can read a failure as a value, pass it up with one character, give it a sentence, and pin that sentence down with a test that runs in a second. That is most of what keeps a growing program honest. As a preview, the next lesson starts splitting Chatterbox into several files, and these tests move with the code they check.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What does <code>?</code> do on an <code>Ok</code>, and what does it do on an <code>Err</code>?Reveal
On an Ok it takes the value out and the line carries on. On an Err it returns that error from the whole function straight away.
Why is <code>?</code> refused inside a function that returns a plain <code>usize</code>?Reveal
On an Err it must return the error from the function, and a function that promises a usize has nowhere to put one. That is E0277.
What decides whether a test passes?Reveal
Whether its function finishes. A panic, from a broken assert_eq! or an unwrap_err() on an Ok, makes it fail.
An error enum prints <code>TooLong(11)</code> instead of a sentence. What was used to print it?Reveal
The debug form, {:?}. Printing with {} uses the Display block and its sentence.
Your Chatterbox so far: v0.7 (after lesson 7.4)
Chatterbox v0.7 after lesson 7.4: the whole program
src/main.rs
// Chatterbox: a small chat program, built one lesson at a time.
use std::collections::HashMap;
use std::fmt;
use std::io;
 
/// What the bot is called. A constant: fixed for the whole program.
const BOT: &str = "Chatterbox";
/// The version as a pair of numbers: (major, minor).
const VERSION: (u32, u32) = (0, 7);
/// The longest message we accept, in bytes.
const MAX_LEN: usize = 200;
/// What /help prints.
const HELP: &str = "commands: /help, /again, /history, /count, /stats, /remember <name> <fact>, /recall <name>, /find <word>, /last <n>, /say <text>, /shout <text>, /debug <text>, /quit (or /h, /q, /exit)";
 
/// One line of the conversation: who said it, and what they said.
struct Message {
author: String,
text: String,
}
 
impl Message {
/// Make a Message from two pieces of borrowed text.
fn new(author: &str, text: &str) -> Message {
Message {
author: String::from(author),
text: String::from(text),
}
}
 
/// The message as one transcript line: "you: hello".
fn to_line(&self) -> String {
format!("{}: {}", self.author, self.text)
}
 
/// The text in capitals, for /shout.
fn shout(&self) -> String {
self.text.to_uppercase()
}
}
 
/// Everything a line of input can mean. One enum, so a `match` on it must
/// handle every kind of line: the compiler checks that nothing is forgotten.
#[derive(Debug)]
enum Command {
Empty,
Help,
Quit,
Again,
History,
Count,
Stats,
Remember(String, String),
Recall(Option<String>),
Find(String),
Last(usize),
Say(String),
Shout(String),
Debug(String),
Chat(String),
}
 
/// Everything that can be wrong with a line of input. parse() returns one of
/// these instead of a Command, and main prints it.
#[derive(Debug)]
enum CommandError {
Unknown(String),
Missing(String, String),
Usage(String),
NotACount(String),
}
 
/// How a CommandError reads when printed with {}.
impl fmt::Display for CommandError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CommandError::Unknown(word) => {
write!(f, "I don't know the command {word}. Try /help.")
}
CommandError::Missing(verb, example) => {
write!(f, "{verb} what? Try /{verb} {example}")
}
CommandError::Usage(form) => write!(f, "try {form}"),
CommandError::NotACount(text) => write!(f, "'{text}' is not a count. Try /last 3"),
}
}
}
 
/// The text after a command word, or the error that says it is missing.
fn need(line: &str, verb: &str, example: &str) -> Result<String, CommandError> {
match arg(line) {
Some(text) => Ok(String::from(text)),
None => Err(CommandError::Missing(
String::from(verb),
String::from(example),
)),
}
}
 
/// How many messages /last should show. "two" or "-1" is an error.
fn parse_count(text: &str) -> Result<usize, CommandError> {
match text.parse::<usize>() {
Ok(n) => Ok(n),
Err(_) => Err(CommandError::NotACount(String::from(text))),
}
}
 
/// Turn one line of input into a Command, or say what is wrong with it.
/// Each `?` means: if this step failed, return its error right here.
fn parse(line: &str) -> Result<Command, CommandError> {
let cmd = match first_word(line) {
"" => Command::Empty,
"/help" | "/h" => Command::Help,
"/quit" | "/q" | "/exit" => Command::Quit,
"/again" => Command::Again,
"/history" => Command::History,
"/count" => Command::Count,
"/stats" => Command::Stats,
"/remember" => {
let usage = CommandError::Usage(String::from("/remember <name> <fact>"));
let rest = match arg(line) {
Some(rest) => rest,
None => return Err(usage),
};
match arg(rest) {
Some(fact) => Command::Remember(String::from(first_word(rest)), String::from(fact)),
None => return Err(usage),
}
}
"/recall" => match arg(line) {
Some(name) => Command::Recall(Some(String::from(name))),
None => Command::Recall(None),
},
"/find" => Command::Find(need(line, "find", "hello")?),
"/last" => Command::Last(parse_count(&need(line, "last", "3")?)?),
"/debug" => Command::Debug(need(line, "debug", "/say hi")?),
"/say" => Command::Say(need(line, "say", "hello")?),
"/shout" => Command::Shout(need(line, "shout", "hello")?),
word => {
if is_command(word) {
return Err(CommandError::Unknown(String::from(word)));
}
Command::Chat(String::from(line))
}
};
Ok(cmd)
}
 
/// The first line Chatterbox prints.
fn banner() -> String {
format!("{} v{}.{}", BOT, VERSION.0, VERSION.1)
}
 
/// The greeting, with a name filled in.
fn greeting(name: &str) -> String {
let mut text = format!("Hi {name}! I'm {BOT}.");
text.push_str(" Ask me anything.");
text
}
 
/// True when the text is a command: it starts with a slash.
fn is_command(text: &str) -> bool {
text.starts_with('/')
}
 
/// The first word of the text: a slice (&str) pointing into it, not a copy.
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[..i],
None => text,
}
}
 
/// The text after the command word, if there is any: Some("hello") for
/// "/say hello", None for a bare "/say". No more "" standing in for nothing.
fn arg(line: &str) -> Option<&str> {
match line.find(' ') {
Some(i) => {
let rest = line[i + 1..].trim();
if rest.is_empty() { None } else { Some(rest) }
}
None => None,
}
}
 
/// What the bot says back to an ordinary message (not a command).
fn reply(message: &str) -> String {
if message.len() > MAX_LEN {
format!("that is too long (the limit is {MAX_LEN} bytes)")
} else if message.ends_with('?') {
String::from("Good question! I can only echo for now. Ask me again in chapter 9.")
} else {
format!("you said: {message}")
}
}
 
fn main() {
let mut messages_seen: u32 = 0;
let quiet = false;
let mut last: Option<String> = None; // the last thing you said, for /again
let mut history: Vec<Message> = Vec::new(); // everything said so far, in order
let mut facts: HashMap<String, String> = HashMap::new(); // name -> fact, for /recall
 
println!("{}", banner());
println!("What should I call you?");
let mut name_line = String::new();
io::stdin().read_line(&mut name_line).unwrap();
let name = name_line.trim();
if !quiet {
println!("{}", greeting(name));
}
 
let mut line = String::new();
loop {
line.clear();
let bytes = io::stdin().read_line(&mut line).unwrap();
if bytes == 0 {
break; // no more input: the keyboard was closed
}
let message = line.trim();
let cmd = match parse(message) {
Ok(cmd) => cmd,
Err(e) => {
println!("bot: {e}");
continue;
}
};
match cmd {
Command::Empty => {} // nothing typed: nothing to answer
Command::Quit => {
println!("bot: bye, {name}!");
break;
}
Command::Help => println!("bot: {HELP}"),
Command::Again => {
if let Some(text) = &last {
println!("bot: {}", reply(text));
} else {
println!("bot: nothing to repeat yet");
}
}
Command::History => {
if history.is_empty() {
println!("bot: nothing said yet");
} else {
for (i, m) in history.iter().enumerate() {
println!("{}. {}", i + 1, m.to_line());
}
}
}
Command::Count => {
let bytes: usize = history.iter().map(|m| m.text.len()).sum();
println!("bot: {} messages, {} bytes of text", history.len(), bytes);
}
Command::Stats => {
// One chain of steps: your messages -> their words -> a count per word.
let mut counts: HashMap<String, u32> = HashMap::new();
for word in history
.iter()
.filter(|m| m.author == "you")
.flat_map(|m| m.text.split_whitespace())
{
*counts.entry(word.to_lowercase()).or_insert(0) += 1;
}
let mut top: Vec<(String, u32)> = counts.into_iter().collect();
// most used first; ties in alphabetical order, so the answer never changes
top.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
let words: Vec<String> = top
.iter()
.take(3)
.map(|(word, n)| format!("{word} ({n})"))
.collect();
if words.is_empty() {
println!("bot: nothing said yet");
} else {
println!("bot: your top words: {}", words.join(", "));
}
}
Command::Remember(key, fact) => {
println!("bot: got it: {key} is {fact}");
facts.insert(key, fact);
}
Command::Recall(Some(key)) => match facts.get(&key) {
Some(fact) => println!("bot: {key} is {fact}"),
None => println!("bot: I don't know anything about {key}"),
},
Command::Recall(None) => {
if facts.is_empty() {
println!("bot: I don't remember anything yet. Try /remember");
} else {
// A HashMap has no order, so sort the names before printing them.
let mut names = Vec::new();
for name in facts.keys() {
names.push(name.as_str());
}
names.sort();
println!("bot: I remember: {}", names.join(", "));
}
}
Command::Find(word) => {
// The closure borrows `word` from here: a small function made on the spot.
let hits: Vec<&Message> = history
.iter()
.filter(|m| m.author == "you" && m.text.contains(&word))
.collect();
if hits.is_empty() {
println!("bot: you never said {word}");
} else {
for m in hits {
println!("{}", m.to_line());
}
}
}
Command::Last(n) => {
// Start n messages from the end, or at the beginning if there are fewer.
let start = if n >= history.len() {
0
} else {
history.len() - n
};
for m in &history[start..] {
println!("{}", m.to_line());
}
}
Command::Say(text) => println!("bot: {text}"),
Command::Shout(text) => println!("bot: {}", Message::new("you", &text).shout()),
Command::Debug(text) => println!("bot: {:?}", parse(&text)),
Command::Chat(text) => {
messages_seen += 1;
let said = Message::new("you", &text);
let answer = Message::new("bot", &reply(&said.text));
println!("bot: {}", answer.text);
history.push(said);
history.push(answer);
// Message::new made the history its own copy, so `text` itself can
// be handed to `last` for /again: no extra copy needed.
last = Some(text);
}
}
}
println!("messages so far: {messages_seen}");
}
 
// Tests: small functions that check the program does what we say it does.
// `cargo test` builds them and runs every one; the program itself never sees them.
#[cfg(test)]
mod tests {
use super::*;
 
#[test]
fn plain_text_is_chat() {
assert!(matches!(parse("hello there"), Ok(Command::Chat(t)) if t == "hello there"));
}
 
#[test]
fn help_has_a_short_form() {
assert!(matches!(parse("/help"), Ok(Command::Help)));
assert!(matches!(parse("/h"), Ok(Command::Help)));
}
 
#[test]
fn say_needs_text() {
assert_eq!(
parse("/say").unwrap_err().to_string(),
"say what? Try /say hello"
);
}
 
#[test]
fn last_needs_a_number() {
assert!(matches!(parse("/last 3"), Ok(Command::Last(3))));
assert_eq!(
parse("/last two").unwrap_err().to_string(),
"'two' is not a count. Try /last 3"
);
}
 
#[test]
fn unknown_commands_are_errors() {
assert_eq!(
parse("/dance").unwrap_err().to_string(),
"I don't know the command /dance. Try /help."
);
}
 
#[test]
fn reply_echoes_and_answers_questions() {
assert_eq!(reply("hi"), "you said: hi");
assert!(reply("are you there?").starts_with("Good question!"));
}
 
#[test]
fn reply_refuses_long_messages() {
let long = "x".repeat(MAX_LEN + 1);
assert!(reply(&long).starts_with("that is too long"));
}
}

Download this version (zip)Unzip, then cargo run inside the folder.

That is lesson 7.5. Tick it off so the course remembers where you are. That is also the end of chapter 7: a few minutes with the course flashcards now makes it stick, and the chapter’s cheat sheet has every idea on one screen.

Questions people ask

What is the difference between Result and the ? operator in Rust?

Result is the type a failing operation returns: Ok with a value or Err with a reason. The ? operator is a shortcut for using one: it takes the value out of an Ok, or returns the Err from the current function.

Why does ? give error E0277 in my function?

The ? operator can only be used in a function that returns a Result or an Option, because on an Err it has to return something. Change the function to return a Result, or handle the error with match instead.

Do Rust tests run when I use cargo run?

No. Tests inside a module marked cfg(test) are only built and run by cargo test. cargo run builds the program without them.

How do I check which error a Rust function returned in a test?

Use assert! with matches! and a pattern such as Err(CountError::TooMany(99)), or take the reason with unwrap_err and compare its to_string with the sentence you expect.

Learn Rust by building a chat app · Lesson 7.5 · every compiler message above is real output from rustc 1.99.0 (b940084d7 2026-09-28) (edition 2024) · Vibe Engines · 2026