Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Безопасная обёртка FFI

В Rust хорошая поддержка вызова функций через интерфейс внешних функций (FFI). Мы используем её, чтобы построить безопасную обёртку над функциями libc, которые вы бы использовали из C для чтения имён файлов в каталоге.

Вам понадобится обратиться к man-страницам:

Также стоит посмотреть модуль std::ffi. Там вы найдёте несколько строковых типов, которые нужны для упражнения:

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

Вам придётся преобразовывать между всеми этими типами:

  • &str в CString: нужно выделить место для завершающего символа \0,
  • CString в *const c_char: нужен указатель для вызова функций C,
  • *const c_char в &CStr: нужно нечто, что найдёт завершающий символ \0,
  • &CStr в &[u8]: срез байтов — универсальный интерфейс для «каких-то неизвестных данных»,
  • &[u8] в &OsStr: &OsStr — шаг к OsString, для его создания используйте OsStrExt,
  • &OsStr в OsString: нужно клонировать данные из &OsStr, чтобы вернуть их и снова вызвать readdir.

В Nomicon также есть очень полезная глава о FFI.

Скопируйте код ниже на https://play.rust-lang.org/ и заполните недостающие функции и методы:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 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-странице 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],
    }

    // Раскладка по man-странице macOS для dir(5).
    #[cfg(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-странице stat(2) для macOS.
        //
        // «Платформы, существовавшие до появления этих обновлений» относится
        // к 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 с сообщением.
        todo!()
    }
}

impl Iterator for DirectoryIterator {
    type Item = OsString;
    fn next(&mut self) -> Option<OsString> {
        // Продолжаем вызывать readdir, пока не получим нулевой указатель.
        todo!()
    }
}

impl Drop for DirectoryIterator {
    fn drop(&mut self) {
        // Вызывайте closedir по мере необходимости.
        todo!()
    }
}

fn main() -> Result<(), String> {
    let iter = DirectoryIterator::new(".")?;
    println!("files: {:#?}", iter.collect::<Vec<_>>());
    Ok(())
}
This slide and its sub-slides should take about 30 minutes.

Код привязок FFI обычно генерируется такими инструментами, как bindgen, а не пишется вручную, как мы делаем здесь. Однако bindgen нельзя запустить в онлайн-песочнице.