Skip to content

Instantly share code, notes, and snippets.

@matklad matklad/
Created Aug 21, 2019

What would you like to do?
VFS API for Rust
use std::path::Path;
/// VFS provides two benefits over using `fs:
/// * consistent snapshots: reading the same file twice is guranteed to return
/// the same result, unless you explicitly advance the revision of VFS
/// * automated file watching: if you read a file or a dir, you will be notified when the
/// file changes.
struct Vfs {}
/// Files are represented by integer handles. This is to avoid costly string
/// hashing and comparison when storing files in maps.
/// In a sense, `VFile` is an interned `PathBuf`
struct VFile(u32);
impl VFile {
fn contents(self, vfs: &Vfs) -> &[u8] {}
fn path(self, vfs: &Vfs) -> &Path {}
/// Not sure if it makes sense to differentiate between files & dirs on the type
/// level.
struct VDir(u32);
impl VDir {
fn path(self, vfs: &Vfs) -> &Path {}
fn list(self, vfs: &Vfs) -> impl Iterator<Item = Either<VFile, VDir>> + '_;
fn walk(self, vfs: &Vfs, exclude: impl Fn(&Path) -> bool) -> impl Iterator<Item = Either<VFile, VDir>> + '_;
struct VfsDiff {
// Should probably differentiate between created/changed/renamed.
// What does `VFile` mean for a file which has been deleted?
changed_files: Vec<VFile>,
changed_dirs: Vec<Dir>,
impl Vfs {
fn new(on_error: impl Fn(&Path, io::Error)) -> Vfs {
// Starts background watcher thread
fn get_file(&self, path: &Path) -> Option<VFile> {}
fn get_dir(&self, path: &Path) -> Option<VDir> {}
/// Watcher thread does not directly commit changes to VFS
fn commit_pending_changes(&mut self) -> VfsDiff {}
/// This somehow needs to be a `select!` able API.
/// I'd rather *not* use futures for the time being, so something like
/// `fn pending_changes() -> crossbeam_channel::Receiver<()>` might work?
fn has_pending_changes(&self) -> bool {
/// Overlays (ie, files which are changed in the editor, but no on disk). I
/// *think* they can be built on top, but it would also be useful to have
/// them built-in.
/// Note that these functions don't actually change the VFS, like the
/// watcher, they only signal pending changes. `commit_pending_changes` must
/// be used to actually retrieve what's new.
/// contents is optional to encode files which are deleted in the editor,
/// but are still on disk. Again, it's unclear what `VFile` means for such
/// semi-zombie files.
fn add_file_overlay(&mut self, path: &Path, initial_contents: Option<Vec<u8>>) -> VFile;
fn change_file_overlay(&mut self, file: VFile, new_contents: Option<Vec<u8>>);
/// Advanced function for file modification, for cases where the client
/// sends a file diff. This unfortunaltelly will have to modify the file
/// in-place, bypassing the `pending_changes` infra.
fn raw_change_file_overlay(&mut self, file: VFile) -> &mut Vec<u8>;
fn remove_file_overlay(&mut self, file: VFile);
impl Drop for Vfs {
fn drop() {
// terminates background watcher thread
fn main(requests: Receiver<Request>) {
let mut vfs = Vfs::new();
let mut app_state = State::new();
loop {
let event = select! {
req = requests.recv() -> Event::Request(req),
() = vfs.pending_changes() => Event::VfsChange,
match event {
Event::Request(req) => handle_request(&vfs, &mut app_state, req),
Event::VfsChange => {
let changes = vfs.commit_pending_changes();
invalidate_caches(&mut app_state, changes)
/// This receives an `&Vfs`, which guarantees repeatable read of file system
/// contents.
fn handle_request(vfs: &Vfs, state: &mut AppState, request: Request) {
fn invalidate_caches(state: &mut AppState, changes: VfsDiff) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.