Twelve Days of Christmas in Rust

Twelve Days of Christmas in Rust

We're going to walk through another program prompt from the Rust Book: "Print the lyrics to the Christmas carol 'The Twelve Days of Christmas'." I know the topic is a few weeks late, but should hopefully still be helpful!

Getting Started

Assuming you've installed Rust, you get started with a simple command in whatever directory you're in:

cargo new --bin twelve_days_of_christmas

This will generate the base project to get started. Go ahead and open src/main.rs and let's get started writing our code!

Change the main function to something like this:

fn main() {
    println!("TWELVE DAYS OF CHRISTMAS:");
}

If everything is set up correctly, when you run cargo run you should see the title printed out.

Days

One nice thing about this song is that it is pretty repetitive. Which means we should be able to extract a lot of the logic into functions!

Each day starts with the pattern of "On the blank day of Christmas my true love sent to me:". So let's start by creating a function that handles that:

fn day_intro(n: u32) {
    let day = match n {
        1 => "first",
        2 => "second",
        3 => "third",
        4 => "fourth",
        5 => "fifth",
        6 => "sixth",
        7 => "seventh",
        8 => "eighth",
        9 => "ninth",
        10 => "tenth",
        11 => "eleventh",
        12 => "twelfth",
        _ => "",
    };

    println!(
        "\nOn the {} day of Christmas\nmy true love sent to me:",
        day
    );
}

day_intro takes a positive integer and returns the corresponding line for that day. Since we know the song only goes up to twelve days, we use the match expression to match the numbers 1-12. If someone passed in another number they would get an empty string. We could throw an error, but for now this seems OK.

Next step is to call this function in main for each day:

fn main() {
    println!("TWELVE DAYS OF CHRISTMAS:");

    for day in 1..13 {
        day_intro(day);
    }
}

This uses a few new syntax tricks since the Fibonacci program, so let's walk through them. We added a for loop that iterates over items in a collection. In this case, that collection is a Range of the numbers 1 through 12. (The .. syntax is inclusive of the first number, and exclusive of the second.)

So, for each number, we assign it to a variable day and call our intro function with the day. When you cargo run at this point you should see something like:

TWELVE DAYS OF CHRISTMAS:

On the first day of Christmas
my true love sent to me:

On the second day of Christmas
my true love sent to me:

...

The Gifts

We've made some progress! But now we have to actually handle the gifts mentioned in the song. We can build a function that handles the numbers with a match expression again:

fn gift(n: u32, prefix: &str) {
    let gift_text = match n {
        1 => "a Partridge in a Pear Tree",
        2 => "Two Turtle Doves",
        3 => "Three French Hens",
        4 => "Four Calling Birds",
        5 => "Five Golden Rings",
        6 => "Six Geese a Laying",
        7 => "Seven Swans a Swimming",
        8 => "Eight Maids a Milking",
        9 => "Nine Ladies Dancing",
        10 => "Ten Lords a Leaping",
        11 => "Eleven Pipers Piping",
        12 => "12 Drummers Drumming",
        _ => "",
    };

    println!("{}{}", prefix, gift_text);
}

We pass in the day and return the corresponding gift for that number. Since sometimes day one needs an "and" in the front, we also allow the user to pass in a prefix to the line.

Now to update main to call this function as well:

fn main() {
    println!("TWELVE DAYS OF CHRISTMAS:");

    for day in 1..13 {
        day_intro(day);

        for gift_day in (1..(day + 1)).rev() {
            gift(
                gift_day,
                if gift_day == 1 && day != 1 {
                    "and "
                } else {
                    ""
                },
            );
        }
    }
}

The gifts always start with the gift for the current day, and then work backwards back to day one. We construct this with (1..(day + 1)) which will make sure we have the correct collection for each day. And since we need to go backwards, we call the .rev() method, which reverses the range so that for loop works backwards through the numbers.

Then we call our gift function for each number. If the current day isn't the first day, but we are on gift number one, then we pass in the correct prefix of "and".

Conclusion

Your src/main.rs file should now look like this:

fn day_intro(n: u32) {
    let day = match n {
        1 => "first",
        2 => "second",
        3 => "third",
        4 => "fourth",
        5 => "fifth",
        6 => "sixth",
        7 => "seventh",
        8 => "eighth",
        9 => "ninth",
        10 => "tenth",
        11 => "eleventh",
        12 => "twelfth",
        _ => "",
    };

    println!(
        "\nOn the {} day of Christmas\nmy true love sent to me:",
        day
    );
}

fn gift(n: u32, prefix: &str) {
    let gift_text = match n {
        1 => "a Partridge in a Pear Tree",
        2 => "Two Turtle Doves",
        3 => "Three French Hens",
        4 => "Four Calling Birds",
        5 => "Five Golden Rings",
        6 => "Six Geese a Laying",
        7 => "Seven Swans a Swimming",
        8 => "Eight Maids a Milking",
        9 => "Nine Ladies Dancing",
        10 => "Ten Lords a Leaping",
        11 => "Eleven Pipers Piping",
        12 => "12 Drummers Drumming",
        _ => "",
    };

    println!("{}{}", prefix, gift_text);
}

fn main() {
    println!("TWELVE DAYS OF CHRISTMAS:");

    for day in 1..13 {
        day_intro(day);

        for gift_day in (1..(day + 1)).rev() {
            gift(
                gift_day,
                if gift_day == 1 && day != 1 {
                    "and "
                } else {
                    ""
                },
            );
        }
    }
}

And that's it! If you do cargo run now, you should see the lyrics print out:

TWELVE DAYS OF CHRISTMAS:

On the first day of Christmas
my true love sent to me:
a Partridge in a Pear Tree

On the second day of Christmas
my true love sent to me:
Two Turtle Doves
and a Partridge in a Pear Tree

On the third day of Christmas
my true love sent to me:
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the fourth day of Christmas
my true love sent to me:
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the fifth day of Christmas
my true love sent to me:
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the sixth day of Christmas
my true love sent to me:
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the seventh day of Christmas
my true love sent to me:
Seven Swans a Swimming
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the eighth day of Christmas
my true love sent to me:
Eight Maids a Milking
Seven Swans a Swimming
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the ninth day of Christmas
my true love sent to me:
Nine Ladies Dancing
Eight Maids a Milking
Seven Swans a Swimming
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the tenth day of Christmas
my true love sent to me:
Ten Lords a Leaping
Nine Ladies Dancing
Eight Maids a Milking
Seven Swans a Swimming
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the eleventh day of Christmas
my true love sent to me:
Eleven Pipers Piping
Ten Lords a Leaping
Nine Ladies Dancing
Eight Maids a Milking
Seven Swans a Swimming
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

On the twelfth day of Christmas
my true love sent to me:
12 Drummers Drumming
Eleven Pipers Piping
Ten Lords a Leaping
Nine Ladies Dancing
Eight Maids a Milking
Seven Swans a Swimming
Six Geese a Laying
Five Golden Rings
Four Calling Birds
Three French Hens
Two Turtle Doves
and a Partridge in a Pear Tree

Hopefully this was a fun way to continue exploring Rust and some new concepts. If you missed my first Rust post on generating Fibonacci numbers, make sure you check that one out too!

If you are creating anything with Rust, feel free to share! I'd love to see what you are working on!