PhantomData 4/4: OwnedFd и BorrowedFd
BorrowedFd — наглядный пример использования PhantomData.
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
use std::marker::PhantomData;
use std::os::raw::c_int;
mod libc_ffi {
use std::os::raw::{c_char, c_int};
pub unsafe fn open(path: *const c_char, oflag: c_int) -> c_int {
3
}
pub unsafe fn close(fd: c_int) {}
}
struct OwnedFd {
fd: c_int,
}
impl OwnedFd {
fn try_from_fd(fd: c_int) -> Option<Self> {
if fd < 0 {
return None;
}
Some(OwnedFd { fd })
}
fn as_fd<'a>(&'a self) -> BorrowedFd<'a> {
BorrowedFd { fd: self.fd, _phantom: PhantomData }
}
}
impl Drop for OwnedFd {
fn drop(&mut self) {
unsafe { libc_ffi::close(self.fd) };
}
}
struct BorrowedFd<'a> {
fd: c_int,
_phantom: PhantomData<&'a ()>,
}
fn main() {
// Создаём файл низкоуровневым системным вызовом с правами только на запись и создание.
let fd = unsafe { libc_ffi::open(c"c_str.txt".as_ptr(), 065) };
// Передаём владение целочисленным файловым дескриптором в `OwnedFd`.
// `OwnedFd::drop()` закрывает файловый дескриптор.
let owned_fd =
OwnedFd::try_from_fd(fd).expect("Could not open file with syscall!");
// Создаём `BorrowedFd` из `OwnedFd`.
// `BorrowedFd::drop()` не закрывает файл, потому что не владеет им!
let borrowed_fd: BorrowedFd<'_> = owned_fd.as_fd();
// std::mem::drop(owned_fd); // ❌🔨
std::mem::drop(borrowed_fd);
let second_borrowed = owned_fd.as_fd();
// owned_fd будет освобождён здесь, и файл будет закрыт.
}
-
Файловый дескриптор представляет доступ конкретного процесса к файлу.
Напоминание: устройства и специфичные для ОС функции в Unix-подобных системах представляются как файлы.
-
OwnedFd— это обёрточный тип с владением для файлового дескриптора. Он владеет файловым дескриптором и закрывает его при освобождении.Примечание: здесь у нас собственная реализация, обратите внимание на явную реализацию
Drop.BorrowedFd— его заимствованный аналог, ему не нужно закрывать файл при освобождении.Примечание: для
BorrowedFdмы явно не реализуемDrop. -
BorrowedFdиспользует время жизни, захваченное с помощьюPhantomData, чтобы обеспечить инвариант: «если этот файловый дескриптор существует, файловый дескриптор ОС всё ещё открыт, хотя он не отвечает за его закрытие».Параметр времени жизни
BorrowedFdтребует, чтобы в программе существовало другое значение, которое живёт не меньше, чем этот конкретныйBorrowedFd, или дольше него (в данном случаеOwnedFd).Продемонстрируйте: раскомментируйте строку
std::mem::drop(owned_fd)и попробуйте скомпилировать, чтобы показать, чтоborrowed_fdопирается на время жизниowned_fd.Проектировщики API закодировали это так, что именно это другое значение поддерживает доступ к файлу открытым.
Поскольку анализатор заимствований Rust обеспечивает соблюдение связи, при которой одно значение должно жить не меньше другого, пользователям этого API не нужно самостоятельно заботиться о корректной работе с псевдонимами файловых дескрипторов и логикой их закрытия.