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 1
Add the missing semicolonObservationI noticed the firstcargo runerror reportedexpected ;, found letpointing to theletbinding near line 5, which told me Rust requires a mandatory semicolon at the end of every variable binding 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 multiple lines, beginners sometimes add the semicolon after the closing parenthesis or bracket of the value, not after the full let statement. In Rust the semicolon must follow the complete binding, so placing it anywhere else still produces a syntax error. Check the exact line the compiler error points to and add the semicolon there.
Tried: Running cargo build instead of re-reading the error and fixing the right line.
After seeing a wall of compiler output, some beginners try rebuilding without changing anything, hoping the error was transient. Rust errors are deterministic - the same broken source always produces the same error. Read the first error message carefully, fix that specific line, then re-run cargo run.
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 2
Fix the return keywordObservationI noticed the compiler error 'cannot find valueretin this scope' on line 18, which indicated Rust was treatingretas an undefined variable name rather than a keyword, suggesting I simply needed to spell out the fullreturnkeyword.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.Removing
retmakes the line look like a bare expression, which Rust may interpret differently depending on whether it is the last expression in the function. If there is more code below, the compiler will complain about an unused or misplaced value. The fix is to replaceretwith the fullreturnkeyword, not to delete it.Tried: Searching online for a Rust
retkeyword or alias assuming it might be a crate macro.There is no
ret!macro in the standard library and noretkeyword in Rust. The compiler error 'cannot find valueretin this scope' means Rust is treating it as a variable name, not a keyword. The correct fix is simply spelling outreturn.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 3
Correct the println! format stringObservationI noticed the finalprintln!call used{:?}(the Debug format specifier) for a plain string value, which would wrap the output in quotes rather than printing the raw flag, suggesting I needed to swap it to{}for the Display format.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 in Rust, so the compile error here is not about a missing derive. The issue is that {:?} produces quoted debug output like "picoCTF{...}" with surrounding quotes, which is not the clean flag string the challenge expects. Use {} (Display) to get the plain unquoted output.
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.
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.