In python taking input from the user is as simple as using the built-in input
function, assigning it to a variable and using it as we need. Below is a simple program which takes in a name from a user and prints out a greeting
name = input("Please enter your name: ")
print(f"Hello {name}")
# Output:
# Enter your name: John Smith
# Hello John Smith
In rust, and in many low level languages, to capture a user input we first have to read from stdin
and store it in a buffer which can then be used as needed. Let's see how this looks all put together
A small note on stdin, stdout, stderr
In a nutshell
stdin
-> Standard Input, your process reads information from youstdout
-> Standard Output, your process writes normal outputstderr
-> Standard Output, your process writes debug output for more detailed information read the wikipedia page on this topic StandardStreams
Let's get back to our rust program
fn main() {
// Prints to the standard output, with a newline.
println!("Please enter your name: ");
// create an empty buffer before hand
let mut name_buf = String::new();
// create a `Stdin` object
let stdin_handle = std::io::stdin();
// read a line from stdin and store it in buffer `name_buf`
stdin_handle.read_line(&mut name_buf).unwrap();
println!("hello {}", name_buf);
}
// Output:
// Enter your name:
// John Smith
// hello John Smith
NOTE: I am going to use unwrap instead of handling errors to have less things to worry about
If we look closely there is a small difference in the two programs. In python the prompt and name input both happen on same line but in our rust program the input happens on a new line
RUST
Enter your name:
John Smith <- input on a new line
hello John Smith
PYTHON
Enter your name: John Smith <- input the same line
Hello John Smith
What if we want the similar behaviour in rust? We some how need to avoid printing a new line after the prompt. For this we can use print!
macro for this. Reading the print!
docs ...
Prints to the standard output.
Equivalent to the println! macro except that a newline is not printed at the end of the message.
Let's try this out
fn main() {
// Prints to the standard output, without a newline.
print!("Greetings, please enter your name: ");
let mut name_buf = String::new();
let stdin_handle = std::io::stdin();
stdin_handle.read_line(&mut name_buf).unwrap();
println!("hello {}", name_buf);
}
// Output:
// John Smith
// Enter your name: hello John Smith
This output was not what we expected. But before understanding what's going on let's try something that fixes this and work our way backwards. Let's try std::io::stdout().flush().unwrap()
from one of the examples in the docs
// flush method comes from `Write` trait. We need this in scope
use std::io::Write;
fn main() {
print!("Enter your name: ");
// immediately "push"(flush) buffer to stdout
std::io::stdout().flush().unwrap();
let mut buf = String::new();
std::io::stdin().read_line(&mut buf).unwrap();
println!("hello {buf}");
}
// Output:
// Enter your name: John Smith
// hello John Smith
Yay, we go the output we wanted. The reason for earlier "bad" output was that stdout
is usually line buffered i.e once there is a new line character in the buffer, the buffer will then immediately be flushed/printed to stdout
. Taking output from first print!
program above analyzing
-----PROGRAM STARTS-----
John Smith
^--- input prompt in buffer is not flushed until we type name and hit enter(newline) key
Enter your name: hello John Smith
^--- prompt and ^--- greeting are flushed to stdout together causing "bad" output
-----PROGRAM ENDS-----
To avoid such issues we can use std::io::stdio().flush()
as needed to push data to stdout immediately.
One more difference between print!
& println!
Another difference between print!
and println!
is that the print!
doesn't accept empty args but you can call println!
without any args
// Program 1
fn main() {
println!();
println!("Hello World");
}
// Output:
// <- empty line
// Hello World
// Program 2
fn main() {
print!(); // <- this will fail to compile
print!("hello World");
}
// Output:
// error: requires at least a format string argument
To find out we can look at source of print!
& println!
macros
macro_rules! print {
($($arg:tt)*) => {{
$crate::io::_print($crate::format_args!($($arg)*));
}};
}
macro_rules! println {
() => {
$crate::print!("\n")
};
($($arg:tt)*) => {{
$crate::io::_print($crate::format_args_nl!($($arg)*));
}};
}
Looking at the source we find that if there are no args passed to println!
we simply print a new line using, print!
macro. But such is not the case for print!
macro, we have to pass in some arguments or it won't compile.
Hopefully you understand a couple of differences between print!
and println!
. Now diving a little deeper
Diving deeper
If stdout
is line buffered then it is a good guess that there must be a size limit to that buffer. Let's try to find its true and try to find the limit
Note: I am doing this on a 64 bit windows machine with 1.72 rustc. Findings may be different on different machines. This may not be the right way, I am just having fun tinkering and sharing what I found
fn main() {
let array = [1; 500];
print!("{array:?}");
let mut buf = String::new();
std::io::stdin().read_line(&mut buf).unwrap();
println!("hello {buf}");
}
// Output:
// [1, 1, 1, 1, 1 ..(repeated around 336 times) JohnSmith
// ^ input
// ...(remaining) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]hello JohnSmith
// ^ output
When we try to print out an array of 1s of length 500, we see some of them are printed before taking input and some after. We can then make a guess, the first portion of 1s might have been written out as it exceeded the buffer limit and remaining output stays in buffer until flushed later. We can try to test this hypothesis by increasing size of array from 500 -> 700. You can try this for yourself, you should see that the first output portion of array is doubled.
// Output:
// [1, 1, 1, 1, 1 ..(repeated around 660 times) JohnSmith
// ^ input
// ...(remaining) 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]hello JohnSmith
// ^ output
Getting a little side tracked on formatter
There was a small thing that was irking me in the above example. Why only 341 1's
are printed. It must be that the debug format adding brackets, spaces and commas adding to buffer size. Okay, now on to finding the size of the debug printed array
#![feature(fmt_internals)]
use std::fmt::{Formatter, Debug};
fn main() {
let arr_341 = [1; 341];
let mut fmt_buf = String::new();
let mut fmtr = Formatter::new(&mut fmt_buf);
arr_341.fmt(&mut fmtr).unwrap();
println!("BUF SIZE: {}", std::mem::size_of_val(&*fmt_buf));
}
// Output:
// BUF SIZE: 1023
There is a little more going on here than some of the earlier examples. Going from top to down
-
#![feature(fmt_internals)]
-> this is a flag that allows the use of constructingFormatter
object asfmt_internals
is a unstable feature and shouldn't be used in normal code. Also this requires rust nightly -
fmt_buf
-> is an owned string -
Formatter::new()
-> constructs a new formatter object with the passed in buffer -
std::mem::size_of_val()
-> gives us the size of an object through shared reference -
&*fmt_buf
-> we have to dereference first because&fmt_buf
is a&String
fat pointer with size 24 bytes(address, length, capacity) and we want the size of what the pointer is pointing to and not the size of pointer itself.*fmt_buf
is astr
object and we can pass a refernce(&*fmt_buf
) to this object tosize_of_val
function to get the correct size
Now if I increase the length of array from 341 to 342, first the size of the debug format buffer becomes 1026
and intitally 1024
are printed out and then the remaining 2
bytes. Try the below program to verify it yourself
#![feature(fmt_internals)]
use std::{fmt::{Formatter, Debug}, time::Duration};
fn main() {
let arr_342 = [1; 342];
let mut fmt_buf = String::new();
let mut fmtr = Formatter::new(&mut fmt_buf);
arr_342.fmt(&mut fmtr).unwrap();
print!("{arr_342:?}");
std::thread::sleep(Duration::from_millis(5000));
println!();
println!("BUF {}", std::mem::size_of_val(&*fmt_buf));
}
// Output:
// [1, 1, 1, ...(repeated until 1024 bytes) |sleep 5sec| 1]
// ^^The last 2 bytes are printed after the 5sec sleep.
Sucker punch on our expectation
The theory so far is that the usual buffer size while writing to stdout
is 1024 bytes and anything less will not be flushed immediately. Testing on an array supports this theory. Now on to try on different data structures. Let's try a String
with the size of 1000 bytes or something less than our guessed size limit for output buffer
use std::{thread::sleep, time::Duration};
fn main() {
let aa_string = "a".repeat(1000);
print!("{aa_string}");
sleep(Duration::from_millis(3000));
println!();
println!("STR SIZE: {}", std::mem::size_of_val(&*aa_string));
}
// Output:
// |Sleep for 3sec|aa... (repeated 1000 times)
// STR SIZE: 1000
As expected, the program sleeps first as the buffer is not exceeding the limit. Now let's try it with something that exceeds the 1024 bytes
// same as prev fuc except this line
let aa_string = "a".repeat(1224);
//
// Output:
// aa... (repeated 1225 times)|Sleep for 3sec|
// STR SIZE: 1225
But this is not following our array example. According to our theory we should get the first 1024 bytes, sleep for 3 seconds, get remaining 200 bytes, then STR SIZE output. This example shows there is something more than just fixed buffer size for outputting stuff to stdout
.
Unfortunately, this is as far as I got for now. I will dig into this topic more and will write another blog if I find something. In the mean time if any of you people reading this have more information kindly point me to those resources. I will be very grateful.
Whew, that's a long story on print!
& println!
. I had fun learning and experimenting about these things and hopefully you found some of it interesting. Thanks for reading and until next one
Top comments (0)