Skip to main content

Rust fixme 1 picoCTF 2025 Solution

Three syntax errors block compilation: a missing semicolon, a stray return keyword, and a bad println! format string.

Published: April 2, 2025Updated: August 25, 2026

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.

bash
wget https://challenge-files.picoctf.net/c_verbal_sleep/3f0e13f541928f420d9c8c96b06d4dbf7b2fa18b15adbd457108e8c80a1f5883/fixme1.tar.gz
bash
tar -xvf fixme1.tar.gz && cd fixme1
bash
sudo apt install cargo -y
bash
less src/main.rs
bash
cargo run

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Add the missing semicolon
    Observation
    The 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 the let binding near line 5 with expected ;, 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 let binding like let x = some_value, the semicolon is mandatory; omitting it makes the compiler think you're trying to use let 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 }, no return keyword needed. But if you accidentally add a semicolon after a + 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 than cargo build during iterative debugging, and cargo clippy catches a broader set of stylistic and correctness issues.

  2. Step 2Fix the return keyword
    Observation
    The 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 shorthand ret. Replace it with the full return keyword so the function exits cleanly.
    What didn't work first

    Tried: Deleting the ret keyword 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 ret keyword 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, fn alternatives, or other shorthands simply do not exist; the compiler treats unrecognized identifiers as variable names, leading to "cannot find value ret in this scope" errors. Rust's keyword list is strict and unlikely to grow, making the language's grammar stable and predictable.

    The return keyword causes early exit from a function, analogous to other languages. However, idiomatic Rust prefers the expression-based implicit return for final values; return is usually only needed for early exits in the middle of a function body (like if error { return Err(e); }). The ? operator goes further, automatically propagating Err variants from Result-returning functions without an explicit return statement.

    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.

  3. Step 3Correct the println! format string
    Observation
    The 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 final println! macro uses :? even though it expects {}. Swap the placeholder and re-run cargo run to 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 a String compiles without error because String already 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's printf.

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.

Key takeaway

Rust enforces correctness at compile time: syntax, type mismatches, and format specifiers are all settled before the binary runs. That matters for security because it removes whole bug classes, format-string vulnerabilities and type confusion among them, that stay exploitable in C. Reading Rust compiler errors fluently is the skill that carries into auditing memory-safe rewrites of critical infrastructure.

Related reading

Useful tools for General Skills

Where to go next