In the last post we created a simple program that would accept an input like 5+5
and calculates the result.
In this post we will improve this program.
To prepare for adding more operators we need to improve the input-parsing.
Currently, we just split the input-string at the plus-sign.
If we want to know the operator, we have to choose another approach.
In this case I chose regex:
#[derive(Debug)]
struct Operation {
left: i32,
operator: char,
right: i32,
}
impl Operation {
fn from_string(input: &String) -> Option<Operation> {
let regex = Regex::new(r"^\s*(\d+)\s*([+])\s*(\d+)\s*$").unwrap();
regex.captures(&input).map(|capture| Operation {
left: (&capture[1]).parse().unwrap(),
operator: (&capture[2]).parse().unwrap(),
right: (&capture[3]).parse().unwrap(),
})
}
}
As you can see we have a new thing called struct
in the code.
If you are coming from a language like Java or Typescript, a struct can be seen as a class without any methods.
This struct is called Operation
and as the name suggests it holds an operation.
The fields left
and right
are both typed as an i32
or in other words a "signed 32-bit integer".
The field operator
is a char
since we only use one character for an operator.
In rust strings
and chars
are utf-8, therefore a char
can contain one unicode codepoint, not just a byte.
The other new thing is a impl
called Operation
.
An impl
is the method-part equivalent of a class.
Here you can define methods, that are available to call on the struct with the same name.
In this case we have one method called from_string
.
This method is our new parser.
It takes a string as an input and returns an Operation
wrapped by an Option
.
Rust does not have null
, so every time when there is the possibility that a value could be non-existent an Option
is used.
If you read the code carefully, you may have noticed that the last statement of the function does not have a semicolon.
That is on purpose.
If the last statement of a function has a semicolon, it returns ()
(equivalent to void
), if it isn't preceded by the keyword return
.
You can omit the keyword return
by omitting the semicolon on the last statement of a function.
Then, the value of the last statement is used as the return value.
Now we have a new method of parsing, so lets look at the new evaluator:
let operation = Operation::from_string(&input).expect("Your input is invalid");
let result = match operation {
Operation { left, operator: '+', right } => Ok(left + right),
x => Err(format!("{} is not a valid operator", x.operator)),
};
println!("The result is: {:?}", result.unwrap());
Here we can see another example of a powerful feature of Rust: Pattern matching.
With pattern matching, we can save a lot of else-if-statements.
In this simple case it wouldn't make a big difference, but we want to expand the code.
So what happens inside the match operation {...
statement?
Each line contains a Pattern, followed by =>
and a statement or block.
The pattern Operation { left, operator: '+', right }
matches every Operation
with +
as its operator.
left
and right
have no value assigned in the pattern, so all values match.
You can use left
and right
as variables in your following statement, as seen in the code.
x => ...
matches all cases, it's therefore the default
case.
After these changes, we are at state: 24affc71
In the next post we will add more operators to the calculator and more.
Top comments (0)