4 ms·
I can't remember the last time I came across a modern language where the "easy default" is unbuffered I/O. A beginner learning C++ today will be getting famili
by BoorishBears 5y ago
I can't remember the last time I came across a modern language where the "easy default" is unbuffered I/O.
A beginner learning C++ today will be getting familiar with buffered I/O primitives by the time they write a "Hello World"
Java mostly keeps the "useful" access patterns (anything more than reading/writing a single byte or manually passing in byte buffers) in buffered wrappers so even a clueless beginner is inclined to use them.
People are missing the forest for the trees here, it's about ergonomics and encouraging people to do the right thing by default.
Ironic to see this attitude in a Rust comment section of all places, isn't this the same attitude that leads people to dismiss Rust? "Just write correct C code without UB, the major pitfalls haven't changed in the last 4 decades duh?"
- PaulDavisThe1st 5y ago> I can't remember the last time I came across a modern language where the "easy default" is unbuffered I/O. "Hello World" in C from 1986 uses printf() which is buffered. So yeah, it's been a long, long, long time.
- kccqzy 5y agoIt's because there's this new trend of high-performance, bufferless, copy-less technologies like io_uring getting mindshare. Beginners who don't understand why these technologies work then blindly remove buffering, or forget to add buffering in the first place.
- trasz 5y agoIt’s not really new, it’s a return to how things were done before Unix.
- spoiler 5y agoWhat is the forest here (asking genuinely)? Language usage ergonomics? I think it's fine like this, because most of the time the language makes it easy to wrap/convert a writer into a buffered writer and use that instead. Also, it preserves the principle of least surprise. If I call write on a Writer, I expect it to write immediately. When I want it to be buffered, I would either look for it in the stdlib (and in Rust's case expect to find it) or look for a lib (sans implementing it)
- PaulDavisThe1st 5y ago> If I call write on a Writer, I expect it to write immediately. But you can't know what "to write" actually means. When you call write(2) on a POSIX system, what is it that you think happens immediately? Patterns of NVRAM storage are changed? Fluctuating voltage on a cat-5 cable wobbles a bit in response? OK, maybe you can get away with saying "If I call write on a Writer I expect it to hand over the data to the kernel immediately", and that's somewhat reasonable. The problem is that such a design encourages beginner programmers to use unbuffered IO, which is generally the wrong thing to do.
- spoiler 5y agoThey're not encouraged to use unbuffered io. The documentation for FS and IO both point you in the direction of BufRead and explain why you might want it (in the intro section of each).
- BoorishBears 5y agoI mentioned what least surprise is to me: I'm used to a raw stream allowing a few specific operations, if it only allows: - read/write/peek one single byte - write a byte array, but you must explicitly specify length and offset I assume it's unbuffered. Usually those are the building blocks of a buffered stream API after all. It also matches write(2) closely (offset replaces pointer math) What surprises me is that it takes a full byte string and writes it with no hints about offset or length, usually that's only provided with buffered interfaces. - Again, there's no written rules about it, I'm just sharing my experience from writing code in a lot of different languages. Someone else might have an experience with a different set and have no such expectation, so I'm not saying Rust is wrong, I'm instead pointing out why this isn't a "well duh" moment
- spoiler 5y agoI think it doesn't ask for length because it would increase the chance of mistakes happening. You don't really need to pass the length "manually" because its encoded in the value already. Not sure what you mean by offset, but you could just index with a range probably (ie `bytes_buff[n..]`), but that won't work for Strings (because they're internally encoded as UTF-8, but you could use `.chars()` and then the iterator methods on it to select which part of the string you want to write).
- xxs 5y ago> I can't remember the last time I came across a modern language where the "easy default" is unbuffered I/O. Java. There In that regard it got turned weirded with multiple buffers which slows down as well, just on as much as totally unbuffered. Java 1.4 added nio where the buffers were part of the framework.
- BoorishBears 5y agoI mentioned Java didn't I? Even in the pre-NIO APIs: If you try to write a string to a FileOutputStream the way that Rust code did this is what it looks like: byte[] byteString = "Hello World".getBytes(StandardCharsets.US_ASCII); myStream.write(byteString, 0, byteString.length); But if you use a FileWriter, which has a built-in buffer and wraps the OutputStream: myWriter.write("Hello World"); For me, if the Rust example had required a length and offset, I'd immediately assume it was unbuffered. I keep clarifying: I realize it's not a rule, but I guess that's just a pattern I've seen across a lot of languages. Maybe because it somewhat matches write(2)?
- xxs 5y agoIndeed, Reader/Writer framerwork provides buffering (esp. now). However, in my experience the writers are not very commonly used which is a stark contrast to BufferedReader that's everywhere (it comes with readLine()). Instead, writing is commonly implemented via encoding/conversion/etc. to a byte[] and then written directly to the output stream.