Skip to content

Conversation

@musicinmybrain
Copy link
Contributor

Release 0.11 of etcetera requires MSRV 1.87, in which std::env::home_dir is no longer deprecated,
rust-lang/rust#137327.

Update to that MSRV and to etcetera, and drop the dependency on the home crate just as etcetera 0.11 did.

@Enselic
Copy link
Collaborator

Enselic commented Nov 3, 2025

How does std::env::home_dir() and home::home_dir() implementations differ?

@musicinmybrain
Copy link
Contributor Author

How does std::env::home_dir() and home::home_dir() implementations differ?

Good question. The PR rust-lang/rust#132515 that fixed the standard-library implementation talks about this a bit. The standard-library function uses a different API function on Windows than the home crate does, but the PR author makes the case that the results should be equivalent.

The home crate has the following.

https://github.com/rust-lang/cargo/blob/home-0.5.12/crates/home/src/lib.rs#L65-L67

pub fn home_dir() -> Option<PathBuf> {
    env::home_dir_with_env(&env::OS_ENV)
}

It turns out that, for the OS context, this ultimately ends up dispatching to crate::home_dir_inner(), immediately below:

#[cfg(windows)]
use windows::home_dir_inner;

#[cfg(unix)]
fn home_dir_inner() -> Option<PathBuf> {
    #[allow(deprecated)]
    std::env::home_dir()
}

So for non-Windows platforms, the answer is easy: home::home_dir() calls std::env::home_dir(), and there is no difference at all. For Windows, we have https://github.com/rust-lang/cargo/blob/home-0.5.12/crates/home/src/windows.rs#L1-L51:

use std::env;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
use std::ptr;
use std::slice;

use windows_sys::Win32::Foundation::S_OK;
use windows_sys::Win32::System::Com::CoTaskMemFree;
use windows_sys::Win32::UI::Shell::{FOLDERID_Profile, KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath};

pub fn home_dir_inner() -> Option<PathBuf> {
    env::var_os("USERPROFILE")
        .filter(|s| !s.is_empty())
        .map(PathBuf::from)
        .or_else(home_dir_crt)
}

#[cfg(not(target_vendor = "uwp"))]
fn home_dir_crt() -> Option<PathBuf> {
    unsafe {
        let mut path = ptr::null_mut();
        match SHGetKnownFolderPath(
            &FOLDERID_Profile,
            KF_FLAG_DONT_VERIFY as u32,
            std::ptr::null_mut(),
            &mut path,
        ) {
            S_OK => {
                let path_slice = slice::from_raw_parts(path, wcslen(path));
                let s = OsString::from_wide(&path_slice);
                CoTaskMemFree(path.cast());
                Some(PathBuf::from(s))
            }
            _ => {
                // Free any allocated memory even on failure. A null ptr is a no-op for `CoTaskMemFree`.
                CoTaskMemFree(path.cast());
                None
            }
        }
    }
}

#[cfg(target_vendor = "uwp")]
fn home_dir_crt() -> Option<PathBuf> {
    None
}

unsafe extern "C" {
    fn wcslen(buf: *const u16) -> usize;
}

Briefly, it uses the value of the USERPROFILE environment variable if set; otherwise, it calls SHGetKnownFolderPath with rfid parameter FOLDERID_Profile and dwFlags parameter KF_FLAG_DONT_VERIFY, which instructs the API not to test the existence of the folder before returning the path.


For the standard library, we have a similar dispatching scheme, https://github.com/rust-lang/rust/blob/1.91.0/library/std/src/env.rs#L643:

pub fn home_dir() -> Option<PathBuf> {
    os_imp::home_dir()
}

This ends up dispatching to home_dir functions in library/std/src/sys/pal/${OS_NAME}/os.rs. We know from looking at home::home_dir() that we only need to care about differences in the Windows implementation.

https://github.com/rust-lang/rust/blob/1.91.0/library/std/src/sys/pal/windows/os.rs#L245

pub fn home_dir() -> Option<PathBuf> {
    crate::env::var_os("USERPROFILE")
        .filter(|s| !s.is_empty())
        .map(PathBuf::from)
        .or_else(home_dir_crt)
}

So far, this is identical to the code in the home crate. The implementation of the home_dir_crt function is a bit more nuanced than the one in the home crate, https://github.com/rust-lang/rust/blob/1.91.0/library/std/src/sys/pal/windows/os.rs#L189-L247, so I’m not going to paste it inline here, but beneath all the boilerplate it’s just as advertised in the PR, a call to GetUserProfileDirectoryW.


So the very short version is just as rust-lang/rust#132515 said: on Windows, home::home_dir() calls SHGetKnownFolderPath, while std::env::home_dir() calls GetUserProfileDirectoryW, and there are no other real differences. If we assume the PR author is correct that these Windows APIs are equivalent in all cases, then there should be no difference at all between home::home_dir() and the “fixed” std::env::home_dir().

Copy link
Collaborator

@Enselic Enselic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thanks for explaining.

Release 0.11 of `etcetera` requires MSRV 1.87, in which
`std::env::home_dir` is no longer deprecated,
rust-lang/rust#137327.

Update to that MSRV and to `etcetera`, and drop the dependency on the
`home` crate just as `etcetera` 0.11 did.
@Enselic Enselic enabled auto-merge (rebase) November 3, 2025 17:57
@Enselic Enselic merged commit ce856db into sharkdp:master Nov 3, 2025
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants