Rust でフォルダ内にあるすべてのファイル名を取得するのが遅い（それほど遅くない）

##どのくらい遅いか
フォルダ内のファイル名を`Vec`にまとめておきたい場面がありました。~~Rust で普通に実装すると下記のようになるでしょうか。ファイル数が少ないときは問題ないですが、私の環境では1万ファイル程度ある場合に **5~10sec** も掛かってしまいました。~~
下記は:x:ダメな例です。（コメントで教えて頂きました）

```rust:Rust（ダメ）
use std::fs;
use std::io;
use std::path::Path;

fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> {
    Ok(fs::read_dir(path)?
        .filter_map(|entry| entry.ok())
        .map(|entry| entry.path())
        .filter(|path| path.is_file())
        .filter_map(|path| Some(path.file_name()?.to_str()?.into()))
        .collect())
}
```
これ↓が:o:良いコード

```rust:Rust（良い）
fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> {
    Ok(fs::read_dir(path)?
        .filter_map(|entry| {
            let entry = entry.ok()?;
            if entry.file_type().ok()?.is_file() {
                Some(entry.file_name().to_string_lossy().into_owned())
            } else {
                None
            }
        })
        .collect())
}
```

~~同じような処理を Node で実行すると **100msec** 台で終了します。~~
JavaScript (Node) では↓のようになる。

```javascript:JavaScript
const fs = require("fs");

function readdir(path) {
    return fs.readdirSync(path, { withFileTypes: true })
        .filter(e => e.isFile())
        .map(e => e.name);
}

```

##なぜ遅い？
ソースを覗いてみましょう。
**Rust:** [https://github.com/rust-lang/rust/blob/stable/src/libstd/sys/windows/fs.rs#L91](https://github.com/rust-lang/rust/blob/stable/src/libstd/sys/windows/fs.rs#L91)
**Node:** [https://github.com/nodejs/node/blob/v12.x/deps/uv/src/win/fs.c#L1588](https://github.com/nodejs/node/blob/v12.x/deps/uv/src/win/fs.c#L1588)

~~Windows の場合、どちらも`FindFirstFileW`、`FindNextFileW`関数を使用しています。
Rust は`ReadDir`のイテレータを進めるたびに`WIN32_FIND_DATA`のメモリ領域を確保して`FindNextFileW`を呼び出しています。一方で Node は`fs__readdir`関数の実行時に Array にすべてのファイル情報を格納しているようです。~~
なぜ Rust はこのような実装になっているのでしょうか？マルチスレッドを意識してるのかな？

遅い原因は以下の通り。私のコードが悪かったのでした。ちゃんちゃん。
> fs::read_dirでせっかく得られた情報を捨てて、Path::is_fileとPath::file_nameを呼び出しています。Path::is_fileはファイルパスを使ってOSに問い合わせていますし、DirEntry::path＋Path::file_nameはフルパスを生成してからファイル名だけを切り出すという処理をしています。

## API を呼び出す実装
### Windows
いつものように`winapi`を使って書いていきます。エラー処理は`anyhow`に丸投げです。

```rust:Rust（API）
use anyhow::{anyhow, bail, Result};
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
use std::mem;
use std::path::Path;
use winapi::um::{
    fileapi::{FindClose, FindFirstFileW, FindNextFileW},
    handleapi::INVALID_HANDLE_VALUE,
    minwinbase::WIN32_FIND_DATAW,
    winnt::FILE_ATTRIBUTE_DIRECTORY,
};

#[cfg(target_os = "windows")]
fn read_dir<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
    if path.as_ref().is_file() {
        bail!("Path is not directory");
    }
    let path = path.as_ref().join("*");
    let path = path.to_str().ok_or(anyhow!("No path str"))?;

    let mut fd = unsafe { mem::MaybeUninit::<WIN32_FIND_DATAW>::zeroed().assume_init() };
    let handle = unsafe { FindFirstFileW(encode(&path).as_ptr(), &mut fd) };
    if handle == INVALID_HANDLE_VALUE {
        bail!("Invalid handle value");
    }
    let mut v = vec![];
    while unsafe { FindNextFileW(handle, &mut fd) } != 0 {
        if fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 {
            let file_name = decode(&fd.cFileName);
            v.push(file_name);
        }
    }
    unsafe { FindClose(handle) };
    Ok(v)
}

fn encode(source: &str) -> Vec<u16> {
    source.encode_utf16().chain(Some(0)).collect()
}
fn decode(source: &[u16]) -> String {
    let mut s = String::with_capacity(260);
    for c in decode_utf16(source.iter().take_while(|&c| c != &0).cloned()) {
        let c = c.unwrap_or(REPLACEMENT_CHARACTER);
        s.push(c);
    }
    s.shrink_to_fit();
    s
}
```
### Linux

```rust:Rust（API）
use anyhow::{anyhow, bail, Result};
use libc;
use std::ffi::CString;
use std::path::Path;
use std::slice;
use std::str;

#[cfg(target_os = "linux")]
fn read_dir<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
    let path = path.as_ref().to_str().ok_or(anyhow!("No path str"))?;
    let path = CString::new(path)?;
    let dir = unsafe { libc::opendir(path.as_ptr()) };
    if dir.is_null() {
        bail!("Dir in null");
    }
    let mut v = vec![];
    loop {
        let entry = unsafe { libc::readdir(dir) };
        if entry.is_null() {
            break;
        }
        let file_name = unsafe {
            let name = (*entry).d_name.as_ptr();
            let len = libc::strlen(name) as usize;
            let slice = slice::from_raw_parts(name as *const u8, len);
            str::from_utf8_unchecked(slice as &[u8]).into()
        };
        v.push(file_name);
    }
    unsafe { libc::closedir(dir) };
    Ok(v)
}
```

## ベンチマーク
単位は msec です。
### Windows
|Rust（ダメ）|Rust（良い）|Rust（API）|JavaScript|
|----------:|---------:|----------:|---------:|
|860        |53        |39         |41        |
### Linux
|Rust（ダメ）|Rust（良い）|Rust（API）|JavaScript|
|----------:|---------:|----------:|---------:|
|82         |21        |14         |33        |

やったぜ:blush: