DEV Community

loading...

Blog post: First Kata: "Multiply"

jonasbn
Computer programmer, runner, LEGO builder, powernapper, yakshaver and father of 2 boys all squeezed in the few hours available.
Updated on ・6 min read

Okay as promised in my previous post additional Rust posts was possibly in the pipeline, I also mentioned Codewars.com and the katas I use for practice.

If you are into Codewars.com this post (and hopefully subsequent posts) will contain spoilers - now you have been warned!

I write these up primarily for my own education and reference, since seems to to stick better if I jot them down, if you find this useful that is bonus - so here goes.

First kata: "multiply". The Kata problem was formulated as:

The code does not execute properly. Try to figure out why.

The original code, looked as follows:

fn multiply(a:i32, b:i32) {
  a * b
}

Here follows my solution, the solution in itself is not particularly interesting, but I can reflect on some of the things I learned from my solution (and possibly the failures leading up to the working solution)and I can spice it up with my approach to solving katas.

fn main () {
    let c = multiply(8, 8);
    println!("{}", c);
}

fn multiply(a:i32, b:i32) -> i32 {
  a * b
}

The main function is just so you can run it from the command line, so the project was generated using cargo like so:

$ cargo new --bin multiply
     Created binary (application) `multiply` package

This generated the src/main.rs file in a directory named: multiply containing the main function.

Line 1: fn main() {

Read up on the anatomy of a Rust program.

The main function is special: it is always the first code that runs in every executable Rust program

  • We have no parameters, hence the empty parentheses ()
  • The function body is defined between the curly braces { ... }, well we only see the first in line one, but the other one follows,

Now we have an idea about what function looks like in Rust. As described in the Rust cheatsheet:

fn f() {}

And as you have noticed, when generating an application with cargo you get a classical "hello world" example for free:

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

Now lets skip the contents of the main function and look at the next function: multiply

fn multiply(a:i32, b:i32) -> i32 {
  a * b
}

Using our newly obtained knowledge, we can read that the function is named "multiply":

Line 6: fn multiply(a:i32, b:i32) -> i32 {

It takes parameters since the parentheses is populated and finally we observe something new: -> i32.

Let's start with the parameters. The function takes to parameters: a and b, if you read a little longer into the body of the code you can see that, a and b are our multiplication operands.

The two parameters are annotated with types: i32 which mean signed integer of size 32. The important lesson here is for me to understand two things.

  1. Parameter specification for our function
  2. and types

As I described in my post on Learning Rust, there are good resources documenting Rust so if you want to have some details on i32 just look it up.

And finally for the -> i32, it describes the return value of the function. So we both take parameters of the type ì32 and we return i32.

Line 7: a * b

This lines uses our two operands, which match the parameters.

Here we can observe another Rust thing: the implicit return value, I would imagine that some people coming from other programming languages, find this a tad special. The implicit return value is the last value. You do not have to write an implicit return.

The same functionality exists in Perl, I must admit that I prefer explicit returns I am bit of an readability freak and I am not a huge fan of implicitness and magic in particular.

So a * b could be written return a * b;, but since I am focussed on learning Rust, understanding and using idiomatic Rust is also a part of the curriculum. I will get back to idiomatic code in another blog post, since it is something I have reflected on (and I need to get out of my system).

Out complete implementation of "multiply" end looking as follows:

fn multiply(a:i32, b:i32) -> i32 {
  a * b
}

When you work on katas on Codewars.com, most katas hold unit-tests for testing your solution. The original tests for this kata looks as follows.

#[test]
fn returns_expected() {
  assert_eq!(multiply(3, 5), 15)
}

You could do TDD and write a lot of tests for observing that your code works and I encourage doing this.

The test suite can be run using cargo

$ cargo test
   Compiling multiply v0.1.0 (/Users/jonasbn/develop/github/rust-multiply-kata)
    Finished dev [unoptimized + debuginfo] target(s) in 0.45s
     Running target/debug/deps/multiply-b4e442fac2c38b72

running 1 test
test returns_expected ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

I will not go into tests in this post, since our focus is on something else, the basic test structure is simple and you should be able to tweak it to your needs, so please read on.

Since we are using cargo and we get the main for free function we can also do manual testing. It just requires some minor tweaks to the main function, so we can allow it to take parameters, so we provide parameters via the command line.

Lets first do it using the modified version of a "Hello World", changed to the also popular "greeting" example.

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() == 2 {

        let name = &args[1];

        println!("Greetings {} from {}", name, &args[0]);
    } else {
        eprintln!("Usage: greetings «name»");
    }
}

Getting string parameters to your Rust command line application is pretty basic and the documentation is pretty clear. If you are doing something more complex I am sure there is a crate that can help you out.

Now lets apply this to our multiplier:

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() == 3 {

        let operand1: i32 = args[1].parse().unwrap();
        let operand2: i32 = args[2].parse().unwrap();

        let result = multiply(operand1, operand2);
        println!("{}", result);
    } else {
        eprintln!("Usage: multiply «operand1» «operand2»");
    }
}

Do note that there are a subtle difference between then two, since we have to cast the parameters from strings to integers.

so line 8 and 9 does this: let operand1: i32 = args[1].parse().unwrap(); for each of the operands.

And there you have it, you can now test your Rust command line application if need be.

But lets get back to doing proper software development, since manual testing is tiresome and trivial (and error prone), so lets extend the unit-test suite with some more tests.

As shown earlier a test suite looks as follows:

#[test]
fn returns_expected() {
  assert_eq!(multiply(3, 5), 15)
}

We can easily extend this with some interesting scenarios and corner cases, because a single test should capture the essence of such a simple function:

#[test]
fn returns_expected() {
  assert_eq!(multiply(3, 5), 15);
  assert_eq!(multiply(3, 0), 0);
  assert_eq!(multiply(0, 5), 0);
  assert_eq!(multiply(-3, -5), 15);
  assert_eq!(multiply(-3, 5), -15);
  assert_eq!(multiply(3, -5), -15);
}

This is all marvelous and it looks as if our simple multiplier is working as expected, but I want to leave you with a cliff hanger, based on a problem I ran into in another project, which also applies here:

  • Our return value is of the type i32 right?
  • The two operands are of the type i32 right?

This mean that the product of our calculation can exceed our return value - meaning basic parameters can render our multiplier useless.

The maximum value of an i32 can be extracted from Rust

fn main() {
    println!("{}", i32::max_value());
}

Do checkout the playground if you want to fool around with this brief example.

The maximum value for ì32 is 2147483647.

If we multiply 2147483647 with 2147483647 we get: 4.611686e+18 and the problem is of course relevant for the negative equivalents also (it there a term for this in mathematics?).

If you want to check it out, just add the following test to the test suite, since multiplying our maximum with 2 exceeds the maximum of our return type.

assert_eq!(multiply(2147483647, 2), 4294967294);

This is it for now, I will do more write ups, since they are a good way for me to get my notes and katas in order, and perhaps somebody else can benefit from my notes or get inspired to learning Rust or just solving katas on Codewars.com.

I known I am skipping over a lot of things, and I hope you can still make sense of what I am writing and enjoy it, but I aim to cover these things in other posts, where they might prove more relevant or can be the focal point of the post.

Have fun,

jonasbn

Discussion (5)

Collapse
jeikabu profile image
jeikabu

Not sure if this is in the spirit of code kata, but it was something I've meant to look into. To make it work for most types:

fn multiply<T: std::ops::Mul>(lhs: T, rhs: T) -> T::Output {
    lhs * rhs
}

fn main() {
    assert_eq!(multiply(2u32, 3u32), 6);
    assert_eq!(multiply(2u16, 3u16), 6);
    assert_eq!(multiply(std::u8::MAX, 1), std::u8::MAX);
}
Collapse
jonasbn profile image
jonasbn Author

Well it is not exactly fight club, so I guess we can talk about everything.

I was thinking about something along the lines of what you suggest and I am on the process of working on a write up of another example, based on another small prototype. I just can not find the time to get the post written.

Your example really got me thinking about the whole type-hype, coming from Perl it very different from what I am used to.

Thanks for the example and suggestion, it gave me some perspective and some Rust code to read and reflect on.

Collapse
bmitch profile image
Bill Mitchell

Nice article. I'm learning Rust as well so thanks for pointing out Codewars.com. Will try that.

Collapse
jonasbn profile image
jonasbn Author

Hi Bill,

Do that and let me know how it goes or compares to other resources. Sorry about the late response, pretty busy currently, even missed my scheduled blog post for this Sunday. Many topics, not enough time.

Take care,

jonasbn

Collapse
jonasbn profile image
jonasbn Author

Hi @bmitch

I have tried out Exercism.io as recommended on one of my posts.

I can really recommend it, I do it in parallel with Codewars.com, even though I have been spending more time on Exercism.io lately.

The assignments on Exercism.io are a tad more clean and the option of using mentors is awesome - do check it out