RPN Calculator in Rust

Reading lines from standard input is achieved through making use of the lines iterator. The outcome from lines is an Optional value: Some(string) for an actual string and None signifying end of input. The latter causes termination of the for loop and thus ending the program. Within the loop, s is unwrapped from the Some tag and an immutable, referable version of it is obtained (as_slice), so that the words iterator can be constructed which tokenizes the string, letting the body of the inner for loop receive the tokens in sequence, each one under the name tk.

Note that the string being read includes a possible terminating end-of-line character, but that makes no difference as words considers all whitespace to be delimiters.

If parsing a token (from_str) as a floating-point number (f64) is successful (the outcome x is a Some), the respective number is unwrapped and pushed onto the vector stk which serves as a stack of intermediate values. A new, empty stack is created before processing each RPN expression. If tk fails to read as a number, it has to be an arithmetic operator. The two variables y and x are created for that operator's arguments, their values being popped off from the stack if available. The latter is ensured in adavance, but pop produces Optional values anyway, and the respective numbers have to be obtained by unwrapping. Then, if tk is indeed an arithmetic operator, a calculation is performed and the result is stored onto the stack.

Upon termination of the inner for, it remains to recap whether the RPN expression is syntactically correct or not, and accordingly print either the result or the "error" string. At that point, erroneous tokens and missing arguments must have lead to err being true, and it is only needed to check whether the stack holds exactly one number. If there are more, the expression is still invalid. However, an empty stack and err==false is not an error: it means that an empty or blank line was entered, i.e. the do-nothing case.

As the stk and err variables necessarily update their values within their lifespans, they have to be explicitly declared mutable, which a Rust variable normally is not. All other variables – s, tk, x, y – are indeed immutable, but are being created anew with each use.

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.

use std::io;

fn main() {
  for s in io::stdin().lines() {
    let mut stk: Vec<f64> = vec!();
    let mut err = false;
    for tk in s.unwrap().as_slice().words() {
      let x: Option<f64> = from_str(tk);
      if x.is_some() {stk.push(x.unwrap());}
      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");}