fs-err icon indicating copy to clipboard operation
fs-err copied to clipboard

Deny std::fs functions

Open pixelshot91 opened this issue 7 months ago • 2 comments

Your crate provide a very useful error message, and I thank you for that 👍 But I find it is easy to forget to use it and still use the original std::fs function.
I would like to ensure that I always use the fs_err replacement function when available.

I created a clippy.toml that list the functions I would like not to use

disallowed-methods = [
    # Do not use the `std::fs` method below, use their `fs_err` equivalent
    # The `std::fs` methods fail with 'No such file or directory' if the path does not exist, but do not print the problematic path
    # See: https://docs.rs/fs-err/latest/fs_err/
    "std::fs::canonicalize",
    "std::fs::copy",
    "std::fs::create_dir",
    "std::fs::create_dir_all",
    "std::fs::hard_link",
    "std::fs::metadata",
    "std::fs::read",
    "std::fs::read_dir",
    "std::fs::read_link",
    "std::fs::read_to_string",
    "std::fs::remove_dir",
    "std::fs::remove_dir_all",
    "std::fs::remove_file",
    "std::fs::rename",
    "std::fs::set_permissions",
    "std::fs::soft_link",
    "std::fs::symlink_metadata",
    "std::fs::write",
    "std::path::Path::canonicalize", # Use fs_err::canonicalize instead
]

And it works well. But it is not complete: I need to add std::fs::File::open, create, ...

It tried to disallow all std::fs::File::*, but clippy does not seem to support globing

Do you know how I can easily complete the list ? And keep it updated ? Can I add to this file to the repo so that people can easily add it to their project ?

pixelshot91 avatar May 19 '25 15:05 pixelshot91

First off, I didn't know you could do this, and love the idea. I've been wondering how to write a custom linter rule for this exact same purpose, and disallowing the methods makes more sense.

As a starting point, we could add a section to the README that suggests using disallowed-methods and documents them all. It wouldn't be automated, but it would let people know about the technique and provide a centralized source of information.

If you wanted something more automated, what if: fs-err maintained that list in it's codebase, and had a function that takes a vec and returns a diff of any methods that it knows about that weren't in the vec. Then a codebase could call that function in a test. Something like:

// pseudo rust code
let clippy = toml::Toml::from_str(include_str("../clippy.toml")).unwrap().get("disallowed-methods").unwrap();
let diff = fs_err::clippy::disallowed_diff(clippy);
assert_eq!(diff, Vec::empty())

If you missed one that fs-err knows about, then it would error and that would let you know which to add.

schneems avatar Nov 03 '25 20:11 schneems

I applied this to my repo and specified replacements for most. Commit is here: https://github.com/heroku/docker-heroku-ruby-builder/pull/92.

disallowed-methods = [
    # Use fs_errr functions, so the filename is available in the error message
    { path = "std::fs::canonicalize", replacement = "fs_err::canonicalize" },
    { path = "std::fs::copy", replacement = "fs_err::copy" },
    { path = "std::fs::create_dir", replacement = "fs_err::create_dir" },
    { path = "std::fs::create_dir_all", replacement = "fs_err::create_dir_all" },
    { path = "std::fs::exists", replacement = "fs_err::exists" },
    { path = "std::fs::hard_link", replacement = "fs_err::hard_link" },
    { path = "std::fs::metadata", replacement = "fs_err::metadata" },
    { path = "std::fs::read", replacement = "fs_err::read" },
    { path = "std::fs::read_dir", replacement = "fs_err::read_dir" },
    { path = "std::fs::read_link", replacement = "fs_err::read_link" },
    { path = "std::fs::read_to_string", replacement = "fs_err::read_to_string" },
    { path = "std::fs::remove_dir", replacement = "fs_err::remove_dir" },
    { path = "std::fs::remove_dir_all", replacement = "fs_err::remove_dir_all" },
    { path = "std::fs::remove_file", replacement = "fs_err::remove_file" },
    { path = "std::fs::rename", replacement = "fs_err::rename" },
    { path = "std::fs::set_permissions", replacement = "fs_err::set_permissions" },
    { path = "std::fs::soft_link", replacement = "fs_err::soft_link" },
    { path = "std::fs::symlink_metadata", replacement = "fs_err::symlink_metadata" },
    { path = "std::fs::write", replacement = "fs_err::write" },

    # Use fs_err::path::PathExt methods, so the filename is available in the error message
    { path = "std::path::Path::try_exists", reason = "Use fs_err::path::PathExt methods" },
    { path = "std::path::Path::metadata", reason = "Use fs_err::path::PathExt methods" },
    { path = "std::path::Path::symlink_metadata", reason = "Use fs_err::path::PathExt methods" },
    { path = "std::path::Path::canonicalize", reason = "Use fs_err::path::PathExt methods" },
    { path = "std::path::Path::read_link", reason = "Use fs_err::path::PathExt methods" },
    { path = "std::path::Path::read_dir", reason = "Use fs_err::path::PathExt methods" },
]

schneems avatar Nov 14 '25 14:11 schneems