Безопасная обёртка FFI
В Rust хорошая поддержка вызова функций через интерфейс внешних функций (FFI). Мы используем её, чтобы построить безопасную обёртку над функциями libc, которые вы бы использовали из C для чтения имён файлов в каталоге.
Вам понадобится обратиться к man-страницам:
Также стоит посмотреть модуль std::ffi. Там вы найдёте несколько строковых типов, которые нужны для упражнения:
| Типы | Кодировка | Используется |
|---|---|---|
str и String | UTF-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(())
}
Код привязок FFI обычно генерируется такими инструментами, как bindgen, а не пишется вручную, как мы делаем здесь. Однако bindgen нельзя запустить в онлайн-песочнице.