← thecodex.expert · The Codex Family of Knowledge
Tier 0 · Absolute Beginner · Rust Project

Temperature Converter

Convert between Celsius and Fahrenheit. A small project that teaches functions, floating-point numbers, and turning a menu choice into a decision.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a small program that converts a temperature from Celsius to Fahrenheit, or the other way around. The user picks which direction, types a number, and the program does the maths and shows the answer. Simple — but it teaches the core shape of every calculator-style tool: ask, calculate, show.

Where this shows up in real life: every app that takes a number and transforms it — a currency converter, a tip calculator, a unit converter, a tax estimator — is doing exactly this. Take input, apply a formula, return a result. Master the pattern here and you can build any of them.

2 How to Think About It

Two formulas, one menu. The whole program is: ask which direction, ask the value, apply the right formula, print the result.

The plan — in plain English
1. Show the two choices. → 2. Read the choice and the value. → 3. Apply the matching formula. → 4. Print the result, formatted to one decimal place.

1

2

Ask: convert which way?

Choice 1 or 2?

Read Celsius

Read Fahrenheit

Multiply by 9/5, add 32

Subtract 32, multiply by 5/9

Show Fahrenheit

Show Celsius

3 The Build — explained part by part

Here is the complete converter, with the actual conversion logic pulled into small, independently testable functions.

Rustsrc/main.rs
use std::io::{self, Write};

fn c_to_f(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

fn f_to_c(f: f64) -> f64 {
    (f - 32.0) * 5.0 / 9.0
}

/// Runs one convert-and-print cycle for a menu choice ("1" = C->F,
/// "2" = F->C) and a value, returning the formatted result line — or an
/// error message for a bad choice, kept separate from I/O so it is testable.
fn convert(choice: &str, value: f64) -> Result<String, String> {
    match choice.trim() {
        "1" => Ok(format!("{value:.1}\u{b0}C = {:.1}\u{b0}F", c_to_f(value))),
        "2" => Ok(format!("{value:.1}\u{b0}F = {:.1}\u{b0}C", f_to_c(value))),
        other => Err(format!("Unknown choice '{other}' — pick 1 or 2.")),
    }
}

fn main() {
    let stdin = io::stdin();
    println!("1) Celsius to Fahrenheit");
    println!("2) Fahrenheit to Celsius");
    print!("Choose 1 or 2: ");
    io::stdout().flush().ok();

    let mut choice = String::new();
    stdin.read_line(&mut choice).ok();

    print!("Enter the temperature: ");
    io::stdout().flush().ok();
    let mut value_line = String::new();
    stdin.read_line(&mut value_line).ok();

    let value: f64 = match value_line.trim().parse() {
        Ok(v) => v,
        Err(_) => {
            println!("That's not a number.");
            return;
        }
    };

    match convert(&choice, value) {
        Ok(line) => println!("{line}"),
        Err(msg) => println!("{msg}"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn converts_freezing_point_c_to_f() {
        assert_eq!(convert("1", 0.0).unwrap(), "0.0\u{b0}C = 32.0\u{b0}F");
    }

    #[test]
    fn converts_boiling_point_f_to_c() {
        assert_eq!(convert("2", 212.0).unwrap(), "212.0\u{b0}F = 100.0\u{b0}C");
    }

    #[test]
    fn round_trips_a_value() {
        let f = c_to_f(37.0);
        let back = f_to_c(f);
        assert!((back - 37.0).abs() < 0.0001);
    }

    #[test]
    fn rejects_an_unknown_choice() {
        assert!(convert("9", 100.0).is_err());
    }
}
⚠ No in-browser playground here
Rust compiles to a real binary, so unlike the Python version of this project there is no editor above you can run in the browser. Copy the code below and run it on your own machine — it takes seconds once Rust (via rustup) is installed.
What each part does — in plain words
fn c_to_f(c: f64) -> f64 and f_to_c — each formula lives in its own tiny function that takes and returns an f64 (a 64-bit floating-point number), Rust’s default float type. Keeping them separate from I/O is what makes them testable on line 1 below without any stdin involved.

match choice.trim() { "1" => ..., "2" => ..., other => ... } — Rust’s match is exhaustive: the compiler forces a catch-all arm (other) so every possible string is handled, unlike an if/else if chain you could forget to close.

format!("{value:.1}°C = {:.1}°F", ...) — the :.1 inside the format specifier rounds to one decimal place, and {value} is Rust’s captured-identifier shorthand: it reads the local variable value directly instead of needing {}, value.

Result<String, String> — convert returns either a formatted string or an error message, so the compiler forces the caller to handle both instead of trusting the menu choice blindly.
Common mistakes — and how to avoid them
✗ Forgetting .trim() on the choice string before matching on it — read_line keeps the trailing newline, so "1\n" never matches the pattern "1".
✓ Always .trim() a line read from stdin before comparing or parsing it.
✗ Using integer division for the 9/5 factor — 9 / 5 on integers truncates to 1, silently breaking every conversion.
✓ Write the formula with float literals, 9.0 / 5.0, as the code above does.

4 Test & Prove Each Part

We test the two formulas directly, and the menu-choice logic, without ever needing to type anything.

0°C converts to exactly 32°F
212°F converts to exactly 100°C
Converting forward and back returns (almost) the original number
An unknown menu choice returns an error instead of a wrong answer
Rustsrc/main.rs (tests module)
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn converts_freezing_point_c_to_f() {
        assert_eq!(convert("1", 0.0).unwrap(), "0.0\u{b0}C = 32.0\u{b0}F");
    }

    #[test]
    fn converts_boiling_point_f_to_c() {
        assert_eq!(convert("2", 212.0).unwrap(), "212.0\u{b0}F = 100.0\u{b0}C");
    }

    #[test]
    fn round_trips_a_value() {
        let f = c_to_f(37.0);
        let back = f_to_c(f);
        assert!((back - 37.0).abs() < 0.0001);
    }

    #[test]
    fn rejects_an_unknown_choice() {
        assert!(convert("9", 100.0).is_err());
    }
}

Run with cargo test. The round-trip test compares with a small tolerance (< 0.0001) rather than exact equality, because floating-point arithmetic can leave a tiny rounding error — comparing floats for exact equality is a classic bug in any language.

5 The Interface

INPUTINPUTmenu choice + number
What it expects
Choice: "1" or "2"
Value: a number like 100
OUTPUTOUTPUTformatted conversion
What it returns
100.0°C = 212.0°F

6 Run It & Automate It

Save the code as src/main.rs inside a Cargo project's src/ folder and run it with cargo run — Cargo compiles and executes in one step while you are experimenting, then cargo build --release gives you an optimized binary once you are done.

Run it locally
cargo run
Enter 1 or 2, then the temperature value.

A CI tool like Jenkins runs cargo test automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
1) Celsius to Fahrenheit
2) Fahrenheit to Celsius
Choose 1 or 2: 1
Enter the temperature: 100
100.0°C = 212.0°F
If it breaks — how to fix it
🚨 That’s not a number.
The value you typed could not be parsed as an f64. Make sure it is a plain number like 100 or 36.6, with no stray letters or units.
🚨 Unknown choice '1\r' — pick 1 or 2.
On Windows, a line can end in \r\n rather than just \n. .trim() already strips both, so if you see this, check you did not remove that call.
GroovyJenkinsfile
// Jenkinsfile — runs the tests automatically every time the code changes.
pipeline {
    agent any                                  // run on any available machine

    stages {
        stage('Get the code') {
            steps { checkout scm }             // download the latest code
        }
        stage('Set up Rust') {
            steps {
                sh 'rustc --version'                // confirm Rust is installed
                sh 'cargo build'                     // compile, downloading any crates
            }
        }
        stage('Run the tests') {
            steps {
                sh 'cargo clippy -- -D warnings'     // catch obvious mistakes before running
                sh 'cargo test'                       // run every test, show each result
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Add Kelvin. A three-way match instead of two arms. (Teaches: extending an exhaustive match.)
  2. Accept a command-line argument. Read from std::env::args() instead of a menu. (Teaches: non-interactive CLI design.)
  3. Round-trip validation. Warn if converting forward then back does not return the original value within a tolerance. (Teaches: floating-point comparison.)
What you learned
You learned Rust’s exhaustive match, the format! macro’s precision specifiers and captured-identifier shorthand, and why floats should never be compared for exact equality. Related: Control Flow, Data Types.