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

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 не нужно самостоятельно заботиться о корректной работе с псевдонимами файловых дескрипторов и логикой их закрытия.