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 3/4: времена жизни для внешних ресурсов

Инварианты внешних ресурсов часто совпадают с тем, что можно выразить правилами времён жизни.

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

// use std::marker::PhantomData;

/// Прямой FFI к библиотеке базы данных на C.
/// Мы получили этот API как есть и повлиять на него не можем.
mod ffi {
    pub type DatabaseHandle = u8; // Максимум 255 баз данных, открытых одновременно

    fn database_open(name: *const std::os::raw::c_char) -> DatabaseHandle {
        unimplemented!()
    }
    // ... и т. д.
}

struct DatabaseConnection(ffi::DatabaseHandle);

struct Transaction<'a>(&'a mut DatabaseConnection);

impl DatabaseConnection {
    fn new_transaction(&mut self) -> Transaction<'_> {
        Transaction(self)
    }
}

fn main() {}
  • Вспомните API транзакций из примера Aliasing XOR Mutability.

    Внутри типа транзакции мы хранили изменяемую ссылку на подключение к базе данных, чтобы заблокировать базу, пока транзакция активна.

    В этом примере мы хотим реализовать API Transaction поверх внешнего API, который написан не на Rust.

    Начнём с определения типа Transaction, который хранит &mut DatabaseConnection.

  • Спросите: каковы ограничения этой реализации? Считайте, что u8 реализован корректно и что у нас достаточно информации, чтобы использовать внешний API.

    Ожидаемые ответы:

    • Косвенность занимает на 7 байт больше, чем нужно, на 64-битной платформе, а также требует разыменования указателя во время выполнения.
  • Проблема: мы хотим, чтобы транзакция заимствовала подключение к базе данных, которое её создало, но не хотим, чтобы объект Transaction хранил настоящую ссылку.

  • Спросите: что произойдёт, если убрать изменяемую ссылку из Transaction, но оставить параметр времени жизни?

    Ожидаемый ответ: неиспользуемый параметр времени жизни!

  • Как и в случае с тегированием типов с предыдущих слайдов, мы можем подключить PhantomData, чтобы он захватил для нас этот неиспользуемый параметр времени жизни.

    Разница в том, что параметр времени жизни нужно будет использовать вместе с другим типом, но этот другой тип не так важен.

  • Продемонстрируйте: измените Transaction так:

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    struct Transaction<'a> {
        connection: DatabaseConnection,
        _phantom: PhantomData<&'a mut DatabaseConnection>,
    }
    }

    Обновите метод DatabaseConnection::new_transaction():

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    impl DatabaseConnection {
        fn new_transaction<'a>(&'a mut self) -> Transaction<'a> {
            Transaction { connection: DatabaseConnection(self.0), _phantom: PhantomData }
        }
    }
    }

    Это даёт подключение к базе данных, которым владеет вызывающий код и которое связано с DatabaseConnection, создавшим его, но с меньшим расходом памяти во время выполнения, чем у версии, которая хранит ссылку.

    Поскольку PhantomData — это тип нулевого размера (как () или struct MyZeroSizedType;), размер Transaction теперь такой же, как у u8.

    Реализация, которая вместо этого хранила ссылку, была размером с usize.

Для дальнейшего изучения

  • Такой способ кодирования связей между типами и значениями очень мощен в сочетании с unsafe, поскольку способы манипулирования временами жизни становятся почти произвольными. Это также опасно, но в сочетании с такими инструментами, как внешние механически проверенные доказательства, мы можем безопасно кодировать циклические и самоссылающиеся типы, описывая ожидания по времени жизни и безопасности в соответствующих типах данных.

  • В статье GhostCell (2021) и в её реализации показана именно такая работа. Хотя анализатор заимствований строг, всё же есть способы воспользоваться «запасными выходами» и затем показать, что способы их использования непротиворечивы и безопасны.