Безпечна обгортка інтерфейсу зовнішньої функції (FFI)

Rust має чудову підтримку виклику функцій через інтерфейс зовнішніх функцій (FFI). Ми скористаємося цим, щоб створити безпечну оболонку для функцій libc, які ви використовуєте у C для читання імен файлів у директорії.

Ви захочете переглянути сторінки посібника:

Ви також захочете переглянути модуль std::ffi. Там ви знайдете ряд типів рядків, які вам знадобляться для вправи:

ТипиКодуванняВикористання
str і StringUTF-8Обробка тексту в Rust
CStr і CStringNUL-термінованийСпілкування з функціями C
OsStr і OsStringСпецифічні для ОССпілкування з ОС

Ви будете конвертувати між усіма цими типами:

  • &str до CString: вам потрібно виділити місце для кінцевого символу \0,
  • CString до *const i8: вам потрібен покажчик для виклику функцій C,
  • *const i8 до &CStr: вам потрібно щось, що може знайти кінцевий символ \0,
  • &CStr до &[u8]: зріз байт є універсальним інтерфейсом для "деяких невідомих даних",
  • &[u8] до &OsStr: &OsStr є кроком до OsString, використовуйте OsStrExt, щоб створити його,
  • &OsStr до OsString: вам потрібно клонувати дані в &OsStr, щоб мати можливість повернути їх і знову викликати readdir.

У 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)>,
    }

    // Розміщення відповідно до man-сторінки Linux для readdir(3), де 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],
    }

    // Розміщення відповідно до man-сторінки 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 та розділ про
        // _DARWIN_FEATURE_64_BIT_INODE у man-сторінці macOS про stat(2).
        //
        // "Платформи, які існували до виходу цих оновлень" відносяться
        // до macOS (на відміну від iOS / wearOS / тощо) на Intel і PowerPC.
        #[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> {
        // Продовжуємо викликати readdir до тих пір, поки не отримаємо назад вказівник NULL.
        unimplemented!()
    }
}

impl Drop for DirectoryIterator {
    fn drop(&mut self) {
        // Викликати closedir за необхідністю.
        unimplemented!()
    }
}

fn main() -> Result<(), String> {
    let iter = DirectoryIterator::new(".")?;
    println!("файли: {:#?}", iter.collect::<Vec<_>>());
    Ok(())
}