Wasm Builders 🧱

Moksh Pathak
Moksh Pathak

Posted on

How I made a calculator in Rust and ran it in an Enarx Keep - Part 2

Before we begin, I highly recommend you to give a read to my previous article in this series.

Let me give you a short recap of what we achieved in the previous article.

  • How to make a new directory for your project using Cargo.
  • What is Cargo
  • What are dependencies in Rust and how to use one
  • What’s Cargo.toml
  • How to import modules
  • Mutable and Immutable variables in Rust
  • String in Rust.
  • How to flush output without using \n or println!
  • How to take input from user and store it in a variable

With that done, lets jump back to our code.

use std::io::{stdin, stdout, Write};

fn main(){
    let mut a = String::new();
    let mut b = String::new();
    let mut operator = String::new();
    print!("Enter number 1 : ");
    stdout().flush().expect("Couldn't flush statement 1");
    stdin().read_line(&mut a).expect("Couldn't read number 1");
    print!("Enter number 2 : ");
    stdout().flush().expect("Couldn't flush statement 2");
    stdin().read_line(&mut b).expect("Couldn't read number 2");
    print!("Enter the operation (+, -, *, /) : ");
    stdout().flush().expect("Couldn't flush statement 3");
    stdin().read_line(&mut operator).expect("Couldn't read operator");

    let a: f32 = a.trim().parse().unwrap();
    let b: f32 = b.trim().parse().unwrap();
    let operator: char = operator.trim().chars().next().unwrap();
}
Enter fullscreen mode Exit fullscreen mode

We changed the type of our variables a and b as float type, and the type which takes up 32 bits or 4 bytes in memory. Then we removed any white spaces using .trim(), parsed our String type variable to f32 type variable using .parse() and .unwrap() returns our value if there is one, or it will panic (stop the execution), if there isn’t.

Speaking of the variable operator, we changed its type to char, removed any whitespaces using .trim(), converted the String type to char type using .chars(), and advanced the iterator over the string using .next() until it returned None, which means we iterated over the string until each character of the string is char type with the help of .next().

Since we are making a simple calculator, we want to limit our operations to:

  • Addition
  • Subtraction
  • Multiplication
  • Division

Let’s make a separate String for that, which contains the operators for all the above mentioned four operations.

use std::io::{stdin, stdout, Write};

fn main(){
    let mut a = String::new();
    let mut b = String::new();
    let mut operator = String::new();
    print!("Enter number 1 : ");
    stdout().flush().expect("Couldn't flush statement 1");
    stdin().read_line(&mut a).expect("Couldn't read number 1");
    print!("Enter number 2 : ");
    stdout().flush().expect("Couldn't flush statement 2");
    stdin().read_line(&mut b).expect("Couldn't read number 2");
    print!("Enter the operation (+, -, *, /) : ");
    stdout().flush().expect("Couldn't flush statement 3");
    stdin().read_line(&mut operator).expect("Couldn't read operator");

    let a: f32 = a.trim().parse().unwrap();
    let b: f32 = b.trim().parse().unwrap();
    let operator: char = operator.trim().chars().next().unwrap();

    let operators = String::from("+-/*");
}
Enter fullscreen mode Exit fullscreen mode

Notice that we use String::from instead of String::new. It’s because we want to make operators of type String which contains the string "+-/*".

We want to make sure that the user inputs only these 4 operators, not any other. For that, we add :

use std::io::{stdin, stdout, Write};

fn main(){
    let mut a = String::new();
    let mut b = String::new();
    let mut operator = String::new();
    print!("Enter number 1 : ");
    stdout().flush().expect("Couldn't flush statement 1");
    stdin().read_line(&mut a).expect("Couldn't read number 1");
    print!("Enter number 2 : ");
    stdout().flush().expect("Couldn't flush statement 2");
    stdin().read_line(&mut b).expect("Couldn't read number 2");
    print!("Enter the operation (+, -, *, /) : ");
    stdout().flush().expect("Couldn't flush statement 3");
    stdin().read_line(&mut operator).expect("Couldn't read operator");

    let a: f32 = a.trim().parse().unwrap();
    let b: f32 = b.trim().parse().unwrap();
    let operator: char = operator.trim().chars().next().unwrap();

    let operators = String::from("+-/*");

    if !operators.contains(operator){
        panic!("Invalid operator");
    }
}
Enter fullscreen mode Exit fullscreen mode

This checks whether the string operators contains the character stored by the variable operator. If it doesn’t, the execution stops, and the message “Invalid operator” is displayed.

Now let’s move on to the “operating on the numbers” part.

use std::io::{stdin, stdout, Write};

fn main(){
    let mut a = String::new();
    let mut b = String::new();
    let mut operator = String::new();
    print!("Enter number 1 : ");
    stdout().flush().expect("Couldn't flush statement 1");
    stdin().read_line(&mut a).expect("Couldn't read number 1");
    print!("Enter number 2 : ");
    stdout().flush().expect("Couldn't flush statement 2");
    stdin().read_line(&mut b).expect("Couldn't read number 2");
    print!("Enter the operation (+, -, *, /) : ");
    stdout().flush().expect("Couldn't flush statement 3");
    stdin().read_line(&mut operator).expect("Couldn't read operator");

    let a: f32 = a.trim().parse().unwrap();
    let b: f32 = b.trim().parse().unwrap();
    let operator: char = operator.trim().chars().next().unwrap();

    let operators = String::from("+-/*");

    if !operators.contains(operator){
        panic!("Invalid operator");
    }

    let mut result: f32 = 1.0;

    if operator =='+'{
            result = a + b
        }

    else if operator == '-' {
            result = a - b
        }

    else if operator == '*' {
            result = a * b
        }

    else if operator == '/'{
            result = a / b
        }
}
Enter fullscreen mode Exit fullscreen mode

We need a variable to store the result of our operation on the numbers, hence the variable result.

We also need to initialize the variable, otherwise while printing the value of result, we would be facing an error from compiler.

If you are reading this article, chances are that you know what if ,else and else if are.

We compare our variable operator with different operator signs, and if the condition evaluates to true, the if or else if block is executed.

Notice that we are not using semicolons when inside the if or else if block. That’s because we want to return the value of expression to our variable result.

Question

Let me explain. Rust is primarily an expression language. An expression can be a line or piece of code which returns a value.

Then there are statements, which is a piece or line of code that executes and tells the computer to do something specific.

Examples for statement are :

use std::io::{stdin, stdout, Write};
Enter fullscreen mode Exit fullscreen mode

and

print!("Enter number : ");
Enter fullscreen mode Exit fullscreen mode

An expression which evaluates to a value would look like :

if operator =='+'{
    result = a + b
}
Enter fullscreen mode Exit fullscreen mode

Here, the value of a + b is returned to the variable result.

Let’s consider an example to understand this concept much better.

let mut a = {
    let b = 10;
    b*b
};
Enter fullscreen mode Exit fullscreen mode

After executing the above code, the value of variable a would be 100.

But if we execute the code below :

let mut a = {
    let b = 10;
    b*b;
};
Enter fullscreen mode Exit fullscreen mode

Then printing a would give us (), as it contains no value.


wait a minute

An important line is still missing from our code. We need to print the result of our calculation.

use std::io::{stdin, stdout, Write};

fn main(){
    let mut a = String::new();
    let mut b = String::new();
    let mut operator = String::new();
    print!("Enter number 1 : ");
    stdout().flush().expect("Couldn't flush statement 1");
    stdin().read_line(&mut a).expect("Couldn't read number 1");
    print!("Enter number 2 : ");
    stdout().flush().expect("Couldn't flush statement 2");
    stdin().read_line(&mut b).expect("Couldn't read number 2");
    print!("Enter the operation (+, -, *, /) : ");
    stdout().flush().expect("Couldn't flush statement 3");
    stdin().read_line(&mut operator).expect("Couldn't read operator");

    let a: f32 = a.trim().parse().unwrap();
    let b: f32 = b.trim().parse().unwrap();
    let operator: char = operator.trim().chars().next().unwrap();

    let operators = String::from("+-/*");

    if !operators.contains(operator){
        panic!("Invalid operator");
    }

    let mut result: f32 = 1.0;

    if operator =='+'{
            result = a + b
        }

    else if operator == '-' {
            result = a - b
        }

    else if operator == '*' {
            result = a * b
        }

    else if operator == '/'{
            result = a / b
        }
    println!("Result of {} {} {} is : {}", a, operator, b, result)
}
Enter fullscreen mode Exit fullscreen mode

There. Now our code is complete. The curly braces would take up the value of the variables mentioned after the string. It is important to keep in mind that we need to keep the order of variables correct. println! prints to the standard output, but with a newline.

We are now done with the coding part!

yay

Now, we need to run our code.

For that, we need to run the following command :

cargo run
Enter fullscreen mode Exit fullscreen mode

This command builds and runs our project. It creates the executable file target/debug/rust-calc or target/debug/rust-calc.exe on Windows.

To run our project in an Enarx keep, we need to create a .wasm file of our project. But first, make sure that you installed the WebAssembly Rust toolchain. The command to install the same is :

rustup target install wasm32-wasi
Enter fullscreen mode Exit fullscreen mode

To generate the .wasm file of our project, run the following command.

cargo build --release --target=wasm32-wasi
Enter fullscreen mode Exit fullscreen mode

But why are we generating a .wasm file for our Rust code?

To run it in an Enarx keep. Enarx provides a WebAssembly runtime, based on wasmtime.

We can find our .wasm file in target/wasm32-wasi/release/rust-calc.wasm.

Finally, we can run our project in an Enarx keep with this simple command.

enarx run target/wasm32-wasi/release/rust-calc.wasm
Enter fullscreen mode Exit fullscreen mode

To recap briefly, you now know :

  • How to change the type of a variable
  • What does .trim(), .parse(), .next(), .unwrap() do
  • How to use panic!
  • How to use .contains() on a string
  • How to use if, else if and else in Rust.
  • Difference between an expression and a statement
  • When to use and not to use semicolon in Rust.
  • How to use println! in Rust.
  • How to build and run a project using cargo.
  • How to generate a .wasm of our project.
  • How to run a .wasm file in an Enarx keep.

Congratulations 🥳 !

You just went from a complete newbie in Rust to making a calculator in Rust and running it in an Enarx keep!

This is just the beginning. I highly suggest you to read “The Rust programming Language” book, which can found here, to get familiar and learn more concepts of Rust.

With this, I come to the end of this series. I hope you enjoyed this as much as I did.

Never stop learning and exploring 😃

I will see you in the next one.

Top comments (8)

Collapse
 
wkoch profile image
William Koch

You're on the right path, keep exploring stuff and publishing it. Don't get discouraged by trolls like timq.

When learning new languages, try to adapt to the way things are done on that language, the Rust Way, in this case.

Making posts like this and explaining things you learned are very helpful to you, they give better clarity for what you learned and will expose the things you thought you knew but need better understanding, that is basically the feynman learning technique.

Collapse
 
moksh_pathak profile image
Moksh Pathak Author

Thank you for your kind and encouraging words William!

Collapse
 
timleg002 profile image
timq

This is by far one of the worst introductions to the Rust language I've ever seen.

Collapse
 
moksh_pathak profile image
Moksh Pathak Author

Thanks for your feedback. May I request you to please let me have your views/gripes, which you think are not accurate and are far from factual.
This will help me better understand your perspective and hopefully resolve and improve further :)

Collapse
 
timleg002 profile image
timq
  1. No match statement, when it's fit PERFECTLY for this case (this is taught to pre-college students!)
  2. Useless repeated statements - just put them in a function, trim parse all that shit.
  3. Invalid operator - just have an else clause or a default match arm? Absolutely useless. Just THINK.
  4. Why the fuck do you need flushing everything? You don't flush each time the shit falls in the shitter.
  5. stdin with Rust is kinda wonky, but doing it in a loop and not this weird initialize everything
  6. Unwrapping vs expecting - unwrap flush, don't expect it, it's not a user error. Also stop flushing everything!
Thread Thread
 
moksh_pathak profile image
Moksh Pathak Author

Thanks again for your constructive and positive feedback for a student/intern.
This article is for those, who do not know anything about Rust and have no experience, and the big part of the target audience is students.

We learn from experiences and in the process grow to achieve a higher credibility.
Thanks again and best wishes

Thread Thread
 
timleg002 profile image
timq

This literally ruins the Rust experience for many. They'll only know shit about Rust from now on.

Thread Thread
 
wkoch profile image
William Koch • Edited on

timq, you're probably not as good a programmer as you think you are if you need to be this rude to people just to feel better about yourself.

Moksh is still studying, learning a new language and is already publishing stuff, this is just a learning project, it doesn't have to be perfect, it's just one of many projects he'll do while learning Rust. You act like you were never at this stage, like you never did bad code.

You tried to look good here, but just made a fool of yourself:

When you said "This is by far one of the worst introductions to the Rust language I've ever seen.": Moksh wasn't introducing Rust to readers, as the tittle makes pretty clear: "How I made a calculator..." he was just showing his project and explaining what he learned building it.

Then you made a few, simple and badly explained, suggestions about things that Moksh will probably learn by himself in his next projects. But you didn't miss the opportunity to be rude again.

And then: "This literally ruins the Rust experience for many. They'll only know shit about Rust from now on." Seriously? Like anyone learning Rust would use just these posts for studying the language.

I think comments like yours timq are ruining the Rust/Whatever experience for everyone. Be helpful or just shut up.