PLACE
spec
solutions
Rust

RPN Calculator in Rust

The calculator reads lines from the standard input stream and processes each one in forward direction, using a vector stk of floating-point numbers as a stack of intermediate values. Buffered reading line by line is achieved by making use of a for loop with a suitable iterator: each time the loop is resumed, the iterator delivers the next line of text to be processed. Fragmenting an input line s into tokens is done in another for loop, where the split_whitespace() iterator yields the non-nlank slices of the string s.

A token tk may parse successfully as a floating-point number x, in which case the number is pushed onto the stack stk. Failing that, tk ought to be an arithmetic operator. Then two variables y and x are created for that operator's arguments, values for them are popped off from stk if available, and the result of the arithmetic operation is pushed back onto stk. Processing the RPN expression in the inner for loop is aborted with the err variable set to true if tk is neither a number or any of +, -, *, and /, or if stk lacks an operand for an operation.

When the inner for terminates with no err set and there is exactly one number stored in stk, that number is the result and is printed. If err is not set, the RPN expression is still invalid if there are more than one numbers in stk. And if stk is empty (and err is not set), then the input line itself was empty and accordingly the program prints nothing.

The names vec and println designate macro definitions, which is why a ! has to be appended to each. The curly braces in the argument to println show where a value must be interpolated in the formatting string, and in general may contain formatting specifications.

As the values of stk and err are necessarily updated within their lifespans, these variables have to be explicitly declared mutable. In contrast, the variables ins, s, tk, x, and y are immutable, but are being created anew with each use.

The only variable in the program for which the type has to be explicitly declared is stk: the type is Vec<f64>, which is a vector of 64-bit floating-point numbers. For all other variables stating the type explicitly is possible but not needed, as the compiler deduces it automatically. For example, in if let Ok(x) = tk.parse() the variable x is known to be of type f64, because x's value is stored in the vector stk, whose elements are known to be f64. Furthermore, parse() can, in principle, return results of several different types, but in this case it is inferred that the call is equivalent to the more detailed parse::<f64>(). (The ::<> notation for type specification is interestingly called ‘turbofish’.)

In Rust, all functions for which producing their expected values may fail under impeding circumstances, return a value that is tagged either Ok() or Err(). If it is the former, the result indeed contains the needed value, and it can be extracted by calling unwrap() on the result. Thus we have to use unwrap even when it is known for certain that the expected value is there, as is the case with popping the values for x and y from stk in the program.

Alternatively, the if let construct can be used to match the result against the Ok tag, as is done with the value returned by parse(). In this case, the argument to Ok gets the unwrapped value if there is one.

The two lines

  let ins = std::io::stdin();
  for s in ins.lock().lines() {

hide some complexity that needs a more detailed explanation.

Obtaining an iterator for buffered reading line by line takes several steps. First, calling std::io::stdin() produces a handle to the standard input stream. (std::io is a module, providing core I/O functionality.) From that handle lock() creates a version capable of buffered reading. Buffered reading is enabled through implementing the BufRead trait (which the locked handle does). A trait in Rust is a formally specified interface, and implementing BufRead ensures, in particular, the availability of the lines() method. Invoking lines() finally produces the needed iterator.

As the name lock suggests, the handle created by that method is locked, so that no other thread could possibly steal a piece of input intended for the calculator, and it is only natural that buffered reading is obtained through locking.

An observant reader will inevitably notice that the ins variable is introduced only to be used once, immediately following its definition, as the implicit argument in the call to lock(). It is tempting to remove the name ins, replacing the two discussed lines with a single one:

  for s in std::io::stdin().lock().lines() {

or perhaps to define a variable that holds the iterator rather than a handle to the input stream:

  let iter = std::io::stdin().lock().lines();
  for s in iter {

However, neither of these works. As it happens, the value produced by stdin() is being borrowed within lock(), and the scope of the latter is the entire outer for loop. Rust's rules require that the owner of a value must outlive the borrower, so the owner of the value of stdin() must be a variable defined outside the loop, and the borrower must receive the borrowed value from that variable. This is what the variable ins does in the program. The expression stdin().lock() is erroneous by construction, as the owner in it is stdin() itself and it would have disappeared as soon as its execution completes.

use std::io::prelude::*;

fn main() {
  let ins = std::io::stdin();
  for s in ins.lock().lines() {
    let mut stk: Vec<f64> = vec![];
    let mut err = false;
    for tk in s.unwrap().split_whitespace() {
      if let Ok(x) = tk.parse() {
        stk.push(x);
      } else {
        err = stk.len()<2;
        if err {break;}
        let (y,x) = (stk.pop().unwrap(),stk.pop().unwrap());
        match tk {
          "+" => stk.push(x+y),
          "-" => stk.push(x-y),
          "*" => stk.push(x*y),
          "/" => stk.push(x/y),
          _   => {err = true; break;}
        }
      }
    }
    if !err && stk.len()==1    {println!("{}",stk[0]);}
    else if err || stk.len()>1 {println!("error");}
  }
}

boykobbatgmaildotcom