Description
Fix three small syntax mistakes in a Rust starter project so it compiles and prints the flag.
Extract the archive, cd fixme1, and install Rust/Cargo if you don't already have them.
Open src/main.rs so you can see the three broken lines side-by-side, then run cargo run and let the compiler errors tell you exactly what to fix.
wget https://challenge-files.picoctf.net/c_verbal_sleep/3f0e13f541928f420d9c8c96b06d4dbf7b2fa18b15adbd457108e8c80a1f5883/fixme1.tar.gztar -xvf fixme1.tar.gz && cd fixme1sudo apt install cargo -yless src/main.rscargo runSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Add the missing semicolon
ObservationThe first build error reads 'expected ;, found let' and points at the binding near line 5. Rust requires a semicolon at the end of every let statement.The first compiler error fires on theletbinding near line 5 withexpected ;, found let. Add the trailing semicolon and that error disappears.What didn't work first
Tried: Adding the semicolon at the end of the entire expression rather than at the end of the let binding line.
When the right-hand side spans several lines, it is easy to put the semicolon after a closing bracket rather than after the whole statement. Rust wants it at the end of the complete binding; anywhere else is still a syntax error. Add it on the line the compiler names.
Tried: Running cargo build instead of re-reading the error and fixing the right line.
A wall of compiler output tempts you to rebuild and hope, but Rust errors are deterministic: the same source produces the same error every time. Read the first message, fix that line, rebuild.
Learn more
Rust is an expression-based language with two distinct statement forms. A statement is terminated by a semicolon and produces no value. An expression without a semicolon at the end of a block becomes that block's return value. In a
letbinding likelet x = some_value, the semicolon is mandatory; omitting it makes the compiler think you're trying to uselet x = ...as an expression, which is not valid syntax.This distinction matters deeply in Rust because the last expression in a function body (without a semicolon) is implicitly returned. A common pattern is:
fn add(a: i32, b: i32) -> i32 { a + b }, noreturnkeyword needed. But if you accidentally add a semicolon aftera + b, the function returns()(the unit type) instead of the integer, causing a type error.Rust's compiler error messages are famously helpful: they point to the exact line, explain what went wrong, and often suggest a fix. Running
cargo check(which type-checks without producing a binary) is faster thancargo buildduring iterative debugging, andcargo clippycatches a broader set of stylistic and correctness issues.Step 2Fix the return keyword
ObservationThe next error says it cannot find a value named ret, so Rust is reading it as a variable rather than a keyword. Spell out return.Line 18 uses the shorthandret. Replace it with the fullreturnkeyword so the function exits cleanly.What didn't work first
Tried: Deleting the
retkeyword entirely and leaving just the value expression on that line.Delete ret and the line becomes a bare expression, which Rust reads differently depending on whether it is the function's last one. With code below it, the compiler complains about a misplaced value. Replace the word rather than removing it.
Tried: Searching online for a Rust
retkeyword or alias assuming it might be a crate macro.Rust has no ret keyword and no ret! macro. The error about a missing value means the compiler read it as a variable name. Write return.
Learn more
Unlike C or Java, Rust does not have abbreviations for keywords.
ret,fnalternatives, or other shorthands simply do not exist; the compiler treats unrecognized identifiers as variable names, leading to "cannot find valueretin this scope" errors. Rust's keyword list is strict and unlikely to grow, making the language's grammar stable and predictable.The
returnkeyword causes early exit from a function, analogous to other languages. However, idiomatic Rust prefers the expression-based implicit return for final values;returnis usually only needed for early exits in the middle of a function body (likeif error { return Err(e); }). The?operator goes further, automatically propagatingErrvariants fromResult-returning functions without an explicitreturnstatement.Learning Rust's keywords and grammar rules is worthwhile beyond CTF: Rust is now the second language (after C) approved for Linux kernel contributions, is heavily used in WebAssembly, systems programming, and security tooling. Familiarity with Rust is increasingly valuable for security engineers working on memory-safe rewrites of critical infrastructure.
Step 3Correct the println! format string
ObservationThe final println! uses the Debug specifier on a plain string, which wraps the output in quotes instead of printing the flag. Switch it to the Display form.The finalprintln!macro uses:?even though it expects{}. Swap the placeholder and re-runcargo runto print the flag.What didn't work first
Tried: Leaving the {:?} placeholder and adding #[derive(Debug)] to the string type to make it compile.
String already implements Debug, so nothing is missing a derive. The problem is that the Debug specifier prints the value with surrounding quotes, which is not the clean flag string. Display prints it plain.
Tried: Changing the placeholder to {:#?} thinking pretty-print debug would give a cleaner result.
{:#?} is the pretty-printed debug format and still wraps strings in quotes and may add indentation. For a plain string, only {} (Display) prints the raw characters without any extra decoration. Switch the placeholder to {} and re-run cargo run.
Learn more
Rust's
println!is a macro (note the!) that processes format strings at compile time, generating type-safe code. The two most common format specifiers are{}(Display trait, human-readable output) and{:?}(Debug trait, machine-readable, often quoted). Using{:?}on aStringcompiles without error becauseStringalready implements the Debug trait, but it prints the value wrapped in double quotes (e.g."picoCTF{...}") rather than the raw characters, so the output does not match the expected bare flag string.The Display trait is implemented manually for custom types and defines how they appear to end users. The Debug trait can be derived automatically with
#[derive(Debug)]and is intended for developer-facing output. Many CTF Rust challenges use{:?}where{}is needed because the author mixed up the two, making this a common error to recognize instantly.Beyond these, Rust supports
{:#?}(pretty-printed Debug),{:b}(binary),{:x}(lowercase hex),{:X}(uppercase hex), and{:e}(scientific notation). All are checked at compile time; a format string mismatch is a compiler error, not a runtime panic, which is one of Rust's key safety advantages over C'sprintf.
Interactive tools
- Cyclic Pattern GeneratorGenerate de Bruijn cyclic patterns and find buffer overflow offsets. The browser equivalent of pwntools cyclic and cyclic_find.
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Flag
Reveal flag
picoCTF{4r3_y0u_4_ru$t4c30n_...}
Follow-up challenges build on the same project. Keep Cargo installed if you plan to tackle Rust Fixme 2 and 3.