Change:
There is a panic safety issue in : after a call panics, the impl of calls again, which means the buffer contents are potentially written twice. This may cause an application to overwrite parts of a file that it did not mean to overwrite (in a DB engine written in Rust, this could cause unrecoverable data corruption!). Demonstration: The expected output of the demo program is , the actual output is: More generally, we need a story for panic safety in Rust. My takeaway from the related discussions (e.g. , , the trait) was that only code and impls should have to worry about panic safety. The demo app contains none of these, so I'd consider this a bug in . (otherwise all implementations would need to provide the strong exception safety guarantee?) Solution: could use temporarily mark the buffer as empty during the calls; so that the impl doesn't do anything after a panic. However, this doesn't help if the panic occurs during a call...
Note that Rust is normally referring to "memory safety" when talking about safety, so the issue here isn't in conflict with the related discussions. It's definitely a bug in , but I want to point out how specific it is here: the only reason you have odd behaviour is because you have a implementation that will panic after successfully writing the data it is given. I'm all for making more robust, but it's not just "if the call panics", it's "if the call panics after successfully writing data". Panicking before a successful write (or if the write fails), would not have the same issue.
If your impl is panicking in the middle of a write, it seems to me like you're in data corruption land with or without a second write. This is not quite the case - and thread locals expand that surface area. What's specifically the expected behavior here? Is the implementation specifically the thing that needs tweaking, or should the totally poison itself? For reference, Java's class has no special handling for exceptions thrown from the inner : , so adjusting alone seems like a reasonable route to take.
Without the , a panic in the middle of a write (without ) only corrupts the part of the file that the application meant to overwrite. In a DB engine context, that means the resulting corruption can be repaired when the transaction is rolled back. But with , a panic after some data was written might corrupt parts of the file that are not saved in the rollback journal, so recovery not possible. I think we need some guideline like: If a method call panics, it's a bug to call any additional methods on the same object. Thus, the bug is in . may need something similar to a poison-flag so that the impl can detect whether a previous call panicked, but it doesn't need to protect against it's own methods being called after a panic (that would be considered a bug in the caller). The Java has the same problem, but the expectation in the Java world is different: it's trivially possible to catch an exception and continue using the potentially-invalid state; so in Java's case I would argue that all implementations should provide the strong exception safety guarantee to avoid this kind of issue. Essentially, the Rust world needs to decide between a Java/C++-style "strong exception safety guarantee" (=failing method call should not have any side effects) or a new "no-use-after-panic guarantee" (=after a failing method call, no additional method calls should occur). Some types (like BufWriter) can be compatible with at most one of the guarantees -- strong exception safety requires that a panicking call has no side effects; no-use-after-panic requires that a panicking call has the side effect of suppressing the buffer flush in the impl. The trait seems to be roughly equivalent to basic exception safety. Types that do not implement instead live in the world. Generic types implementing need to be careful not to call any functions on the type parameter from within the implementation if a previous call panicked (unless they use a bound).
triage: P-medium is fixing the data corruption in

Discussion:
pub struct BufWriter<W: Write> { inner: Option<W>, buf: Vec<u8>, <ins> // #30888: If the inner writer panics in a call to write, we don't want to // write the buffered data a second time in BufWriter's destructor. This // flag tells the Drop impl if it should skip the flush. panicked: bool, </ins> } /// An error returned by `into_inner` which combines an error that

BufWriter { inner: Some(inner), buf: Vec::with_capacity(cap), <ins> panicked: false, </ins> } }

let len = self.buf.len(); let mut ret = Ok(()); while written < len { <del> match self.inner.as_mut().unwrap().write(&self.buf[written..]) { </del> <ins> self.panicked = true; let r = self.inner.as_mut().unwrap().write(&self.buf[written..]); self.panicked = false; match r { </ins> Ok(0) => { ret = Err(Error::new(ErrorKind::WriteZero, "failed to write the buffered data"));

try!(self.flush_buf()); } if buf.len() >= self.buf.capacity() { <del> self.inner.as_mut().unwrap().write(buf) </del> <ins> self.panicked = true; let r = self.inner.as_mut().unwrap().write(buf); self.panicked = false; r </ins> } else { let amt = cmp::min(buf.len(), self.buf.capacity()); Write::write(&mut self.buf, &buf[..amt])

#[stable(feature = "rust1", since = "1.0.0")] impl<W: Write> Drop for BufWriter<W> { fn drop(&mut self) { <del> if self.inner.is_some() { </del> <ins> if self.inner.is_some() && !self.panicked { </ins> // dtors should not panic, so we ignore a failed flush let _r = self.flush_buf(); }

use prelude::v1::*; use io::prelude::*; use io::{self, BufReader, BufWriter, LineWriter, SeekFrom}; <ins> use sync::atomic::{AtomicUsize, Ordering}; use thread; </ins> use test; /// A dummy reader intended at testing short-reads propagation.

panic!(); } <ins> #[test] fn panic_in_write_doesnt_flush_in_drop() { static WRITES: AtomicUsize = AtomicUsize::new(0); struct PanicWriter; impl Write for PanicWriter { fn write(&mut self, _: &[u8]) -> io::Result<usize> { WRITES.fetch_add(1, Ordering::SeqCst); panic!(); } fn flush(&mut self) -> io::Result<()> { Ok(()) } } thread::spawn(|| { let mut writer = BufWriter::new(PanicWriter); writer.write(b"hello world"); writer.flush(); }).join().err().unwrap(); assert_eq!(WRITES.load(Ordering::SeqCst), 1); } </ins> #[bench] fn bench_buffered_reader(b: &mut test::Bencher) { b.iter(|| {