Rust fixme 1 picoCTF 2025 Solution

Published: April 2, 2025

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 1
    Add the missing semicolon
    Observation
    I noticed the first cargo run error reported expected ;, found let pointing to the let binding 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 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 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 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 2
    Fix the return keyword
    Observation
    I noticed the compiler error 'cannot find value ret in this scope' on line 18, which indicated Rust was treating ret as an undefined variable name rather than a keyword, suggesting I simply needed to spell out the full return keyword.
    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.

    Removing ret makes 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 replace ret with the full return keyword, not to delete it.

    Tried: Searching online for a Rust ret keyword or alias assuming it might be a crate macro.

    There is no ret! macro in the standard library and no ret keyword in Rust. The compiler error 'cannot find value ret in this scope' means Rust is treating it as a variable name, not a keyword. The correct fix is simply spelling out 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 3
    Correct the println! format string
    Observation
    I noticed the final println! 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 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 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 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.

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 rather than runtime: syntax rules, type mismatches, and format-specifier errors are all caught before the binary ever runs. This compile-time safety model is directly relevant to security because it eliminates entire classes of bugs (format-string vulnerabilities, type confusions) that remain exploitable in C programs. Learning to read Rust compiler errors fluently is the primary skill that transfers to auditing memory-safe rewrites of critical infrastructure and security tooling.

Related reading

Want more picoCTF 2025 writeups?

Useful tools for General Skills

What to try next