File I/O

What exactly is a file?

The Unix File Abstraction

  • A file is a sequence of bytes
  • Doesn’t have to be persistent:
    • Sockets, memory maps, stdin, stdout, stderr
  • Supports these operations:
    • Open / Close
    • Read a range of bytes
    • Write a range of bytes
    • Change the current read/write position (seek)

Files in Rust

  • Distinguish between I/O operations and the file system
    • I/O: std::io
    • File system: std::fs
  • The Unix philosophy is modelled through three separate Rust traits:
    • std::io::Read
    • std::io::Write
    • std::io::Seek

The Read trait

pub trait Read {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
}
  • Read a sequence of bytes from a source into a byte buffer
  • Returns the number of bytes read, or an error
  • Q: Why not the following signature?
    • fn read(&mut self) -> Result<Vec<u8>>

The Write trait

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;
}
  • Write a sequence of bytes to a sink, returning the number of bytes written
  • Supports buffering through flush

The Seek trait

pub trait Seek {
    fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
}

pub enum SeekFrom {
    Start(u64), // Q: Why `u64` and not `i64`?
    End(i64),
    Current(i64),
}
  • Move the current position within a stream of bytes
  • Returns the new position within the stream, or an error

Overview

File I/O example

use anyhow::Result;
use std::fs::*;
use std::io::prelude::*;

const ENTRY_SIZE: usize = 128;

fn parse_entry(bytes: &[u8]) -> Result<(Key, Value)> { todo!() }

fn main() -> Result<()> {
    let entry_offset = 1024; // Offset taken from DB index

    let mut file = File::open("database.dat")?;
    file.seek(SeekFrom::Start(entry_offset))?;
    let mut buffer = vec![0; ENTRY_SIZE];
    file.read_exact(&mut buffer)?;

    let (key, value) = parse_entry(&buffer)?;
    println!("{key}: {value}");
} // File is automatically closed here

Network I/O is similar

use anyhow::Result;
use std::io::prelude::*;
use std::net::TcpStream;

const RESPONSE_SIZE: usize = 128;

fn main() -> Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:34254")?;

    stream.write(&[1])?;
    let mut response_buf = vec![0; RESPONSE_SIZE];
    stream.read_exact(&mut response_buf)?;
    Ok(())
} // TCP connection is automatically closed here