Skip to content

Commit 89573b3

Browse files
committed
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.
2 parents 48e354d + f95219f commit 89573b3

File tree

2 files changed

+134
-2
lines changed

2 files changed

+134
-2
lines changed

src/libstd/io/cursor.rs

+8
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ impl<T> io::Seek for Cursor<T> where T: AsRef<[u8]> {
212212
"invalid seek to a negative or overflowing position"))
213213
}
214214
}
215+
216+
fn stream_len(&mut self) -> io::Result<u64> {
217+
Ok(self.inner.as_ref().len() as u64)
218+
}
219+
220+
fn stream_position(&mut self) -> io::Result<u64> {
221+
Ok(self.pos)
222+
}
215223
}
216224

217225
#[stable(feature = "rust1", since = "1.0.0")]

src/libstd/io/mod.rs

+126-2
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,85 @@ pub trait Seek {
13451345
/// [`SeekFrom::Start`]: enum.SeekFrom.html#variant.Start
13461346
#[stable(feature = "rust1", since = "1.0.0")]
13471347
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
1348+
1349+
/// Returns the length of this stream (in bytes).
1350+
///
1351+
/// This method is implemented using up to three seek operations. If this
1352+
/// method returns successfully, the seek position is unchanged (i.e. the
1353+
/// position before calling this method is the same as afterwards).
1354+
/// However, if this method returns an error, the seek position is
1355+
/// unspecified.
1356+
///
1357+
/// If you need to obtain the length of *many* streams and you don't care
1358+
/// about the seek position afterwards, you can reduce the number of seek
1359+
/// operations by simply calling `seek(SeekFrom::End(0))` and using its
1360+
/// return value (it is also the stream length).
1361+
///
1362+
/// Note that length of a stream can change over time (for example, when
1363+
/// data is appended to a file). So calling this method multiple times does
1364+
/// not necessarily return the same length each time.
1365+
///
1366+
///
1367+
/// # Example
1368+
///
1369+
/// ```no_run
1370+
/// #![feature(seek_convenience)]
1371+
/// use std::{
1372+
/// io::{self, Seek},
1373+
/// fs::File,
1374+
/// };
1375+
///
1376+
/// fn main() -> io::Result<()> {
1377+
/// let mut f = File::open("foo.txt")?;
1378+
///
1379+
/// let len = f.stream_len()?;
1380+
/// println!("The file is currently {} bytes long", len);
1381+
/// Ok(())
1382+
/// }
1383+
/// ```
1384+
#[unstable(feature = "seek_convenience", issue = "0")]
1385+
fn stream_len(&mut self) -> Result<u64> {
1386+
let old_pos = self.stream_position()?;
1387+
let len = self.seek(SeekFrom::End(0))?;
1388+
1389+
// Avoid seeking a third time when we were already at the end of the
1390+
// stream. The branch is usually way cheaper than a seek operation.
1391+
if old_pos != len {
1392+
self.seek(SeekFrom::Start(old_pos))?;
1393+
}
1394+
1395+
Ok(len)
1396+
}
1397+
1398+
/// Returns the current seek position from the start of the stream.
1399+
///
1400+
/// This is equivalent to `self.seek(SeekFrom::Current(0))`.
1401+
///
1402+
///
1403+
/// # Example
1404+
///
1405+
/// ```no_run
1406+
/// #![feature(seek_convenience)]
1407+
/// use std::{
1408+
/// io::{self, BufRead, BufReader, Seek},
1409+
/// fs::File,
1410+
/// };
1411+
///
1412+
/// fn main() -> io::Result<()> {
1413+
/// let mut f = BufReader::new(File::open("foo.txt")?);
1414+
///
1415+
/// let before = f.stream_position()?;
1416+
/// f.read_line(&mut String::new())?;
1417+
/// let after = f.stream_position()?;
1418+
///
1419+
/// println!("The first line was {} bytes long", after - before);
1420+
/// Ok(())
1421+
/// }
1422+
/// ```
1423+
#[unstable(feature = "seek_convenience", issue = "0")]
1424+
fn stream_position(&mut self) -> Result<u64> {
1425+
self.seek(SeekFrom::Current(0))
1426+
}
13481427
}
13491428

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

21792257
#[test]
21802258
#[cfg_attr(target_os = "emscripten", ignore)]
@@ -2396,4 +2474,50 @@ mod tests {
23962474
super::read_to_end(&mut lr, &mut vec)
23972475
});
23982476
}
2477+
2478+
#[test]
2479+
fn seek_len() -> io::Result<()> {
2480+
let mut c = Cursor::new(vec![0; 15]);
2481+
assert_eq!(c.stream_len()?, 15);
2482+
2483+
c.seek(SeekFrom::End(0))?;
2484+
let old_pos = c.stream_position()?;
2485+
assert_eq!(c.stream_len()?, 15);
2486+
assert_eq!(c.stream_position()?, old_pos);
2487+
2488+
c.seek(SeekFrom::Start(7))?;
2489+
c.seek(SeekFrom::Current(2))?;
2490+
let old_pos = c.stream_position()?;
2491+
assert_eq!(c.stream_len()?, 15);
2492+
assert_eq!(c.stream_position()?, old_pos);
2493+
2494+
Ok(())
2495+
}
2496+
2497+
#[test]
2498+
fn seek_position() -> io::Result<()> {
2499+
// All `asserts` are duplicated here to make sure the method does not
2500+
// change anything about the seek state.
2501+
let mut c = Cursor::new(vec![0; 15]);
2502+
assert_eq!(c.stream_position()?, 0);
2503+
assert_eq!(c.stream_position()?, 0);
2504+
2505+
c.seek(SeekFrom::End(0))?;
2506+
assert_eq!(c.stream_position()?, 15);
2507+
assert_eq!(c.stream_position()?, 15);
2508+
2509+
2510+
c.seek(SeekFrom::Start(7))?;
2511+
c.seek(SeekFrom::Current(2))?;
2512+
assert_eq!(c.stream_position()?, 9);
2513+
assert_eq!(c.stream_position()?, 9);
2514+
2515+
c.seek(SeekFrom::End(-3))?;
2516+
c.seek(SeekFrom::Current(1))?;
2517+
c.seek(SeekFrom::Current(-5))?;
2518+
assert_eq!(c.stream_position()?, 8);
2519+
assert_eq!(c.stream_position()?, 8);
2520+
2521+
Ok(())
2522+
}
23992523
}

0 commit comments

Comments
 (0)