Bytes written to a BufWriter aren't actually written - rust

I'm trying to write a small Rust program that echoes lines on /dev/ttyS0 back to the sender:
fn echo_loop(device: &str) {
let f = File::open(device).unwrap();
let read = BufReader::new(&f);
let mut writer = BufWriter::new(&f);
read.lines().for_each(|l: Result<String, Error>| match l {
Ok(line) => {
let _ = writer.write(line.as_bytes()).unwrap();
let _ = writer.flush();
println!("We should have written: {}", line);
},
Err(err) => println!("Err!: {:?}", err)
});
}
To test this code, I call the function with "/dev/tty" as its argument.
I would expect to be able to type lines on the console that get echoed after I press return, but I don't get an echo (beside from what I print with the println!(…) macro.
The part that reads the lines seems to work, else I couldn't println! what I type, but why don't I get the echo by writing to the writer?
This is the output of my code (note: The lines “Hello world!” and “That's what I get :-(” is the text I typed, not an output by the program):
Hello world!
We should have written: Hello world!
That's what I get :-(
We should have written: That's what I get :-(

As the docs state, File::open opens the file in read only mode.
When you try to write, you are writing to the BufWriter, which always succeeds. Once you flush, you are ignoring the Result, which means that even if the writing fails (which it does, since it's read-only), you are ignoring that error.
OpenOptions allows you to open a file both in read and write mode at the same time. Note that reading and writing at the same time can have weird consequences, like reading failing to produce anything after the first write, because the write replaces the entire file instead of inserting/appending your writes

Related

The print!() macro is not working in a loop {} block with other code in Rust [duplicate]

I am studying Rust and upon working on the Guessing Game I found this odd behaviour:
use std::io;
fn main() {
println!("Welcome!");
let mut input = String::new();
print!("Please type something:"); // this line is not printed UNTIL the Enter key is pressed
io::stdin()
.read_line(&mut input)
.expect("Failed to read input!");
println!("Bye!");
}
The following happens:
Welcome! is printed
Please type something: is NOT printed
If you type some text and press Enter, you will see your text followed by Please type something:Bye!
How can I print a message to the standard output and have the input being printed on the same line?
For instance:
Please enter your name:
(user types Chuck Norris)
Please enter your name: Chuck Norris
From the docs for std::print:
Note that stdout is frequently line-buffered by default so it may be necessary to use io::stdout().flush() to ensure the output is emitted immediately.
So looks like you need to call io::stdout().flush().

Why does `format_args!()` ignore truncation? How to truncate without allocation then?

Why does this work?
fn main() {
println!("{:.3}", "this is just a test");
}
prints => thi
While this doesn't?
fn main() {
println!("{:.3}", format_args!("this is just a test"));
}
prints => this is just a test
Here's a playground.
For a little more context, I’m interested in the reasoning behind it, and a way to do it without any allocations.
I'm developing a terminal game in Rust, where I have a write! which shows some statistics about the rendering and game loop, and that text can be quite long. Now that I read the terminal size and adjust its output accordingly, I need to truncate that output, but without any allocations.
I thought I was super clever when I refactored this:
write!(
stdout,
"{} ({} {} {}) {}",
...
)
into this:
write!(
stdout,
"{:.10}", // simulate only 10 cols in terminal.
format_args!(
"{} ({} {} {}) {}",
...
)
)
How unfortunate, it doesn’t work… How to do that without allocating a String?
For one thing, not every type obeys all formatting arguments:
println!("{:.3}", 1024);
1024
Second, format_args! serves as the backbone for all of the std::fmt utilities. From the docs on format_args:
This macro functions by taking a formatting string literal containing {} for each additional argument passed. format_args! prepares the additional parameters to ensure the output can be interpreted as a string and canonicalizes the arguments into a single type. Any value that implements the Display trait can be passed to format_args!, as can any Debug implementation be passed to a {:?} within the formatting string.
This macro produces a value of type fmt::Arguments. This value can be passed to the macros within std::fmt for performing useful redirection. All other formatting macros (format!, write!, println!, etc) are proxied through this one. format_args!, unlike its derived macros, avoids heap allocations.
You can use the fmt::Arguments value that format_args! returns in Debug and Display contexts as seen below. The example also shows that Debug and Display format to the same thing: the interpolated format string in format_args!.
let debug = format!("{:?}", format_args!("{} foo {:?}", 1, 2));
let display = format!("{}", format_args!("{} foo {:?}", 1, 2));
assert_eq!("1 foo 2", display);
assert_eq!(display, debug);
Looking at the source for impl Display for Arguments, it just ignores any formatting parameters. I couldn't find this explicitly documented anywhere, but I can think of a couple reasons for this:
The arguments are already considered formatted. If you really want to format a formatted string, use format! instead.
Since its used internally for multiple purposes, its probably better to keep this part simple; its already doing the format heavy-lifting. Attempting to make the thing responsible for formatting arguments itself accept formatting parameters sounds needlessly complicated.
I'd really like to truncate some output without allocating any Strings, would you know how to do it?
You can write to a fixed-size buffer:
use std::io::{Write, ErrorKind, Result};
use std::fmt::Arguments;
fn print_limited(args: Arguments<'_>) -> Result<()> {
const BUF_SIZE: usize = 3;
let mut buf = [0u8; BUF_SIZE];
let mut buf_writer = &mut buf[..];
let written = match buf_writer.write_fmt(args) {
// successfully wrote into the buffer, determine amount written
Ok(_) => BUF_SIZE - buf_writer.len(),
// a "failed to write whole buffer" error occurred meaning there was
// more to write than there was space for, return entire size.
Err(error) if error.kind() == ErrorKind::WriteZero => BUF_SIZE,
// something else went wrong
Err(error) => return Err(error),
};
// Pick a way to print `&buf[..written]`
println!("{}", std::str::from_utf8(&buf[..written]).unwrap());
Ok(())
}
fn main() {
print_limited(format_args!("this is just a test")).unwrap();
print_limited(format_args!("{}", 123)).unwrap();
print_limited(format_args!("{}", 'a')).unwrap();
}
thi
123
a
This was actually more involved than I originally thought. There might be a cleaner way to do this.
I found this word here
For non-numeric types, this can be considered a "maximum width". If the resulting string is longer than this width, then it is truncated down to this many characters and that truncated value is emitted with proper fill, alignment and width if those parameters are set.
For integral types, this is ignored.
For floating-point types, this indicates how many digits after the decimal point should be printed.
And format_args return type is std::fmt::Arguments,that is not String ,even though it looks like a string.
If you want to get same print contents,i think those code will work
/// unstable
println!("{:.3}", format_args!("this is just a test").as_str().unwrap());
println!("{:.3}", format_args!("this is just a test").to_string().as_str());

How to split a String in Rust that I take as an input with read!()

I want to split a String that I give as an input according to white spaces in it.
I have used the split_whitespaces() function but when I use this function on a custom input it just gives me the first String slice.
let s:String = read!();
let mut i:usize = 0;
for token in s.split_whitespace() {
println!("token {} {}", i, token);
i+=1;
}
What am I missing?
As far as I know, read! is not a standard macro. A quick search reveals that is probably is from the text_io crate (if you are using external crates you should tell so in the question).
From the docs in that crate:
The read!() macro will always read until the next ascii whitespace character (\n, \r, \t or space).
So what you are seeing is by design.
If you want to read a whole line from stdin you may try the standard function std::Stdin::read_line.
You are missing test cases which could locate the source of the problem. Split the code into a function and replace the read!()-macro with a test case, which you could put in main for now, where you provide different strings to the function and observe the output.
fn strspilit(s:String){
let mut i:usize = 0;
for token in s.split_whitespace() {
println!("token {} {}", i, token);
i+=1;
}
}
fn main() {
println!("Hello, world!");
strspilit("Hello Huge World".to_string());
}
Then you will see your code is working as it should but as notices in other answers the read!() macro is only returning the string until the first white space so you should probably use another way of reading your input.

How do I print output without a trailing newline in Rust?

The macro println! in Rust always leaves a newline character at the end of each output. For example
println!("Enter the number : ");
io::stdin().read_line(&mut num);
gives the output
Enter the number :
56
I don't want the user's input 56 to be on a new line. How do I do this?
It's trickier than it would seem at first glance. Other answers mention the print! macro but it's not quite that simple. You'll likely need to flush stdout, as it may not be written to the screen immediately. flush() is a trait method that is part of std::io::Write so that needs to be in scope for it to work (this is a pretty easy early mistake).
use std::io;
use std::io::Write; // <--- bring flush() into scope
fn main() {
println!("I'm picking a number between 1 and 100...");
print!("Enter a number: ");
io::stdout().flush().unwrap();
let mut val = String::new();
io::stdin().read_line(&mut val)
.expect("Error getting guess");
println!("You entered {}", val);
}
You can use the print! macro instead.
print!("Enter the number : ");
io::stdin().read_line(&mut num);
Beware:
Note that stdout is frequently line-buffered by default so it may be necessary to use io::stdout().flush() to ensure the output is emitted immediately.
Don't use the print/ln!-macros. Use write/ln!-macros.
It is more verbose, but print/ln! are problematic for using in command-line apps where their output might get piped or redirected to other apps, which is characteristic for Unix environments.
There is used always the same (only once requested and "buffered") stdout-device, but the stdout-device of the system is changed for piping/redirecting. So for each output to stdout you have to request the current stdout-device (std::io::stdout()). This can be done with write/ln!-macros.
So to say print/ln! is broken and there is an open issue since years.

Comparing string in Rust

I want to compare a string input from stdin to a static string with no luck.
Here is what I have tried so far:
fn main() -> () {
let mut line = "".to_string();
let exit = "exit".to_string();
while line.as_slice() != exit.as_slice() {
let input = std::io::stdin().read_line().ok();
line.push_str( input.unwrap().to_string().as_slice() );
assert_eq!(line, exit);
}
}
However during assertion it failed. How should I compare a string input to a static string in Rust?
Your help is very much appreciated.
First of all, the line contains the line terminator. You probably want to use trim (or one of its variants) to ignore that.
Secondly, you're doing a lot of unnecessary conversions and allocations. Try to avoid those.
Third, to_string is (or at least, was the last time I checked) inefficient due to over-allocation. You want into_string.
Fourth, the quickest way to go from a String to a &str is to "cross-borrow" it; given a String s, &*s will re-borrow it as a &str. This is because a String implements Deref<&str>; in other words, String acts kind of like a smart pointer to a borrowed string, allowing it to decay into a simpler form.
Fifth, unless you're doing something unusual, you can rewrite this as a for loop using the lines iterator method.
Sixth, be aware that stdin() actually allocates a new buffered reader every time you call it. Not only that, but characters read into the buffer do not get "pushed back" into STDIN when a new buffer is created; that data is simply lost. So you really don't want to be calling it in a loop. If you need to, call it once and keep the result in a variable.
So, I end up with this:
fn main() {
for line in std::io::stdin().lines() {
// Extract the line, or handle the error.
let line = match line {
Ok(line) => line,
Err(err) => panic!("failed to read line: {}", err)
};
assert_eq!(line.trim(), "exit");
}
}

Resources