diff options
| author | bors <bors@rust-lang.org> | 2019-03-21 14:28:18 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2019-03-21 14:28:18 +0000 |
| commit | 89573b3c8b629507130b1ec8beeaf550fdc0e046 (patch) | |
| tree | 1ba57102396b918c7e59e7a73ddf69ddc1ae8856 /src/libstd | |
| parent | 48e354d224ff38ccde3936479a7f514b6492e910 (diff) | |
| parent | f95219fa580a514b5ca6c1425335afadbe394b57 (diff) | |
| download | rust-89573b3c8b629507130b1ec8beeaf550fdc0e046.tar.gz rust-89573b3c8b629507130b1ec8beeaf550fdc0e046.zip | |
Auto merge of #58422 - LukasKalbertodt:seek-convenience, r=alexcrichton
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.
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/io/cursor.rs | 8 | ||||
| -rw-r--r-- | src/libstd/io/mod.rs | 128 |
2 files changed, 134 insertions, 2 deletions
diff --git a/src/libstd/io/cursor.rs b/src/libstd/io/cursor.rs index 873da0898c7..247d45c3ec9 100644 --- a/src/libstd/io/cursor.rs +++ b/src/libstd/io/cursor.rs @@ -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")] diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index 1a2152a79af..4c88fc889f3 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -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. @@ -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)] @@ -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(()) + } } |
