First of all, I dabbled in Rust a couple of years ago, but that was it—just going over data types and syntax. But I want to learn more about this beast that blows every benchmark out of the water.

Refreshing our knowledge and learning more about the basics

First, I started looking for a worthwhile Spanish course on YouTube, and that's when I came across this 4-hour course in Spanish, which is really great.

Imagen del artículo


After an hour of reviewing the material, I created my first Cargo project for 2024.

rust
fn main() {
    println!("Hello, chupapis!");
}

Yeah, that's what I was expecting xD.

Imagen del artículo

Creating Something More Elaborate

Anyway, I couldn't just leave it at that. I wanted to do something simple to start with. What could be better than a CLI that creates a user and then displays the user's information?

rust
fn main() {
    println!("Hello, chupapis!");
}

struct User {
    name: String,
    age: i8,
    email: String,
    birthday: // No hay Date .___.
}

Hmm... Rust doesn't have a `Date` type 🥲

Faced with these problems: “Time to ask ChatGPT 👍.”

Imagen del artículo

That's so cool! 😯 And he gave me this example:

rust
use chrono::NaiveDate;

fn main() {
    let user = User {
        name: String::from("Diego"),
        age: 22,
        email: String::from("diego@example.com"),
        birthday: NaiveDate::from_ymd(2002, 3, 28), // Año, mes, día
    };

    println!(
        "Nombre: {}, Edad: {}, Email: {}, Cumpleaños: {}",
        user.name, user.age, user.email, user.birthday
    );
}

struct User {
    name: String,
    age: i8,
    email: String,
    birthday: NaiveDate,
}

What I didn't know was that….

Imagen del artículo

from_ymd This is deprecated 🥲

But problems are meant to be solved. I'll use the recommended one. from_ymd_opt .

rust
fn main() {
    let user = User {
        name: String::from("Diego Musagy"),
        age: 22,
        email: String::from("diego.1203.dm@gmail.com"),
        birthday: NaiveDate::from_ymd_opt(2002, 3, 28)
            .expect("Fecha inválida"),
    };

    println!(
        "Nombre: {}, Edad: {}, Email: {}, Cumpleaños: {}",
        user.name, user.age, user.email, user.birthday
    );
}

struct User {
    name: String,
    age: i8,
    email: String,
    birthday: NaiveDate,
}

Now that was a whole different story. 😎

But it still lacked interactivity, so now I had to learn how to receive input.

rust
use std::io::stdin as input;

Yep, just to make it more fun.

Imagen del artículo

And after a few minutes, I ended up with this. 💀

rust
use chrono::NaiveDate;
use std::io::stdin as input;

fn main() {
    let mut name = String::from("");
    let mut age_str = String::from("");
    let age: u8;
    let mut email = String::from("");
    let mut bd_year_str = String::from("");
    let bd_year: i32;
    let mut bd_month_str = String::from("");
    let bd_month: u32;
    let mut bd_day_str = String::from("");
    let bd_day: u32;
    
    println!("¿Cual es tu nombre?");
    input().read_line(&mut name)
        .expect("error al leer la entrada");

    println!("¿Cual es tu edad?");
    input().read_line(&mut age_str)
        .expect("error al leer la entrada");
    age = age_str.trim()
        .parse()
        .expect("Por favor. Inserte un numero correcto.");

    println!("¿Cual es tu email?");
    input().read_line(&mut email)
        .expect("error al leer la entrada");
    
    println!("¿Cual es tu cumpleaños?");
    println!("\tAño: ");
    input().read_line(&mut bd_year_str)
        .expect("error al leer la entrada");
    println!("\tMes: ");
    input().read_line(&mut bd_month_str)
        .expect("error al leer la entrada");
    println!("\tDia: ");
    input().read_line(&mut bd_day_str)
        .expect("error al leer la entrada");
    
    bd_year = bd_year_str.trim()
        .parse()
        .expect("Por favor. Inserte un numero correcto.");
    bd_month = bd_month_str.trim()
        .parse()
        .expect("Por favor. Inserte un numero correcto.");
    bd_day = bd_day_str.trim()
        .parse()
        .expect("Por favor. Inserte un numero correcto.");

    let birthday = NaiveDate::from_ymd_opt(bd_year, bd_month, bd_day)
        .expect("Fecha invalida");

    let user = User {
        name,
        age,
        email,
        birthday,
    };

    println!(
        "Nombre: {}, Edad: {}, Email: {}, Cumpleaños: {}",
        user.name, user.age, user.email, user.birthday
    );
}

struct User {
    name: String,
    age: u8,
    email: String,
    birthday: NaiveDate,
}

Yeah, a lot of “talking to myself,” but it works at the end of the day.

Let's refactor it.

The first thing I did was make a read_line generic. the variable's responsibility for retrieving the content “RAW.” It would remain within the function itself, so that the scope would eliminate the variable when the function ends. And of course, the only thing it would return would be a string clean and with line breaks \n. 👍

rust
fn read_line (placeholder: &str) -> String {
    println!("{}", placeholder);
    let mut input_var= String::from("");
    input()
        .read_line(&mut input_var)
        .expect("error al leer la entrada");
    input_var.trim().to_string()
}

Now, in the case of the integer First, I did something like this:

rust
fn read_u8(placeholder: &str) -> u8 {
    loop {
        let raw = read_line(placeholder);
        match raw.parse() {
            Ok(value) => return value,
            Err(_) => println!("Inserte un numero valido")
        };
    }   
}
fn read_i32(placeholder: &str) -> i32 {
    loop {
        let raw = read_line(placeholder);
        match raw.parse() {
            Ok(value) => return value,
            Err(_) => println!("Inserte un numero valido")
        };
    }   
}
fn read_u32(placeholder: &str) -> u32 {
    loop {
        let raw = read_line(placeholder);
        match raw.parse() {
            Ok(value) => return value,
            Err(_) => println!("Inserte un numero valido")
        };
    }   
}

But I feel like something's off xD

I'd have to learn "generics" so I can use any type of data I want, just like we do with TypeScript generics.

typescript
function firstElement<T>(array: T[]): T | undefined {
    return array[0];
}

let primerNumero = firstElement([1, 2, 3]);          // T es number
let primeraPalabra = firstElement(["a", "b", "c"]);  // T es string

I figured this was already pretty advanced, so I went to ask ChatGPT.

He gave me this from here.

rust
fn read_int<T: std::str::FromStr>(placeholder: &str) -> T
where
    T::Err: std::fmt::Debug,
{
    loop {
        let raw = read_line(placeholder);
        match raw.parse::<T>() {
            Ok(value) => return value,
            Err(_) => println!("Por favor, ingrese un número válido."),
        }
    }
}

The most exotic thing would be that "guy" from the opening credits, std::str::FromStr and what he told me was:

Imagen del artículo

The only thing I can say is that I already know why Rust is so secure—it even restricts you when it comes to generics, requiring you to specify the exact operations that need to be PERFORMED so that the… function works correctly—pardon the repetition xD

And what I ended up with was this function.

rust
fn read_int<T: std::str::FromStr>(placeholder: &str) -> T {
    loop {
        let raw = read_line(placeholder);
        match raw.parse::<T>() {
            Ok(value) => return value,
            Err(_) => println!("Inserte un numero valido")
        };
    }   
}

By not catching the error returned by the parse if it fails in the match, I just take away the where That's syntax I'm not familiar with yet, but I suppose it was meant to ensure that the error can be printed. This makes it a little cleaner.

And finally, create a function to read a date.

rust
fn read_date(placeholder: &str) -> NaiveDate {
    println!("{}", placeholder);
    let year = read_int("Año: ");
    let month = read_int("Mes: ");
    let day = read_int("Dia: ");
    
    NaiveDate::from_ymd_opt(year, month, day)
        .expect("Fecha invalida")
}

And the read_int It already returns the types required by the method from_ymd_opt automatically.

I'm refactoring the main and it would look like this:

rust
use chrono::NaiveDate;
use std::io::stdin as input;

fn main() {
    let name = read_line("¿Cual es tu nombre?");
    let age = read_int("¿Cual es tu edad?");
    let email = read_line("¿Cual es tu Email?");
    let birthday = read_date("¿Cual es tu cumpleaños?");

    let user = User {
        name,
        age,
        email,
        birthday
    };
    
    println!("Hello, chupapis!");

    println!(
        "Nombre: {}, Edad: {}, Email: {}, Cumpleaños: {}",
        user.name, user.age, user.email, user.birthday
    );
}

fn read_line (placeholder: &str) -> String {
    println!("{}", placeholder);
    let mut input_var= String::from("");
    input()
        .read_line(&mut input_var)
        .expect("error al leer la entrada");
    input_var.trim().to_string()
}

fn read_int<T: std::str::FromStr>(placeholder: &str) -> T {
    loop {
        let raw = read_line(placeholder);
        match raw.parse::<T>() {
            Ok(value) => return value,
            Err(_) => println!("Inserte un numero valido")
        };
    }   
}

fn read_date(placeholder: &str) -> NaiveDate {
    println!("{}", placeholder);
    let year = read_int("Año: ");
    let month = read_int("Mes: ");
    let day = read_int("Dia: ");
    
    NaiveDate::from_ymd_opt(year, month, day)
        .expect("Fecha invalida")
}

struct User {
    name: String,
    age: u8,
    email: String,
    birthday: NaiveDate
}

and when I run it, I get this result:

Imagen del artículo

And that's all for now. I guess the next step is to build a REST API. I'll start looking into whether to choose Actix, Axum, or Rocket.