Skip to content

Commit 246d327

Browse files
committed
Implement Seek::stream_position() for BufReader
Optimization over BufReader::seek() for getting the current position without flushing the internal buffer. Related to #31100. Based on code in #70577.
1 parent 445f34b commit 246d327

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

library/std/src/io/buffered.rs

+45
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,51 @@ impl<R: Seek> Seek for BufReader<R> {
386386
self.discard_buffer();
387387
Ok(result)
388388
}
389+
390+
/// Returns the current seek position from the start of the stream.
391+
///
392+
/// The value returned is equivalent to `self.seek(SeekFrom::Current(0))`
393+
/// but does not flush the internal buffer. Due to this optimization the
394+
/// function does not guarantee that calling `.into_inner()` immediately
395+
/// afterwards will yield the underlying reader at the same position. Use
396+
/// [`BufReader::seek`] instead if you require that guarantee.
397+
///
398+
/// # Panics
399+
///
400+
/// This function will panic if the position of the inner reader is smaller
401+
/// than the amount of buffered data. That can happen if the inner reader
402+
/// has an incorrect implementation of [`Seek::stream_position`], or if the
403+
/// position has gone out of sync due to calling [`Seek::seek`] directly on
404+
/// the underlying reader.
405+
///
406+
/// # Example
407+
///
408+
/// ```no_run
409+
/// #![feature(seek_convenience)]
410+
/// use std::{
411+
/// io::{self, BufRead, BufReader, Seek},
412+
/// fs::File,
413+
/// };
414+
///
415+
/// fn main() -> io::Result<()> {
416+
/// let mut f = BufReader::new(File::open("foo.txt")?);
417+
///
418+
/// let before = f.stream_position()?;
419+
/// f.read_line(&mut String::new())?;
420+
/// let after = f.stream_position()?;
421+
///
422+
/// println!("The first line was {} bytes long", after - before);
423+
/// Ok(())
424+
/// }
425+
/// ```
426+
fn stream_position(&mut self) -> io::Result<u64> {
427+
let remainder = (self.cap - self.pos) as u64;
428+
self.inner.stream_position().map(|pos| {
429+
pos.checked_sub(remainder).expect(
430+
"overflow when subtracting remaining buffer size from inner stream position",
431+
)
432+
})
433+
}
389434
}
390435

391436
/// Wraps a writer and buffers its output.

library/std/src/io/buffered/tests.rs

+42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::io::prelude::*;
22
use crate::io::{self, BufReader, BufWriter, ErrorKind, IoSlice, LineWriter, SeekFrom};
3+
use crate::panic;
34
use crate::sync::atomic::{AtomicUsize, Ordering};
45
use crate::thread;
56

@@ -86,6 +87,47 @@ fn test_buffered_reader_seek_relative() {
8687
assert_eq!(reader.fill_buf().ok(), Some(&[2, 3][..]));
8788
}
8889

90+
#[test]
91+
fn test_buffered_reader_stream_position() {
92+
let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4];
93+
let mut reader = BufReader::with_capacity(2, io::Cursor::new(inner));
94+
95+
assert_eq!(reader.stream_position().ok(), Some(0));
96+
assert_eq!(reader.seek(SeekFrom::Start(3)).ok(), Some(3));
97+
assert_eq!(reader.stream_position().ok(), Some(3));
98+
// relative seeking within the buffer and reading position should keep the buffer
99+
assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..]));
100+
assert!(reader.seek_relative(0).is_ok());
101+
assert_eq!(reader.stream_position().ok(), Some(3));
102+
assert_eq!(reader.buffer(), &[0, 1][..]);
103+
assert!(reader.seek_relative(1).is_ok());
104+
assert_eq!(reader.stream_position().ok(), Some(4));
105+
assert_eq!(reader.buffer(), &[1][..]);
106+
assert!(reader.seek_relative(-1).is_ok());
107+
assert_eq!(reader.stream_position().ok(), Some(3));
108+
assert_eq!(reader.buffer(), &[0, 1][..]);
109+
// relative seeking outside the buffer will discard it
110+
assert!(reader.seek_relative(2).is_ok());
111+
assert_eq!(reader.stream_position().ok(), Some(5));
112+
assert_eq!(reader.buffer(), &[][..]);
113+
}
114+
115+
#[test]
116+
fn test_buffered_reader_stream_position_panic() {
117+
let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4];
118+
let mut reader = BufReader::with_capacity(4, io::Cursor::new(inner));
119+
120+
// cause internal buffer to be filled but read only partially
121+
let mut buffer = [0, 0];
122+
assert!(reader.read_exact(&mut buffer).is_ok());
123+
// rewinding the internal reader will cause buffer to loose sync
124+
let inner = reader.get_mut();
125+
assert!(inner.seek(SeekFrom::Start(0)).is_ok());
126+
// overflow when subtracting the remaining buffer size from current position
127+
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| reader.stream_position().ok()));
128+
assert!(result.is_err());
129+
}
130+
89131
#[test]
90132
fn test_buffered_reader_invalidated_after_read() {
91133
let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4];

0 commit comments

Comments
 (0)