安全なFFIラッパ
Rust は、外部関数インターフェース(FFI)を介した関数呼び出しを強力にサポートしています。これを使用して、ディレクトリ内のファイル名を読み取るために Cプログラムで使用する libc
関数の安全なラッパーを作成します。
以下のマニュアル ページをご覧ください。
std::ffi
モジュールも参照してください。ここには、この演習で必要な文字列型が多数掲載されています。
以下のすべての型間で変換を行います。
&str
からCString
: 末尾の\0
文字にも領域を割り当てる必要があります。CString
から*const i8
: C 関数を呼び出すためのポインタが必要です。*const i8
から&CStr
: 末尾の\0
文字を検出できるものが必要です。&CStr
から&[u8]
: バイトのスライスは「不明なデータ」用の汎用的な インターフェースです。&[u8]
から&OsStr
:&OsStr
はOsString
に変換するための中間ステップであり、OsStrExt
を使用して作成します。&OsStr
内のデータを返し、さらに再びreaddirを呼び出せるようにするためには&OsStr
内のデータをクローンする必要があります。
Nomicon にも、FFI に関する有益な章があります。
以下のコードを https://play.rust-lang.org/ にコピーし、不足している関数とメソッドを記入します。
// TODO: 実装が完了したら、これを削除します。 #![allow(unused_imports, unused_variables, dead_code)] mod ffi { use std::os::raw::{c_char, c_int}; #[cfg(not(target_os = "macos"))] use std::os::raw::{c_long, c_uchar, c_ulong, c_ushort}; // オペーク型。https://doc.rust-lang.org/nomicon/ffi.html をご覧ください。 #[repr(C)] pub struct DIR { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } // readdir(3) の Linux マニュアル ページに沿ったレイアウト。ino_t と // off_t は // /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h} の定義に従って解決されます。 #[cfg(not(target_os = "macos"))] #[repr(C)] pub struct dirent { pub d_ino: c_ulong, pub d_off: c_long, pub d_reclen: c_ushort, pub d_type: c_uchar, pub d_name: [c_char; 256], } // macOSマニュアル ページのdir(5)に沿ったレイアウト。 #[cfg(all(target_os = "macos"))] #[repr(C)] pub struct dirent { pub d_fileno: u64, pub d_seekoff: u64, pub d_reclen: u16, pub d_namlen: u16, pub d_type: u8, pub d_name: [c_char; 1024], } unsafe extern "C" { pub unsafe fn opendir(s: *const c_char) -> *mut DIR; #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] pub unsafe fn readdir(s: *mut DIR) -> *const dirent; // https://github.com/rust-lang/libc/issues/414、および macOS 版マニュアル ページのstat(2)における // _DARWIN_FEATURE_64_BIT_INODE に関するセクションをご覧ください。 // // 「これらのアップデートが利用可能になる前に存在していたプラットフォーム("Platforms that existed before these updates were available")」とは、 // Intel および PowerPC 上の macOS(iOS / wearOS などではない)を指します。 #[cfg(all(target_os = "macos", target_arch = "x86_64"))] #[link_name = "readdir$INODE64"] pub unsafe fn readdir(s: *mut DIR) -> *const dirent; pub unsafe fn closedir(s: *mut DIR) -> c_int; } } use std::ffi::{CStr, CString, OsStr, OsString}; use std::os::unix::ffi::OsStrExt; #[derive(Debug)] struct DirectoryIterator { path: CString, dir: *mut ffi::DIR, } impl DirectoryIterator { fn new(path: &str) -> Result<DirectoryIterator, String> { // opendir を呼び出し、成功した場合は Ok 値を返し、 // それ以外の場合はメッセージとともに Err を返します。 unimplemented!() } } impl Iterator for DirectoryIterator { type Item = OsString; fn next(&mut self) -> Option<OsString> { // NULL ポインタが返されるまで readdir を呼び出し続けます。 unimplemented!() } } impl Drop for DirectoryIterator { fn drop(&mut self) { // 必要に応じて closedir を呼び出します。 unimplemented!() } } fn main() -> Result<(), String> { let iter = DirectoryIterator::new(".")?; println!("files: {:#?}", iter.collect::<Vec<_>>()); Ok(()) }