Использование анализатора заимствований для обеспечения инвариантов
Анализатор заимствований, добавленный для контроля владения памятью, может моделировать и другие задачи и предотвращать некорректное использование API.
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// Двери можно открывать и закрывать, и для запирания или отпирания нужен правильный ключ.
/// Смоделировано с помощью неизменяемой ссылки на ключ и владения дверью.
pub struct DoorKey {
pub key_shape: u32,
}
pub struct LockedDoor {
lock_shape: u32,
}
pub struct OpenDoor {
lock_shape: u32,
}
fn open_door(key: &DoorKey, door: LockedDoor) -> Result<OpenDoor, LockedDoor> {
if door.lock_shape == key.key_shape {
Ok(OpenDoor { lock_shape: door.lock_shape })
} else {
Err(door)
}
}
fn close_door(key: &DoorKey, door: OpenDoor) -> Result<LockedDoor, OpenDoor> {
if door.lock_shape == key.key_shape {
Ok(LockedDoor { lock_shape: door.lock_shape })
} else {
Err(door)
}
}
fn main() {
let key = DoorKey { key_shape: 7 };
let closed_door = LockedDoor { lock_shape: 7 };
let opened_door = open_door(&key, closed_door);
if let Ok(opened_door) = opened_door {
println!("Opened the door with key shape '{}'", key.key_shape);
} else {
eprintln!(
"Door wasn't opened! Your key only opens locks with shape '{}'",
key.key_shape
);
}
}
-
Мы видели, как анализатор заимствований предотвращает ошибки безопасности памяти (использование после освобождения, гонки данных).
-
Мы также уже использовали типы для формирования и ограничения API с помощью паттерна Typestate.
-
Возможности языка часто вводятся с конкретной целью.
Со временем пользователи могут находить способы использовать возможность, которые не предполагались при её введении.
Java 5 ввела обобщения в 2004 году с основной заявленной целью — обеспечить типобезопасные коллекции.
Сначала внедрение шло медленно, но некоторые новые проекты начали проектировать свои API вокруг обобщений с самого начала.
С тех пор пользователи и разработчики языка расширили использование обобщений на другие области проектирования типобезопасных API:
- Информацию о классе можно сохранить с помощью
Class<T>в Java илиTypeToken<T>из Guava. - Паттерн Builder можно реализовать с помощью рекурсивных обобщений. Здесь мы хотим сделать нечто похожее: хотя анализатор заимствований был введён для предотвращения использования после освобождения и гонок данных, мы будем рассматривать его как ещё один инструмент проектирования API. Его можно использовать для моделирования свойств программы, которые не имеют отношения к предотвращению ошибок безопасности памяти.
- Информацию о классе можно сохранить с помощью
-
Чтобы использовать анализатор заимствований как инструмент решения задач, нужно «забыть», что его изначальная цель — предотвращать изменяемые псевдонимы в контексте использования после освобождения и гонок данных.
Представьте, что работаем в ситуациях, где правила те же, но смысл немного другой.
-
В этом примере владение и заимствование используются для моделирования состояния физической двери.
open_doorпотребляетLockedDoorи возвращает новое значениеOpenDoor. Прежнее значениеLockedDoorстановится недоступно.Если используется неправильный ключ, дверь остаётся запертой. Она возвращается как вариант
ErrуResult.Попытка использовать дверь, которую уже открыли, — ошибка времени компиляции.
-
Аналогично,
lock_doorпотребляетOpenDoor, поэтому закрыть дверь дважды невозможно — это проверяется на этапе компиляции. -
Правила анализатора заимствований существуют для предотвращения ошибок безопасности памяти, но лежащая в их основе логическая система не «знает», что такое память.
Анализатор заимствований лишь обеспечивает соблюдение определённого набора правил о том, в каком порядке пользователь может выполнять операции.
Это лишь один из случаев, когда мы опираемся на правила анализатора заимствований, чтобы проектировать API, которые сложнее или невозможно использовать неправильно.