I was reading through this topic and realized it was never fully answered:
This post is intended to show how you can get the drive letters on Windows using the windows-rs
crate.
First, we need to set up the project dependencies. We will be using the windows
and bitvec
crates to make our lives easier.
[dependencies]
bitvec = '1'
[dependencies.windows]
version = '0.57'
features = [
'Win32_Storage_FileSystem',
]
Next, we need to set up our imports from these crates. We're following the advice from the bitvec
docs to pull in the prelude, and we're bringing in the specific Windows APIs we need to access drives and errors. Our function for fetching the drive letters is going to be a HashSet<char>
since it cannot contain duplicates.
use std::{
collections::HashSet,
error::Error,
fmt::{Debug, Display, Formatter},
};
use bitvec::prelude::*;
use windows::Win32::{
Storage::FileSystem::GetLogicalDrives,
Foundation::GetLastError,
};
We grab the various std
types to implement our own error types for the function we are going to write. There are two potential failure modes for the API, since a u32
is too wide for valid drive letters (A-Z is 26 bits), it could return a value with the most-significant 6 bits set. It's also possible for the API to return 0
to indicate a Windows error.
pub enum GetLogicalDrivesError {
TooManyDrivesError,
ApiError(u32),
}
impl Display for GetLogicalDrivesError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl Debug for GetLogicalDrivesError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
GetLogicalDrivesError::TooManyDrivesError => write!(f, "TooManyDrives"),
GetLogicalDrivesError::ApiError(code) => write!(f, "ApiError({code})"),
}
}
}
impl Error for GetLogicalDrivesError {}
And finally, the function itself:
const INVALID_DRIVE_LETTER_BITMASK: u32 = 0b11111100_00000000_00000000_00000000;
/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives
pub fn get_drives() -> Result<HashSet<char>, GetLogicalDrivesError> {
let drives_bitmap = unsafe { GetLogicalDrives() };
// If the function fails, the return value is zero. To get extended error information, call GetLastError.
if drives_bitmap == 0 {
let err = unsafe { GetLastError() };
Err(GetLogicalDrivesError::ApiError(err.0))
} else if drives_bitmap & INVALID_DRIVE_LETTER_BITMASK != 0 {
Err(GetLogicalDrivesError::TooManyDrivesError)
} else {
Ok(drives_bitmap
.view_bits::<Lsb0>()
.iter()
.zip('A'..='Z')
.filter_map(|(bit, drive_letter)| {
// a bit derefs into a bool
if *bit {
Some(drive_letter)
} else {
None
}
})
.collect())
}
}
Since drive letters are enumerated by least-significant (Lsb0
, smallest, or right-most first) bits, we need to specify this detail to the bitvec
type.
Getting the actual Windows error string is left as an exercise to the user. Here is the documentation for the API, however:
To obtain an error string for system error codes, use the FormatMessage function.