Return to String Basics

Where I try to give some life to poor “Bob”, but learn more about Clippy and Fmt

Pale robot face, is this the face of Bob?
I’m sorry “Bob”, I cannot do that.

Another sample programming problem and I am about to practice so string methods, but also learn about two Rust helpers: Cargo Clippy and Fmt. It is becoming apparent just how much help in Rust coding is embedded in the error messages and in cargo.

The next Exercism programming task was to have “Bob” respond in various ways to a passed in prompt. If you ask a question, Bob should respond with “Sure.” unless you are SCREAMING, in which case his response is “Calm down, I know what I’m doing!”. If you prompt “Bob” with nothing, he says “Fine. Be that way!”, but everything gets back a simple “Whatever.”

Coding Like a Rust Coder

Of course, there are many ways to check all these conditions. Still, I wanted to use the built-in helper methods and find the most Rust-idiomatic way. I was also cognizant of not repeating myself: not running the same method or checking the same test more than once and wasting clock cycles.

For instance, string.trim() removes leading and trailing whitespace, and combining that with a built-in test, we can see if the prompt is basically empty by using string.trim().is_empty()

I choose to see if “Bob” is being yelled at, but uppercasing the string and seeing if nothing changed – of course, that’s only true if the string includes at least 1 alphabetic character, so lets check that first with string.chars().any(char::is_alphabetic).

That will iterate using the chars() that we’ve seen before and tell me if any of them are a-z/A-Z. To uppercase the string (I’m only going to worry about the ASCII characters here; Rust also has to_uppercase which will follow the terms of the Unicode Derived Core Property Uppercase, see this for some issues with Unicode characters), so I’ll just use string.to_ascii_uppercase().




Searching for Possibilities

Lastly, it might be a “whispered question”, but I want to make sure the prompt isn’t entirely just a question mark – that doesn’t fit the premise of a question according to the Exercism-provided test I am still failing, so string.len() will give me the length of the string. I end up with this code:

 pub fn reply(message: &str) -> &str {
    let check_message = message.trim();

    if check_message.is_empty() {
        return "Fine. Be that way!";
    }

    let question = check_message.ends_with('?');

    if check_message.chars().any(char::is_alphabetic)
        && check_message.to_ascii_uppercase() == message
    {
        if question {
            return "Calm down, I know what I'm doing!";
        } else {
            return "Whoa, chill out!";
        }
    }

    if check_message.len() > 1 && question {
        return "Sure.";
    }
    "Whatever."
} 

So, “not running the same method or checking the same test more than once” meant I had to make a copy of the passed in string so I could trim() it just once at the beginning. I also didn’t want to test for a ‘?’ at the end of the prompt more than one time. Maybe my extra variable creation costs more than running trim() each time I would need it, I’m not sure!

Cargo Clippy and Fmt

Bad sci-fi movie robot, maybe his name can be Cargo Clippy
Cargo Clippy is here to help!

Another note: I used two Rust assistants: first, clippy to check my code before submitting it to Exercism. Clippy is basically a Rust lint program and will alert you to changes you can make to your code even though it compiles. For instance, I learned about is_empty() (instead of checking len() == 0) and I learned that ends_with() can apparently take a char OR a string (I originally had ends_with(“?”) since I was calling a method on a String type and I just assumed…)

You can get “clippy with it” (no, he didn’t!) by typing rustup component add clippy. I then ran it against my code by doing cargo clippy –all-targets.

Also, there is a prettifier called fmt; get it by doing rustup component add fmt and then run cargo fmt. This “fixed” my indentation and curly braces (and probably other things) to match what is commonly accepted correct-formatting for Rust scripts.

Crazy (En) code and decode

Where I fiddle with a crazy encoding scheme

laptop in dark room with green code on screen
The 0s and 1s have me…

Encoding with Rust code – sounds fun! Pulling from a different “programming exercise” site this time: CodeWars. One of the very first exercises there is a crazy encoding which looks like:

++++++++++++++++++++++++++++
++++++++++++++++++++++++++++
++++++++++++++++.++++++++++++
+++++++++++++++++.+++++++..+++.

Starting at ASCII value 0 when we begin decoding, we start processing the string:
+ means we add 1 to the ASCII value we are holding
. means we print the character based on the current ASCII value

If you add 1 to get 256, that would loop back to 0.

So, that mess above equates to the string “Hello” (you can probably find the two ‘l’s in the encoded string).

So, lets whip up a function to decode that:

Coding Time

fn decode(code: &str) -> String {
    let mut str = String::new();
    let mut cur: u8 = 0;

    for c in code.chars() {
        match c {
            '+' => cur = cur.wrapping_add(1),
            '.' => str.push(cur as char),
            _ => {},
        }
    }
    str
}

Simple enough, we get an encoded string as a parameter, we start our cur variable at 0 and loop thru the characters of the string. A simple match lets us choose what to do. BTW, wrapping_add means we don’t have to check for 256 ourselves, it will wrap back to 0 automatically. Anything other than a + or . we simply ignore.

So, for fun, lets write an encode function as well:

fn encode(orig: &str) -> String {
    let mut str = String::new();
    let mut cur: u8 = 0;

    for c in orig.chars() {
        while c != cur as char {
            cur = cur.wrapping_add(1);
            str.push('+');
        }
        str.push('.');
    }
    str
}

Just slightly different, but still quick and easy. Ok, we can setup a simple main() to test these:

fn main() {
     let original = "Hello, World!";

     let encoded = encode(&original);
     println!("'{}' encodes as '{}'", &original, &encoded);

     let decoded = decode(&encoded);
     println!("which then decodes to '{}'", &decoded);
     assert_eq!(original, decoded);
 }



Command Line Args

Well, that was fun. Hey, lets change it to take what we’d like encoded from the command line. There a built-in library to help with that and, of course, we only need to change main()

fn main() {
     for arg in std::env::args().skip(1) {
       let encoded = encode(&arg);
       let decoded = decode(&encoded);
       assert_eq!(arg, decoded);
       println!("'{}' encodes as '{}' and decode works",
           &arg, &encoded);
     }
 }

So, std::env::args will give us all the args a program was called with, including the program name itself which is always in [0] and why we want to skip(1) to avoid encoding it. So now:

cargo run “Please sir, may I have some more soup”

runs just fine – I’ll avoid showing you the encoded version! Also:

cargo run What in the world

runs, and encodes each word separately, since they are individual parameters to the program.

Ok, more fun, what if we changed the + to 1 and the . to 0 and made this encoding look like binary, lol! Now, “Hello” encodes as:

11111111111111111111111111111111111111111111111111111111111111111111111101111111111111111111111111111101111111001110

I have more ideas of ways to play with this, so more to come next post…

1 Mississippi, 2 Mississippi…

Where I learn about crates and modules and time… and sheep

Recycle logo on bag
Re-use code to make their owners proud!

Let’s learn about stealing reusing code! Rust has a developing library of code, from contributors, that you pull into your own programs and then not have to reinvent the wheel. Perl calls these modules and has CPAN as a repository and for searches, updates, and documentation. In Rust, these are instead called library crates and you can look around at the crates.io repository and others – plus create your own, internal repositories.

Searching Through Crates

For instance, go to crates.io and search for “chrono“. You should come to the chrono crates.io page. Right now, I’m seeing the most recent version as 0.4.7. Note if you had searched for “date time” instead, you’ll find many results, and the one with the huge number of downloads is chrono!

Like CPAN, crates.io can tell you the latest version (and how recent it is), if the current build of the crate is passing tests, usage information, and more. Note that if you click on the documentation link on the chrono crate page, you are taken to the docs.rs page: docs.rs/chrono/0.4.6/chrono/ and can read the full documentstion.

Now, we come to the term modules as it applies to Rust. A Rust crate has a default module, but can also have additional modules, providing additional features. For instance, from the docs.rs page about chrono, click modules on the left-side and you will see which modules are a part of chrono: format, naive, offset, prelude, and serde. We won’t need any of those additional modules, just some pieces of what the default chrono crate provides.

Rust Chrono Crate

Sheep and lamb looking at us
One second for every sheep in the world – plus some sheep left over.

I see our next Exercism problem uses the chrono crate in the solution (coincidence?!) Let’s take a look! So, they want us to display the date and time when 1 Gigasecond, from a given date and time, would occur. That is, given a date and time, figure out the date and time 1,000,000,000 seconds later. Who needs this? I dunno, but who cares about that.

Looking in the tests/gigasecond.rs file from Exercism, where the tests for this package are stored, the first one will be giving our code Utc.ymd(2011, 4, 25).and_hms(0, 0, 0) and expecting back Utc.ymd(2043, 1, 1).and_hms(1, 46, 40). Looks like a gigasecond is just shy of 32 years. If we check tests by running cargo test, we can see, of course, that it fails – we haven’t done any work yet!




Crate Semantic Versioning

Exercism did help us out though – they had already figured out the most popular crate to use for date/time types and added it as a dependency to our project. Check out the included Cargo.toml file and notice the one dependency at the end: chrono = “0.4”. Cargo looks on crates.io by default, and the version number listed is a semver (for semantic versioning). It is like a pattern to be used to determine which versions are allowed to be used for this project. In this case, specifying 0.4 means the latest 0.4.x version of the library should be pulled (so it will pull the 0.4.7 version we saw as the latest), but Cargo will not advance to 0.5. You’d have to test 0.5 and, if your code still works, modify your toml file manually. This prevents a potentially major upgrade of a library to suddenly break your code in your production deployment cycle.

Geese
We don’t tweet, we jabber – but not about Rust!

Enough jabbering – with all of the help (and documentation) chrono gives us, and all that background knowledge, I turn this into a single expression like prior problems we’ve had:

 use chrono::{DateTime, Utc, Duration}; 

 pub fn after(start: DateTime) -> DateTime {
     start + Duration::seconds(1_000_000_000)
 }

Exercism gave us use chrono::{DateTime, Utc}; in the answer already, I just tacked on “, Duration” when I realized I needed that struct of the chrono crate as well. We are passed in the parameter start, we add 1,000,000,000 seconds as a Duration type. Then the expression calculates and returns a DateTime result.

We could have written this without the use statement but I hope you’ll see its help in shortening the code is worth it:

 pub fn after(start: chrono::DateTime)
 -> chrono::DateTime {
     start + chrono::Duration::seconds(1_000_000_000)
 }

Light’s Green, Trap is Clean

Anyway, running cargo test now is green! We also run cargo test — –ignored to run all of the additional tests the Exercism team wrote, and they all pass as well!

So, another simple answer – but ONLY because someone went to all the trouble of DateTime and Duration processing and manipulation!