4

Is there an easy way to read the first N bytes of a file in Rust? The two most relevant functions seem to be read and read_exact, but read can return fewer bytes than available for whatever reason, so I would have to call it in an annoying loop, and read_exact gives up if the file is shorter than N bytes (whereas I'd prefer it to just read the entire file).

This is not a duplicate of this question which can be solved with read_exact: How to read a specific number of bytes from a stream?

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • I would guess not, due to it trying to encapsulate all of the functionality of C's read function. See https://stackoverflow.com/questions/37042287/read-from-regular-file-block-or-return-less-data – Max Mar 31 '20 at 13:07
  • 1
    `reader.take(N).read_to_end()` – Shepmaster Mar 31 '20 at 13:34
  • Is that efficient? I feel like there's a decent chance it might do stuff one byte at a time... – Timmmm Mar 31 '20 at 13:39
  • This isn't a duplicate of that question because - as I explained in the question - I want to read fewer bytes if EOF is reached. The linked question is about reading exactly N bytes and failing if you get EOF first. I think this was pretty clear in my description but I edited the title to make the distinction clearer. Please reopen! – Timmmm Mar 31 '20 at 13:46
  • 1
    The `.take(N).read_to_end()` solution is inefficient: [it only reads 32 bytes at a time until the last read where it reads whatever remains to reach the total](https://doc.rust-lang.org/src/std/io/mod.rs.html#2285-2290) instead of attempting to read `N` bytes at once. – Jmb Mar 31 '20 at 14:22
  • @Jmb that sounds like it would be better as a comment on the answer that suggests using it. – Shepmaster Mar 31 '20 at 14:36

1 Answers1

3

I'm just going to copy the read_exact implementation and modify it slightly. It was already really close to working as desired.

/// This is the same as read_exact, except if it reaches EOF it doesn't return
/// an error, and it returns the number of bytes read.
fn read_up_to(file: &mut impl std::io::Read, mut buf: &mut [u8]) -> Result<usize, std::io::Error> {
    let buf_len = buf.len();

    while !buf.is_empty() {
        match file.read(buf) {
            Ok(0) => break,
            Ok(n) => {
                let tmp = buf;
                buf = &mut tmp[n..];
            }
            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
            Err(e) => return Err(e),
        }
    }
    Ok(buf_len - buf.len())
}

(Completely untested!)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • I think you linked `read_to_end`? [`read_exact`](https://github.com/rust-lang/rust/blob/2113659479a82ea69633b23ef710b58ab127755e/src/libstd/io/mod.rs#L764) unless I am being stupid! – Timmmm Mar 31 '20 at 13:43
  • Yes, sorry about that! – Shepmaster Mar 31 '20 at 14:30
  • @Stargateur: No. Read my question and the `read()` documentation. I think I was pretty clear. – Timmmm Apr 01 '20 at 07:11
  • @Shepmaster: Why? The correct answer to the "duplicate" is `read_exact()`. – Timmmm Apr 01 '20 at 07:19
  • 1
    @Timmmm _so I would have to call it in an annoying loop_ This is stated in your question but this answer has also loop, would you like to reconsider the answer on dupe? I thought you were looking for more idiomatic or concise solution? – Ömer Erden Apr 01 '20 at 13:30
  • Yeah my answer is not ideal. The `.take(N).read_to_end()` solution is definitely more convenient but I don't want to sacrifice performance so I'm going to use this inconvenient solution. – Timmmm Apr 02 '20 at 07:39