[Pre-RFC/Idea] Extend `io::Seek` with convenience methods (with e.g. `stream_len()`)

I found some time to quickly throw together the crate seek-ext. I also thought about the mentioned problems and changed my proposal a bit. I now propose to add these two methods:

  • fn stream_len(&mut self) -> io::Result<u64>
  • fn current_position(&mut self) -> io::Result<u64>

Here is the full implementation including documentation (if you prefer, you can read it in the rendered form):

/// Returns the length (in bytes) of this stream.
///
/// This method is implemented using 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 undefined.
///
/// 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 use its
/// return value (it is also the stream length).
///
///
/// # Example
///
/// ```
/// use std::io::{Cursor, Seek, SeekFrom};
/// use seek_ext::SeekExt;
///
/// # fn main() -> Result<(), std::io::Error> {
/// let mut c = Cursor::new(vec![0; 6]);
/// let pos_before = c.seek(SeekFrom::Current(4))?;
///
/// assert_eq!(c.stream_len()?, 6);
/// assert_eq!(c.current_position()?, pos_before);
/// # Ok(())
/// # }
/// ```
fn stream_len(&mut self) -> Result<u64> {
    let old_pos = self.current_position()?;
    let len = self.seek(SeekFrom::End(0))?;
    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
///
/// ```
/// use std::io::{Cursor, Seek, SeekFrom};
/// use seek_ext::SeekExt;
///
/// # fn main() -> Result<(), std::io::Error> {
/// let mut c = Cursor::new(vec![0; 6]);
///
/// c.seek(SeekFrom::Current(4))?;
/// assert_eq!(c.current_position()?, 4);
///
/// c.seek(SeekFrom::Current(-3))?;
/// assert_eq!(c.current_position()?, 1);
/// # Ok(())
/// # }
/// ```
fn current_position(&mut self) -> Result<u64> {
    self.seek(SeekFrom::Current(0))
}

In particular, as a response to the problems mentioned in this thread:

  • "stream_len looks like a getter": from the name alone, yes. But luckily we have nice types in Rust and the &mut self is a clear indicator that the state of the object might be changed. Additionally, the documentation clearly states what side effect can/should be expected.

  • If any of the seek() operations inside of stream_len fails, the stream position is undefined”: yes, so be it. As you can see, I included this information in the documentation which just states this fact. I don’t think it’s a problem, because (a) in almost all cases, IO errors will be passed up the stack and the seekable object won’t ever be inspected again, and (b) the user can recover from it by simply resetting the seek position.

1 Like