fs4
No libc, pure Rust cross-platform file locks. Original fs2, now supports async and replace libc by rustix.
1.1.0
61M downloads/mo
#1094 most downloaded on crates.io
al8n/fs4
What this package is like to depend on
Last release 3 months ago
28 Apr 2026
Ships unpredictably
gaps range from 2 weeks to 1.1 years
Rarely documented
notes for 5 of 28 stable releases
4 versions withdrawn
withdrawn after publishing
5 years old
32 releases · first in 2021
4 releases in the last 12 months
see the full history below
Release timeline
32 releases · Dec 2021 to Apr 2026Releases
latest 32-
1.1.028 Apr 2026Release notes
Open source →Changes
- Consolidate
FileExtandAsyncFileExtinto single crate-root traits
(fs4::FileExt,fs4::AsyncFileExt) instead of generating a distinct
trait per backend module. The per-backend modules (fs4::tokio,
fs4::async_std,fs4::smol,fs4::fs_err2,fs4::fs_err3,
fs4::fs_err2_tokio,fs4::fs_err3_tokio) now re-export the unified
crate-root trait. Method-call sites that import the trait viause
continue to compile unchanged; code that named two backend traits as
distinct types will see them unify. - Add blanket impls
impl<F: FileExt + ?Sized> FileExt for &Fand
impl<F: AsyncFileExt + ?Sized> AsyncFileExt for &F, so the
extension methods are now callable through shared references. - Seal
FileExtandAsyncFileExtvia a privatesealed::Sealed
supertrait, so the set of implementing types is closed to the
concrete file types fs4 already supports (and references to them).
This locks in the freedom to add methods to either trait in future
minor releases without breaking downstream impls. - Add
DynAsyncFileExt, an object-safe mirror ofAsyncFileExtwhose
async methods returnBoxFuture<'_, T>(alias for
Pin<Box<dyn Future<Output = T> + Send + '_>>). Use it whenever
type erasure is needed (Box<dyn DynAsyncFileExt>,
&dyn DynAsyncFileExt); prefer the staticAsyncFileExtfor
generic code since it has no allocation or dynamic-dispatch
overhead. Every type implementingAsyncFileExtalso implements
DynAsyncFileExt, and the trait is sealed. - Mark the delegating methods
#[inline(always)](skipped under
tarpaulincoverage builds).
Release notes
Open source →Changes
- Consolidate
FileExtandAsyncFileExtinto single crate-root traits (fs4::FileExt,fs4::AsyncFileExt) instead of generating a distinct trait per backend module. The per-backend modules (fs4::tokio,fs4::async_std,fs4::smol,fs4::fs_err2,fs4::fs_err3,fs4::fs_err2_tokio,fs4::fs_err3_tokio) now re-export the unified crate-root trait. Method-call sites that import the trait viausecontinue to compile unchanged; code that named two backend traits as distinct types will see them unify. - Add blanket impls
impl<F: FileExt + ?Sized> FileExt for &Fandimpl<F: AsyncFileExt + ?Sized> AsyncFileExt for &F, so the extension methods are now callable through shared references. - Seal
FileExtandAsyncFileExtvia a privatesealed::Sealedsupertrait, so the set of implementing types is closed to the concrete file types fs4 already supports (and references to them). This locks in the freedom to add methods to either trait in future minor releases without breaking downstream impls. - Add
DynAsyncFileExt, an object-safe mirror ofAsyncFileExtwhose async methods returnBoxFuture<'_, T>(alias forPin<Box<dyn Future<Output = T> + Send + '_>>). Use it whenever type erasure is needed (Box<dyn DynAsyncFileExt>,&dyn DynAsyncFileExt); prefer the staticAsyncFileExtfor generic code since it has no allocation or dynamic-dispatch overhead. Every type implementingAsyncFileExtalso implementsDynAsyncFileExt, and the trait is sealed. - Mark the delegating methods
#[inline(always)](skipped undertarpaulincoverage builds).
- Consolidate
-
1.0.223 Apr 2026Nothing published for this version
-
1.0.123 Apr 2026Release notes
Open source →Fixes
- Unix
allocate: short-circuit on allocated blocks (metadata().blocks() * 512 >= len) instead of logical EOF. The previousmetadata().len() >= lencheck silently turnedallocateinto a no-op on sparse files (logical length large, zero blocks reserved), violating the documented preallocation guarantee. The new check still skips the macOSF_PREALLOCATEre-allocate-ENOSPC path from #15, since it asks the right question: "are the blocks already reserved?" Applies to both the sync and async implementations. - Windows
statvfs: route the threeGetDiskFreeSpaceExWoutputs correctly.free_spacenow comes fromlpTotalNumberOfFreeBytes(volume-wide, quota-independent),available_spacefromlpFreeBytesAvailable(caller-scoped, honours per-user quotas), andtotal_spaceis computed from cluster math (sectors_per_cluster * bytes_per_sector * total_number_of_clusters) so it reports volume capacity rather than the caller's quota. On quota-enabled volumes the three fields now carry distinct, documented meanings; previouslyfree_spaceandavailable_spacewere identical andtotal_spaceunder-reported capacity. - Added
target_os = "fuchsia"to the Unixallocatefallocatebranch (sync and async). Fuchsia iscfg(unix)under rustc andrustixexposesfallocatethere, so the previous omission left the Fuchsia Unix build without anallocatesymbol onceFileExtwas enabled. With this fix, every fs4 feature builds on Fuchsia exceptfs-err3andfs-err3-tokio; those remain blocked onfs-err v3.3.0referencingstd::os::unix::fs::chroot, which rustc gates out ontarget_os = "fuchsia". The fs4 code no longer has a Fuchsia gap — the remaining one is upstream (https://github.com/andrewhickman/fs-err/issues/90).
Testing
- New regression test
allocate_reserves_blocks_on_sparse_file(sync and async, Linux-gated) creates a sparse file viaset_len, assertsallocated_size == 0, callsallocate, and asserts blocks are reserved.
Documentation
- README gained a Minimum Supported Rust Version section noting
that the crate's declared MSRV (
rust-version = "1.75.0") covers the defaultsyncfeature, and thatasync-std/smolinherit a higher effective MSRV (1.85) from their transitive dependencies (async-lock,async-signal).
- Unix
-
1.0.022 Apr 2026Release notes
Open source →1.0.0
Breakage
- Renamed
FileExt::lock_exclusive/AsyncFileExt::lock_exclusiveto
lock, matching the stabilized [std::fs::File::lock] API. - Renamed
FileExt::try_lock_exclusive/AsyncFileExt::try_lock_exclusive
totry_lock, matching [std::fs::File::try_lock]. - Changed the return type of
try_lockandtry_lock_sharedfrom
std::io::Result<bool>toResult<(), TryLockError>.Ok(())still
indicates the lock was acquired;Err(TryLockError::WouldBlock)now
indicates the lock is held by another handle. This matches the stable
[std::fs::File::try_lock] signature (Ok(false)was the nightly
shape prior to 1.89). - Removed the top-level
lock_contended_error()helper. Use
TryLockError::WouldBlockinstead. - Flattened the
fs_stdmodule: theFileExttrait for
std::fs::Filenow lives at the crate root. Update imports from
use fs4::fs_std::FileExt;touse fs4::FileExt;. All other
backends (fs_err2,fs_err3,tokio,smol,async_std,
fs_err2_tokio,fs_err3_tokio) remain nested, since each
defines its ownFileExt/AsyncFileExtover a different concrete
Filetype.
Additions
- New
fs4::TryLockErrorenum, mirroring [std::fs::TryLockError]
withError(io::Error)andWouldBlockvariants. Implements
Debug,Display,std::error::Error(withsource()exposing
the innerio::Error),From<io::Error>(kindWouldBlock
collapses into theWouldBlockvariant; everything else wraps into
Error), andFrom<TryLockError> for io::Error(WouldBlock
becomesio::Error::from(io::ErrorKind::WouldBlock);Error(inner)
passes through verbatim).
Fixes
- Fixed feature typos that made the crate fail to compile with only
fs-err2,fs-err3,fs-err2-tokio, orfs-err3-tokioenabled
(withoutsync/tokio).#[cfg(feature = "fs-err")]and
feature = "fs-err-tokio"both referenced feature names that do not
exist inCargo.toml. - On Windows, skip the internal
set_lencall when the file's existing
cluster-aligned allocation already coverslen. Avoids the Windows
buffered-I/O quirks observed when the end-of-file pointer is moved
inside an already-allocated cluster (#13). The trait doc now carves
this behavior out from the general "file size is at leastlen
bytes" contract. - Short-circuited
allocateon Unix when the file is already at least
lenbytes long. Fixes macOSfallocatespuriously returning
ENOSPCwhen re-callingallocate(len)on an existing file (#15). - Added
cygwinto theallocatetarget_osset so builds stop
failing with a missingsys::allocatesymbol on that target (#44). - Added
redoxandcygwinto the asyncallocatefallback branch
so it matches the sync variant (#43 follow-up). - Moved AIX from the async
fallocatebranch to theset_len
fallback branch, matching the sync implementation. - Trait docs now explicitly state that locks are released
automatically when the owning file handle is closed (#23). - Removed the stale trait-doc reference to non-existent cross-platform
tests inlib.rs(#16). - Mitigated #31 (compiler warning about name collisions with upcoming
std methods): because 1.0 renames the trait methods to match std
exactly,std::fs::File::lock/try_lock/unlock(stable in
Rust 1.89+) now win via inherent dispatch andunstable_name_collisions
no longer fires for std users on recent rustc. - Gated the
lock_impl!macro with#[allow(unused_macros)]so
cargo clippy --no-default-features(filesystem-stats-only build)
does not fail under-D warnings. - Removed dead duplicate macros
cfg_fs2_err/cfg_fs3_errfrom
lib.rs(unified withcfg_fs_err2/cfg_fs_err3). - Updated
html_root_urlto the 1.0.0 docs.rs path.
Dependency updates
- Bumped
windows-sysfrom 0.59 to 0.61.
Testing
- Removed all
#[bench]functions from the test harness. They
measured OS syscalls (flock,LockFileEx,fallocate,statvfs)
rather than fs4 code, and produced numbers dominated by the
underlying filesystem. Dropping them lets the crate build and test
on stable Rust (the bench harness was the only thing pinning
nightly via#![cfg_attr(test, feature(test))]).
rust-toolchain.tomlis nowstable. - Added regression tests:
allocate_preserves_eof_within_cluster— exercises the #13
precondition (cluster-alignedAllocationSizewith EOF inside
the cluster) and asserts EOF is not moved whenallocate(len)is
re-called withlen <= allocated_sizeon Windows.allocate_idempotent— exercises the #15 precondition (back-to-back
allocate(len)calls on macOS) to prevent a regression in the
short-circuit.- Seven
TryLockErrorunit tests covering variants,Display,
std::error::Error::source,From<io::Error>,From<TryLockError> for io::Error, and round-trip preservation ofErrorKind. FsStatsgetter + derive unit tests (previously every test
destructured the struct, so the four getter method bodies were
never executed and showed as uncovered).
- Fixed a long-standing macOS test failure: the cross-platform
filesystem_spacetest assertedavailable_space <= free_space,
which does not hold on macOS APFS. APFS counts purgeable space
(snapshots, cached data reclaimable on demand) inf_bavailbut
not inf_bfree, so the POSIX invariant is violated on every run.
The assertion is removed and the async variant now makes a single
statvfscall plus destructure instead of three separate calls
racing with concurrent filesystem activity from other tests.
CI / tooling
- New
.github/workflows/coverage.ymlworkflow: per-OS matrix
(ubuntu-latest, macos-latest, windows-latest) running
cargo tarpaulin --engine llvm --all-features --run-types tests --ignore-testson each runner and uploading per-OS reports to
Codecov with per-OS flags. Each runner--exclude-filesthe other
OS's sources so they do not register as 0/N uncovered lines. - The
crossjob now installs the MinGW-w64 toolchain when targeting
*-pc-windows-gnu.windows-sys0.61 invokesdlltoolduring
build, andubuntu-latestships only the x86_64 MinGW toolchain
by default. - README install snippets updated from
fs4 = { version = "0.13", ... }
tofs4 = { version = "1", ... }. - Housekeeping: added
.codecov.yml,rustfmt.toml(2-space indent,
explains the session-wide reformat), and
.github/workflows/loc.yml; removed the obsoletetea.yaml.
Release notes
Open source →Breakage
- Renamed
FileExt::lock_exclusive/AsyncFileExt::lock_exclusivetolock, matching the stabilized [std::fs::File::lock] API. - Renamed
FileExt::try_lock_exclusive/AsyncFileExt::try_lock_exclusivetotry_lock, matching [std::fs::File::try_lock]. - Changed the return type of
try_lockandtry_lock_sharedfromstd::io::Result<bool>toResult<(), TryLockError>.Ok(())still indicates the lock was acquired;Err(TryLockError::WouldBlock)now indicates the lock is held by another handle. This matches the stable [std::fs::File::try_lock] signature (Ok(false)was the nightly shape prior to 1.89). - Removed the top-level
lock_contended_error()helper. UseTryLockError::WouldBlockinstead. - Flattened the
fs_stdmodule: theFileExttrait forstd::fs::Filenow lives at the crate root. Update imports fromuse fs4::fs_std::FileExt;touse fs4::FileExt;. All other backends (fs_err2,fs_err3,tokio,smol,async_std,fs_err2_tokio,fs_err3_tokio) remain nested, since each defines its ownFileExt/AsyncFileExtover a different concreteFiletype.
Additions
- New
fs4::TryLockErrorenum, mirroring [std::fs::TryLockError] withError(io::Error)andWouldBlockvariants. ImplementsDebug,Display,std::error::Error(withsource()exposing the innerio::Error),From<io::Error>(kindWouldBlockcollapses into theWouldBlockvariant; everything else wraps intoError), andFrom<TryLockError> for io::Error(WouldBlockbecomesio::Error::from(io::ErrorKind::WouldBlock);Error(inner)passes through verbatim).
Fixes
- Fixed feature typos that made the crate fail to compile with only
fs-err2,fs-err3,fs-err2-tokio, orfs-err3-tokioenabled (withoutsync/tokio).#[cfg(feature = "fs-err")]andfeature = "fs-err-tokio"both referenced feature names that do not exist inCargo.toml. - On Windows, skip the internal
set_lencall when the file's existing cluster-aligned allocation already coverslen. Avoids the Windows buffered-I/O quirks observed when the end-of-file pointer is moved inside an already-allocated cluster (#13). The trait doc now carves this behavior out from the general "file size is at leastlenbytes" contract. - Short-circuited
allocateon Unix when the file is already at leastlenbytes long. Fixes macOSfallocatespuriously returningENOSPCwhen re-callingallocate(len)on an existing file (#15). - Added
cygwinto theallocatetarget_osset so builds stop failing with a missingsys::allocatesymbol on that target (#44). - Added
redoxandcygwinto the asyncallocatefallback branch so it matches the sync variant (#43 follow-up). - Moved AIX from the async
fallocatebranch to theset_lenfallback branch, matching the sync implementation. - Trait docs now explicitly state that locks are released automatically when the owning file handle is closed (#23).
- Removed the stale trait-doc reference to non-existent cross-platform
tests in
lib.rs(#16). - Mitigated #31 (compiler warning about name collisions with upcoming
std methods): because 1.0 renames the trait methods to match std
exactly,
std::fs::File::lock/try_lock/unlock(stable in Rust 1.89+) now win via inherent dispatch andunstable_name_collisionsno longer fires for std users on recent rustc. - Gated the
lock_impl!macro with#[allow(unused_macros)]socargo clippy --no-default-features(filesystem-stats-only build) does not fail under-D warnings. - Removed dead duplicate macros
cfg_fs2_err/cfg_fs3_errfromlib.rs(unified withcfg_fs_err2/cfg_fs_err3). - Updated
html_root_urlto the 1.0.0 docs.rs path.
Dependency updates
- Bumped
windows-sysfrom 0.59 to 0.61.
Testing
- Removed all
#[bench]functions from the test harness. They measured OS syscalls (flock,LockFileEx,fallocate,statvfs) rather than fs4 code, and produced numbers dominated by the underlying filesystem. Dropping them lets the crate build and test on stable Rust (the bench harness was the only thing pinning nightly via#![cfg_attr(test, feature(test))]).rust-toolchain.tomlis nowstable. - Added regression tests:
allocate_preserves_eof_within_cluster— exercises the #13 precondition (cluster-alignedAllocationSizewith EOF inside the cluster) and asserts EOF is not moved whenallocate(len)is re-called withlen <= allocated_sizeon Windows.allocate_idempotent— exercises the #15 precondition (back-to-backallocate(len)calls on macOS) to prevent a regression in the short-circuit.- Seven
TryLockErrorunit tests covering variants,Display,std::error::Error::source,From<io::Error>,From<TryLockError> for io::Error, and round-trip preservation ofErrorKind. FsStatsgetter + derive unit tests (previously every test destructured the struct, so the four getter method bodies were never executed and showed as uncovered).
- Fixed a long-standing macOS test failure: the cross-platform
filesystem_spacetest assertedavailable_space <= free_space, which does not hold on macOS APFS. APFS counts purgeable space (snapshots, cached data reclaimable on demand) inf_bavailbut not inf_bfree, so the POSIX invariant is violated on every run. The assertion is removed and the async variant now makes a singlestatvfscall plus destructure instead of three separate calls racing with concurrent filesystem activity from other tests.
CI / tooling
- New
.github/workflows/coverage.ymlworkflow: per-OS matrix (ubuntu-latest, macos-latest, windows-latest) runningcargo tarpaulin --engine llvm --all-features --run-types tests --ignore-testson each runner and uploading per-OS reports to Codecov with per-OS flags. Each runner--exclude-filesthe other OS's sources so they do not register as 0/N uncovered lines. - The
crossjob now installs the MinGW-w64 toolchain when targeting*-pc-windows-gnu.windows-sys0.61 invokesdlltoolduring build, andubuntu-latestships only the x86_64 MinGW toolchain by default. - README install snippets updated from
fs4 = { version = "0.13", ... }tofs4 = { version = "1", ... }. - Housekeeping: added
.codecov.yml,rustfmt.toml(2-space indent, explains the session-wide reformat), and.github/workflows/loc.yml; removed the obsoletetea.yaml.
- Renamed
-
0.13.108 Mar 2025Nothing published for this version
-
0.13.023 Feb 2025Release notes
Open source →Breakage
- Make
try_lock_*returnstd::io::Result<bool>, which is compatible with the upcomingstd::fs::File::try_lock*instd.
- Make
-
0.12.030 Nov 2024Nothing published for this version
-
0.11.107 Nov 2024Nothing published for this version
-
0.11.004 Nov 2024Nothing published for this version
-
0.10.008 Oct 2024 withdrawnRelease notes
Open source →Features
- Add
*_asyncAPIs for convenience when using fs4 with async runtimes.
- Add
-
0.9.130 Jul 2024Nothing published for this version
-
0.9.027 Jul 2024 withdrawnNothing published for this version
-
0.8.411 Jun 2024Nothing published for this version
-
0.8.309 May 2024Nothing published for this version
-
0.8.202 Apr 2024Nothing published for this version
-
0.8.108 Mar 2024Nothing published for this version
-
0.8.025 Feb 2024Nothing published for this version
-
0.7.125 Feb 2024 withdrawnNothing published for this version
-
0.7.030 Sep 2023 -
0.6.603 Jul 2023Nothing published for this version
-
0.6.531 May 2023Nothing published for this version
-
0.6.404 May 2023Nothing published for this version
-
0.6.331 Jan 2023Nothing published for this version
-
0.6.204 Jul 2022Nothing published for this version
-
0.6.103 Jul 2022Nothing published for this version
-
0.6.003 Jul 2022Nothing published for this version
-
0.5.502 Jul 2022 withdrawnNothing published for this version
-
0.5.431 Dec 2021Nothing published for this version
-
0.5.331 Dec 2021Nothing published for this version
-
0.5.231 Dec 2021Nothing published for this version
-
0.5.131 Dec 2021Nothing published for this version
-
0.5.031 Dec 2021Nothing published for this version