Hi everyone!
I'm completely new to Rust. I've read the crate book and a few pages from the main Rust book. I've also tried simple examples from those books.
use ferris_says::say;
use std::io::{stdout, BufWriter};
use regex::Regex;
fn main() {
//{ // <= if I do not add this phantom scope, then first output "Did our..." from println! macros
let stdout = stdout();
let message = String::from("Hello fellow Rustaceans");
let width = message.chars().count();
let mut writer = BufWriter::new(stdout.lock()); // <= here I lock io::stdout()
say(&message, width, &mut writer).unwrap(); // can I unlock here or something else?
//} // <= if I do not add this phantom scope, then first output "Did our..." from println! macros
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); // expected output second (last), but..
println!("Did our date match? {}", re.is_match("2026-05-05"));
}
Output for example above
nullpwr@mini hello-rust % cargo run
Compiling hello-rust v0.1.0 (/Volumes/source/github/nullpwr/hello-rust)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/hello-rust`
Did our date match? true /// <= must be output as second string
_________________________
< Hello fellow Rustaceans >
-------------------------
\
\
_~^~^~_
\) / o o \ (/
'_ - _'
/ '-----' \
Can someone explain what I did wrong? If I'm already blocking with stdout.lock(), and the println! macro also blocks stdout, why aren't they the same stdout?
And what's the correct way to avoid these logical errors? Maybe I should use unlock, or is phantom scope acceptable? Maybe I should read more books?
I tried to fully comment the code above (as I understand it).
Thanks in advance!
Part of the problem is that you're wrapping stdout.lock() in a BufWriter which is then passed to say(). Any bytes written to BufWriter are stored in memory until either a certain threshold is reached or flush() is called (and it's called implicitly when the BufWriter goes out of scope), and only then is the data actually written to the locked stdout handle.
As for unlocking before stdout goes out of scope, the only way to do that is to call drop() on stdout.lock() (or, in this case, on writer), which, in essence, makes the lock go out of scope explicitly without getting any actual scopes involved.
It’s unfortunately not documented, but the stdout lock is a “reentrant lock”. This means that the same thread is allowed to take the lock more than once; the only thing locking does is prevent other threads from writing interleaved with yours. This reduces the number of ways you can accidentally deadlock, but also weakens how much is guaranteed by locking; it’s a practical compromise.
The reason the output is in reverse order actually has nothing to do with the lock at all; it’s because the BufWriter you created has buffered the text, and doesn’t flush its buffer to the real stdout until it is dropped or the buffer becomes full.
I would say that the “best practice” here is to avoid mixing println! and a BufWriter, and instead use write!() to write into the BufWriter so that the "date match" goes into the same buffer as Ferris.
use std::io::Write; // this trait must be in scope for this to work
fn main() {
...
say(&message, width, &mut writer).unwrap();
write!(writer, "Did our date match? {}", re.is_match("2026-05-05")).unwrap();
// `writer` is dropped at the end of this scope and flushes its buffer
}
It is also a further improvement to explicitly call flush() on the BufWriter. This returns any IO error that occurs when trying to flush, rather than ignoring them.
@jwodder Thanks for explaining the flash/drop functions!
@kpreid Thanks! Explained very well! Marked as solution
I've already decided what I'll study in more depth: ownership/borrowing, lifetimes, and anything else, as far as my energy allows. )
Looking through examples, I see two options: unwrap and Result<()>. I can't yet understand the advantages and disadvantages of each approach. But I think using Result allows for more fine-grained control over what happens in the code.
A clarification: unwrap() is a method of Result. unwrap() is the simplest way of handling a Result; it’s not an alternative to Result.
In serious code, unwrap() should be used only for “this should never happen; if it does, the program has a bug”. So, it is not a good way to handle IO errors; just adequate for introductory programs where the focus is on other things.