Learning Rust with ChatGPT and Copilot (1)

This is a series of posts of learning Rust by asking questions to ChatGPT. For reference, I had almost no experience of Rust but I knew some features of the language, like ownership and how annoyingly good the compiler is.

Keep in mind that GPT lies a lot. But the good thing about Rust is the excellent compiler. If GPT tells a lie, Rust compiler yells at you, then you can either try to ask GPT again or google it.

I used Copilot to assist me in writing one of my first Rust code. I didn't want to waste time to asking/googling some small syntax that I don't know of. It is also great when you create a bunch of sample data for your practice code. It does tend to overwork a little bit and sometimes too smart for me to get used to writing Rust by hand but... eventually this is going to be the way to write code, and it is more important to be able to understand AI-generated code, and correct wrong/imperfect parts.

Learning basics

I had GPT-4 list up the language features and asked for code examples for each. It tends to give me more useful answer if I give my situation like "I'm a software engineer and know TypeScript, but have never written Rust".

It is important to understand even small stuff as you go. I opened another conversation with GPT-3.5 (because I'll be asking a bunch of questions) and asked questions like "Why do you have to write String::from()" or "Can you teach me Rust enum in details".

You can also just google, but I found the Rust community tends to overcomplicate basic explanation when you're new. GPT does a better job until you grasp the overall picture.

Learning through practices

For each of the language feature GPT gave me, I asked for a exercise task. GPT tends to just giveaway answer right after giving the task so I made it explicit that I didn't want answer immediately.

You should definitely set up local environment and run it on your own. GPT is not a Rust compiler, and should never be relied on because it would just lie. Once I wrote and ran the code on my computer and it looks good, I gave it to GPT. Sometimes it gave me useful suggestions.

More practice

Once I finished going through the language features it initially gave me, I opened another GPT-4 conversation and asked for more exercise. It gave me a task that involves user inputs it was more practical.

My code

use regex::Regex;
use std::fmt;
use std::io;

enum TemperatureUnit {
    Celsius,
    Fahrenheit,
    Kelvin,
}
impl fmt::Display for TemperatureUnit {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            TemperatureUnit::Celsius => write!(f, "C"),
            TemperatureUnit::Fahrenheit => write!(f, "F"),
            TemperatureUnit::Kelvin => write!(f, "K"),
        }
    }
}

fn prompt_temperature() -> Result<(f64, TemperatureUnit), String> {
    let temperature_input_regex = Regex::new(r"^(\d+\.?\d*)\s*(C|F|K)$").unwrap();

    println!("Convert temperature with a unit (e.g. 92.8 C): ");

    let mut input = String::new();

    if let Err(err) = io::stdin().read_line(&mut input) {
        return Err(format!("Failed to read input: {err}"));
    }

    if let Some(captures) = temperature_input_regex.captures(input.trim()) {
        let value = match captures.get(1) {
            Some(value) => match value.as_str().parse::<f64>() {
                Ok(value) => value,
                Err(err) => return Err(format!("Failed to parse input: {err}")),
            },
            None => return Err("Invalid input".to_string()),
        };

        let unit = match captures.get(2) {
            Some(unit) => match unit.as_str() {
                "C" => TemperatureUnit::Celsius,
                "F" => TemperatureUnit::Fahrenheit,
                "K" => TemperatureUnit::Kelvin,
                unit => return Err(format!("Invalid unit: {unit}")),
            },
            None => return Err("Invalid input".to_string()),
        };

        return Ok((value, unit));
    } else {
        return Err(format!("Invalid input: {input}"));
    }
}

fn prompt_target_unit() -> Result<TemperatureUnit, String> {
    println!("Convert to unit (C, F, K): ");

    let mut input = String::new();

    if let Err(err) = io::stdin().read_line(&mut input) {
        return Err(format!("Failed to read input: {err}"));
    }

    match input.trim() {
        "C" => Ok(TemperatureUnit::Celsius),
        "F" => Ok(TemperatureUnit::Fahrenheit),
        "K" => Ok(TemperatureUnit::Kelvin),
        unit => Err(format!("Invalid unit: {}", unit)),
    }
}

fn convert(temperature: f64, from: &TemperatureUnit, to: &TemperatureUnit) -> f64 {
    match from {
        TemperatureUnit::Celsius => match to {
            TemperatureUnit::Celsius => temperature,
            TemperatureUnit::Fahrenheit => temperature * 9.0 / 5.0 + 32.0,
            TemperatureUnit::Kelvin => temperature + 273.15,
        },
        TemperatureUnit::Fahrenheit => match to {
            TemperatureUnit::Celsius => (temperature - 32.0) * 5.0 / 9.0,
            TemperatureUnit::Fahrenheit => temperature,
            TemperatureUnit::Kelvin => (temperature + 459.67) * 5.0 / 9.0,
        },
        TemperatureUnit::Kelvin => match to {
            TemperatureUnit::Celsius => temperature - 273.15,
            TemperatureUnit::Fahrenheit => temperature * 9.0 / 5.0 - 459.67,
            TemperatureUnit::Kelvin => temperature,
        },
    }
}

fn main() {
    let (temperature, from) = match prompt_temperature() {
        Ok(temperature) => temperature,
        Err(err) => {
            println!("{err}");
            return;
        }
    };

    let to = match prompt_target_unit() {
        Ok(unit) => unit,
        Err(err) => {
            println!("{err}");
            return;
        }
    };

    let result = convert(temperature, &from, &to);

    println!("Input temperature {temperature} {from} is equivalent to {result} {to}.")
}

(I've already falled in love with destructuring and pattern matching... It's so good, or at least way better than JavaScript/TypeScript)

I guess I take it as a win.

(cont.)