Skip to content

Commit

Permalink
Auto merge of #58422 - LukasKalbertodt:seek-convenience, r=alexcrichton
Browse files Browse the repository at this point in the history
Add provided methods `Seek::{stream_len, stream_position}`

This adds two new, provided methods to the `io::Seek` trait:
- `fn stream_len(&mut self) -> Result<u64>`
- `fn stream_position(&mut self) -> Result<u64>`

Both are added for convenience and to improve readability in user code. Reading `file.stream_len()` is much better than to manually seek two or three times. Similarly, `file.stream_position()` is much more clear than `file.seek(SeekFrom::Current(0))`.

You can find prior discussions [in this internals thread](https://internals.rust-lang.org/t/pre-rfc-idea-extend-io-seek-with-convenience-methods-with-e-g-stream-len/9262). I think I addressed all concerns in that thread.

I already wrote three RFCs to add a small new API to libstd but I noticed that many public changes to libstd happen without an RFC. So I figured I can try opening a PR directly without going through RFCs first. After all, we do have rfcbot here too. If you think this change is too big to merge without an RFC, I can still close this PR and write an RFC.
  • Loading branch information
bors committed Mar 21, 2019
2 parents 48e354d + f95219f commit 89573b3
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 2 deletions.
8 changes: 8 additions & 0 deletions src/libstd/io/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,14 @@ impl<T> io::Seek for Cursor<T> where T: AsRef<[u8]> {
"invalid seek to a negative or overflowing position"))
}
}

fn stream_len(&mut self) -> io::Result<u64> {
Ok(self.inner.as_ref().len() as u64)
}

fn stream_position(&mut self) -> io::Result<u64> {
Ok(self.pos)
}
}

#[stable(feature = "rust1", since = "1.0.0")]
Expand Down
128 changes: 126 additions & 2 deletions src/libstd/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,85 @@ pub trait Seek {
/// [`SeekFrom::Start`]: enum.SeekFrom.html#variant.Start
#[stable(feature = "rust1", since = "1.0.0")]
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;

/// Returns the length of this stream (in bytes).
///
/// This method is implemented using up to three seek operations. If this
/// method returns successfully, the seek position is unchanged (i.e. the
/// position before calling this method is the same as afterwards).
/// However, if this method returns an error, the seek position is
/// unspecified.
///
/// If you need to obtain the length of *many* streams and you don't care
/// about the seek position afterwards, you can reduce the number of seek
/// operations by simply calling `seek(SeekFrom::End(0))` and using its
/// return value (it is also the stream length).
///
/// Note that length of a stream can change over time (for example, when
/// data is appended to a file). So calling this method multiple times does
/// not necessarily return the same length each time.
///
///
/// # Example
///
/// ```no_run
/// #![feature(seek_convenience)]
/// use std::{
/// io::{self, Seek},
/// fs::File,
/// };
///
/// fn main() -> io::Result<()> {
/// let mut f = File::open("foo.txt")?;
///
/// let len = f.stream_len()?;
/// println!("The file is currently {} bytes long", len);
/// Ok(())
/// }
/// ```
#[unstable(feature = "seek_convenience", issue = "0")]
fn stream_len(&mut self) -> Result<u64> {
let old_pos = self.stream_position()?;
let len = self.seek(SeekFrom::End(0))?;

// Avoid seeking a third time when we were already at the end of the
// stream. The branch is usually way cheaper than a seek operation.
if old_pos != len {
self.seek(SeekFrom::Start(old_pos))?;
}

Ok(len)
}

/// Returns the current seek position from the start of the stream.
///
/// This is equivalent to `self.seek(SeekFrom::Current(0))`.
///
///
/// # Example
///
/// ```no_run
/// #![feature(seek_convenience)]
/// use std::{
/// io::{self, BufRead, BufReader, Seek},
/// fs::File,
/// };
///
/// fn main() -> io::Result<()> {
/// let mut f = BufReader::new(File::open("foo.txt")?);
///
/// let before = f.stream_position()?;
/// f.read_line(&mut String::new())?;
/// let after = f.stream_position()?;
///
/// println!("The first line was {} bytes long", after - before);
/// Ok(())
/// }
/// ```
#[unstable(feature = "seek_convenience", issue = "0")]
fn stream_position(&mut self) -> Result<u64> {
self.seek(SeekFrom::Current(0))
}
}

/// Enumeration of possible methods to seek within an I/O object.
Expand Down Expand Up @@ -2173,8 +2252,7 @@ impl<B: BufRead> Iterator for Lines<B> {
mod tests {
use crate::io::prelude::*;
use crate::io;
use super::Cursor;
use super::repeat;
use super::{Cursor, SeekFrom, repeat};

#[test]
#[cfg_attr(target_os = "emscripten", ignore)]
Expand Down Expand Up @@ -2396,4 +2474,50 @@ mod tests {
super::read_to_end(&mut lr, &mut vec)
});
}

#[test]
fn seek_len() -> io::Result<()> {
let mut c = Cursor::new(vec![0; 15]);
assert_eq!(c.stream_len()?, 15);

c.seek(SeekFrom::End(0))?;
let old_pos = c.stream_position()?;
assert_eq!(c.stream_len()?, 15);
assert_eq!(c.stream_position()?, old_pos);

c.seek(SeekFrom::Start(7))?;
c.seek(SeekFrom::Current(2))?;
let old_pos = c.stream_position()?;
assert_eq!(c.stream_len()?, 15);
assert_eq!(c.stream_position()?, old_pos);

Ok(())
}

#[test]
fn seek_position() -> io::Result<()> {
// All `asserts` are duplicated here to make sure the method does not
// change anything about the seek state.
let mut c = Cursor::new(vec![0; 15]);
assert_eq!(c.stream_position()?, 0);
assert_eq!(c.stream_position()?, 0);

c.seek(SeekFrom::End(0))?;
assert_eq!(c.stream_position()?, 15);
assert_eq!(c.stream_position()?, 15);


c.seek(SeekFrom::Start(7))?;
c.seek(SeekFrom::Current(2))?;
assert_eq!(c.stream_position()?, 9);
assert_eq!(c.stream_position()?, 9);

c.seek(SeekFrom::End(-3))?;
c.seek(SeekFrom::Current(1))?;
c.seek(SeekFrom::Current(-5))?;
assert_eq!(c.stream_position()?, 8);
assert_eq!(c.stream_position()?, 8);

Ok(())
}
}

0 comments on commit 89573b3

Please sign in to comment.