Question about Programs in Rust

Hi folks ,
Recently I was experimenting and writing programs in Rust Programming Language
I wanted to ask what are the things that I can do with use ::io
#2 How to use this io package and I wanted to write a
program which adds two numbers and displays the result
How can I do this ??

Aman
Your help is highly appreciated :pray:

There are lots of ways, depending on the details of what you need to do. One option is something like this:

use std::io;

const IN: &[u8] = b"10\n4\n";

fn main() {
    dbg!(sum_lines(IN));
}

fn sum_lines<I: io::BufRead>(inp: I)->u32 {
    let mut sum: u32 = 0;
    for l in inp.lines() {
        sum += l.unwrap().trim().parse::<u32>().unwrap();
    }
    sum
}

(Usually, you'd pass something like a File object or io::stdin()::lock() to sum_lines instead of a constant buffer, but I don't know how to make those work on the playground)

You can use files on the Playground by creating them yourself, like this.

4 Likes

@2e71828 Thanks for your reply but I want the cursor should flash on the screen instead of just displaying output
#2 Could you pls add the explaination as well

I was able to come up with something like this:

use std::io;
use std::io::Write as _;

fn main() {
    let stdin = io::stdin();
    let mut stdout = io::stdout();
    let mut input = String::new();
    print!("a = ");
    stdout.flush().expect("I/O error");
    stdin.read_line(&mut input).expect("I/O error");
    let a: i32 = input.trim().parse().expect("could not parse number");
    input.clear();
    print!("b = ");
    stdout.flush().expect("I/O error");
    stdin.read_line(&mut input).expect("I/O error");
    let b: i32 = input.trim().parse().expect("could not parse number");
    println!("{a} + {b} = {}", a + b);
}

But admittingly, that's a bit complex for such a simple task :sweat_smile:. Perhaps anyone else can provide some shorter (idiomatic?) example which does the same?

P.S.: I didn't use Rust Playground because it doesn't seem to allow input from stdin.

Godbolt supports that.

How about:

use std::io;
use std::io::Write as _;

fn ask(question: &str) -> Result<String, io::Error> {
    let mut result = String::new();
    write!(io::stdout(), "{question}: ")?;
    io::stdout().flush()?;
    io::stdin().read_line(&mut result)?;
    let result = result.trim().to_owned();
    Ok(result)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let a: i32 = ask("a")?.parse()?;
    let b: i32 = ask("b")?.parse()?;
    println!("{a} + {b} = {}", a + b);
    Ok(())
}

P.S.: If you have any questions on how a particular line of code in the example works, feel free to ask.

1 Like

Mmmm, nice to know! But no flashing cursor :sob:.

Plus, the output is a bit weird in this case:

Program returned: 0
Program stdout

a = b = 2 + 2 = 4

It's not really what I see on my screen:

% cargo run
   Compiling add v0.1.0 (/usr/home/jbe/rust-experiment/add)
    Finished dev [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/add`
a = 2
b = 2
2 + 2 = 4

Btw., is there a nicer way in Rust to do "interactive input from the terminal" which doesn't involve io::stdout().flush()?

Use a properly terminal-aware crate, pretty much.

Do you know a good one?

Seriously, I find my Rust example of reading two numbers and adding them quite long. When showing Rust to programming newbies, I didn't even attempt to read from stdin, as it felt overly complex:

  • define an empty String buffer
  • write to stdout
  • flush stdout
  • read into the buffer
  • trim the buffer
  • clear the buffer if you want to reuse it

Compare with Python 3:

a = int(input("a = "))
b = int(input("b = "))
print(f'{a} + {b} = {a+b}')

I'm sure this is an "unfair" comparison :innocent:, but can anyone come up with a cleaner / shorter / more idiomatic example in Rust? Maybe using a commonly used crate or something like that? I know there is, for example, clap for handling command line arguments. Perhaps there is something similar for simple (or more complex?) terminal interaction, like @quinedot suggested. Maybe crossterm? But does it making reading a line really easier?

Disregarding Rust in particular, I know that a lot of command line programs use GNU readline, which is GPL though, so it might not be a good option for every project.

That's because it is. :sweat_smile: Reading from stdin for user input is not common for CLI apps. You do sometimes see it (ssh-keygen and adduser come to mind) but it is far more common to use arguments when a user is expected to run a CLI app.

stdin is more commonly used as part of pipelines, where you don't need to flush stdout at all (and doing so often would negatively affect performance). Perhaps the obvious app where pipelining is used would be ripgrep? Let's see how it handles stdin:

ripgrep/search.rs at master · BurntSushi/ripgrep (github.com)

Nice, just a one-liner making use of io::Read. ripgrep can also acquire patterns from stdin: ripgrep/pattern.rs at master · BurntSushi/ripgrep (github.com)

Once again io:Read does the heavy lifting.

What about another app? I saw jql for the first time not too long ago. Here's how it handles stdin: jql/bin.rs at main · yamafaktory/jql (github.com)

Apart from the special case streaming loop, this also boils down to basically a one-liner:

stdin.read_to_end(&mut buffer).await?;

Oh, look! It's io::Read once again.

I guess the only thing I'm showing is that "reading from stdin" is actually pretty straight forward if you're not doing anything special (like the line-based streaming that jql does). You just end up passing it on to a function that is generic over the Read trait. That function gets reused for handling files, sockets, and in-memory buffers. (Or, again using jql as an example, you call the Read methods directly to populate a buffer that you operate on.)

I'm sure there are also usability and UX arguments against using stdin for requesting user input. As for doing it anyway, there is rustyline! It's based on readline, which is common for creating REPLs. (Oh, yeah, I guess that's actually a more obvious example of using stdin for human inputs. Programming language REPLs, telnet, parted, openssl, heck even terminal multiplexers and shells. But these all seem very different from the interactive modes of ssh-keygen and useradd where it's a question-answer or request-response kind of workflow.)

4 Likes

Very nice, I might use that for some command line tools in the future!

Let me provide an updated example using rustyline. First, we need a dependency in the Cargo.toml file:

[dependencies]
rustyline = "9.1.2"

Then we can do:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut rl = rustyline::Editor::<()>::new();
    let a: i32 = rl.readline("a = ")?.parse()?;
    let b: i32 = rl.readline("b = ")?.parse()?;
    let c = a + b;
    println!("{a} + {b} = {c}");
    Ok(())
}

Looks much cleaner :smiley:, plus it allows certain shortcuts such as CTRL+A to jump to the beginning of the line, etc.

Downside is a moderately sized tree of dependencies, but I think for interactive command line apps, this might be worth it.

3 Likes

When reading numbers, it might be desirable to add .trim() also in case of using rustyline, even if it's not strictly necessary (because rustyline::Editor::readline will not include any newline character, while std::io::Stdin::read_line does include a newline character if it exists).

-    let a: i32 = rl.readline("a = ")?.parse()?;
+    let a: i32 = rl.readline("a = ")?.trim().parse()?;

-    let b: i32 = rl.readline("b = ")?.parse()?;
+    let b: i32 = rl.readline("b = ")?.trim().parse()?;

@community
Means a lot by the way I'll also try to come up with explanation and how to do in it in a simple way :')

use std::io;

fn main() {
          // User will enter first number
 
          println!("Input First number ? ");
 
          let mut var1 = String::new();
 
          io::stdin().read_line(&mut var1).expect("Unable to read entered data");
 
          // User will enter Second number
 
          println!("Input second number ? ");
          let mut var2 = String::new();
          io::stdin().read_line(&mut var2).expect("Unable to read entered data");
 


        
          
          // Converting string to integer
          let a: i32 = var1.trim().parse().ok().expect("Program only processes numbers, Enter number");
          let b: i32 = var2.trim().parse().ok().expect("Program only processes numbers, Enter number");
        
          // Output of basic operations
          println!("Sum is : {}", a + b);
 
}

@jbe
I came up with this :slight_smile:
More simple and easy to understand for a new Rustacean

@jbe Can you explain this entire program I want to know in depth what's going on

I agree your example is easier than mine! :sweat_smile:

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 Strings 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! :smile_cat:


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::Errors? 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. :sweat_smile:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.