I agree your example is easier than mine! 
Note that you move the cursor to the next line as you use println!
instead of print!
, which is why you don't need flush()
. Thus you could do:
- println!("Input First number ? ");
+ println!("Input First number ?");
Sure:
use std::io;
This allows us to write io::something
instead of std::io::something
. It makes our remaining code easier to write.
use std::io::Write as _;
This allows us to use the methods defined in std::io::Write
, namely write_fmt
(which is needed by write!
) and flush
. If we do not import the trait, we cannot use these methods. Importing it "as _
" means that we do not need to refer to std::io::Write
directly but that we only want to use the methods defined there (if we have a value of a type which implements the trait).
Note that I didn't come up with this on my own. I added it because I get a compiler error when I omit the line:
error[E0599]: no method named `write_fmt` found for struct `Stdout` in the current scope
--> src/main.rs:6:5
|
6 | write!(io::stdout(), "{question}: ")?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `Stdout`
|
= help: items from traits can only be used if the trait is in scope
= note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
1 | use std::io::Write;
|
error[E0599]: no method named `flush` found for struct `Stdout` in the current scope
--> src/main.rs:7:18
|
7 | io::stdout().flush()?;
| ^^^^^ method not found in `Stdout`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
1 | use std::io::Write;
|
For more information about this error, try `rustc --explain E0599`.
I chose to write use std::io::Write as _
instead of use std::io::Write
to keep my namespace a bit cleaner.
I decided to write a helper function for asking a question and waiting for the answer from the user. I named this function ask
:
fn ask(question: &str) -> Result<String, io::Error> {
For the question to be asked, we retrieve a shared reference to a string slice (because we only need a temporary reference here). That is why the type of question
is &str
.
The return type is a Result
, which can be either a success value or an error value. In angle brackets (<…>
), we add additional type arguments. This is to specify of which type is the contained success value (it will be a String
) and the container error value (it will be a std::io::Error
). I chose std::io::Error
as error type because those are the only errors we can encounter when reading and writing text from the console/terminal.
let mut result = String::new();
Here we create a new (modifiable) empty String
. We declare the variable as mutable (mut
) such that we can change that String
later. An alternative syntax would have been let mut result = "".to_string();
. We need the String
because the read_line
method, that we use later, doesn't return a string but will append the contents that have been read to an existing String
. That's why we need to create an empty (mutable) String
here.
write!(io::stdout(), "{question}: ")?;
This is almost the same as print!("{question}");
. Except that if there is an error we will return the error instead of panicking. I did this to be consistent with the remaining calls, which also return an error instead of panicking when something goes wrong.
std::io::stdout()
is a handle that represents the output part of the text window / console / terminal. Writing to this handle will print something on the screen.
However, the output may be buffered. That means it will not show up on the screen until the line is completed. When we use println!
, we automatically add a newline. But since I want the user input to happen in the same line (without moving to the next line), I have to manually flush the output (such that it will be shown now and not later when the line is complete). This is done with the following:
io::stdout().flush()?;
Also here, the question mark (?
) will cause to return the error if something goes wrong. It is a shorthand for:
match io::stdout().flush() {
Ok(ok) => ok,
Err(err) => return Err(err.into()),
}
Hope I got that right. If not, please anybody correct me. The .into()
means that the error type can be converted if the function returns a different error type.
io::stdin().read_line(&mut result)?;
This reads a line from std::io::stdin()
which represents any keyboard input (or file input if you call the program with redirected STDIN). The read_line
method doesn't return the read string but a Result<usize, std::io::Error>
, which contains the number of bytes read in case of success or an std::io::Error
in case of an I/O error. We propagate the I/O error using the question mark operator (?
) here, but we discard the result (we don't store or use the result in any way). The read string, however, is appended to the mutable String
whose mutable reference we pass by writing &mut result
.
Side note, click to expand
The read_line
method here comes from a struct std::io::Stdin
, which is returned by std::io::stdin()
. It is slightly different from the std::io::BufRead::read_line
method. Because std::io::Stdin::buf_read
is a method directly belonging to the Stdin
struct, we didn't need to write "use std::io::BufRead as _;
" earlier.
let result = result.trim().to_owned();
We use the method str::trim
to strip any leading or trailing whitespace (such as tabs, newline characters, or spaces). Note that our previous result
is of type String
, and our newly defined result
(we declare another variable with the same name) is also of type String
. So why can we use a method that belongs to str
instead of String
? This is because String
s are (arguably) smart-pointers to str
's, and Rust performs some automatic type coercion here.
More details
However, .trim()
returns a reference (&str
) which is only valid as long as our original value has been valid. It won't allocate a copy of the string. To fix that, we have to convert the &str
into a String
by using the .to_owned
method. Alternatively, I could have used .to_string
, which is more specific even, or I could have written:
let result = result.trim().into();
But using .into()
is less verbose here and might confuse the reader (i.e. what is converted into what?). Anyway it would work too!
When the last statement in a function is an expession without semicolon, then we return that value:
Ok(result)
However, we can't return the result
directly because it is of type String
. Our function returns a Result<String, std::io::Error>
. That's why I have to write Ok(…)
to wrap the String
inside its Ok
variant.
}
End of function! 
Now to our main function:
fn main() -> Result<(), Box<dyn std::error::Error>> {
I decided to make the main
function return a Result
. You may choose whether your main
function returns nothing (i.e. ()
) or a Result<(), E>
where E
is some error type.
Here I use Box<dyn std::error::Error>
as error type, which basically means we can return any type of error.
Details on dyn and Box
- The keyword
dyn
followed by a trait name allows us to return trait objects where the type is determined at runtime instead of compile-time. Here, this allows us to return any error type, i.e. any type which implements std::error::Error
. Note that std::error::Error
is different from std::io::Error
.
- As trait objects can differ in size (they are not
Sized
), we need to wrap them in a Box
to be able to return them.
Why do we want to return any error and not just std::io::Error
s? That's because .parse
returns another type of error (depending on the type that you want to parse), which is not an I/O error.
In short:
fn main() -> Result<(), Box<dyn std::error::Error>>
means that our main
function can return any sort of error.
let a: i32 = ask("a")?.parse()?;
let b: i32 = ask("b")?.parse()?;
Here we use our previously defined ask
helper function. We pass a &str
("a"
and "b"
) as argument. The first ?
indicates that we want to return any I/O error that we encounter while calling ask
. It also will convert our Result<String, std::io::Error>
into a String
. I.e. if ask
was successful, we get our String
, and if it was unsuccessful, we will return the error (converting it to a Box<dyn std::error::Error>
automatically). So the tiny ?
operator does a lot of helpful things for us!!
The String
is then converted into a number using str::parse
. Note that parse
is polymorphic, i.e. it can parse into any type (any type which implements FromStr
). Because we declared our variables a
and b
to be of type i32
, the Rust compiler knows which parsing we want: parsing to an integer (i32
).
Again, the ?
at the end will try to unwrap the successful result and, if there was an error, make main
return the error (or, strictly speaking, a converted version of the error).
println!("{a} + {b} = {}", a + b);
Here we print the variables a
and b
, which we can do with the placeholders {a}
and {b}
. That works because println!
is a macro (you can recognize that because of the !
character). Macros allow us to do things which the language otherwise wouldn't support (such as referring to a variable name from inside a string literal). The empty curly braces {}
indicate a placeholder for an expression that follows the formatting string (which is a + b
in this example). We cannot write a + b
directly in the curly braces as only variable names and formatting instructions are allowed there.
Ok(())
Now this is important! Remember that our main
function returns a Result<(), Box<dyn std::error::Error>>
. We cannot just end the function without returning a proper value of that type. The type ()
is an empty tuple, called the unit type (see also Wikipedia on unit type). It has only one value: ()
. So ()
can refer to the type or the value, depending on context.
Because main
returns a Result
, we cannot return ()
directly. We have to wrap it in an Ok(…)
, similar like we did when returning Ok(result)
in our ask
function. Except here we have no result,
but only ()
(i.e. nothing) to return. Thus we write: Ok(())
.
}
Done. 