Узнайте, как использовать обёртки-классы для более чистых объектно-ориентированных API плагинов на Rust.
Использование классов в Rust
Классы в Plugify предоставляют безопасный и идиоматичный для Rust способ работы со сложными объектами, экспортируемыми плагинами. Вместо ручного управления сырыми указателями и вызова функций конструктора и деструктора вы можете использовать сгенерированные обёртки-структуры Rust, которые автоматически управляют ресурсами через систему владения Rust.
Когда плагин экспортирует функции, создающие и уничтожающие объекты (например, Kv1Create и Kv1Destroy), вы могли бы вызывать эти функции напрямую:
// Ручной подход — подвержен ошибкам и небезопасен
let kv = test_keyvalues::Kv1Create(&Str::from("Config"));
test_keyvalues::Kv1SetName(kv, &Str::from("ServerConfig"));
let name = test_keyvalues::Kv1GetName(kv);
test_keyvalues::Kv1Destroy(kv); // Легко забыть!
Однако у этого подхода есть несколько проблем:
Утечки ресурсов: если вы забудете вызвать Kv1Destroy(), произойдёт утечка ресурса
Использование после освобождения: вы можете случайно использовать хэндл после его уничтожения
Отсутствие типобезопасности: сырые хэндлы (usize) не дают проверки типов на этапе компиляции
Ручная очистка: вы должны самостоятельно отслеживать и уничтожать ресурсы
Неидиоматичный Rust: не используется система владения Rust
Классы решают все эти проблемы, используя владение Rust и трейт Drop:
// Подход RAII — автоматически и безопасно
let kv = test_keyvalues::KeyValues::new(&Str::from("Config")).unwrap();
kv.SetName(&Str::from("ServerConfig")).unwrap();
let name = kv.GetName().unwrap();
// Автоматически уничтожается, когда kv выходит из области видимости!
Классы в Rust используют RAII (Resource Acquisition Is Initialization) для автоматического управления ресурсами. Это обеспечивает безопасность, гарантированную на этапе компиляции.
Ресурсы автоматически уничтожаются, когда объекты выходят из области видимости:
fn process_config() {
let kv = test_keyvalues::KeyValues::new(&Str::from("ServerConfig")).unwrap();
kv.SetName(&Str::from("Production")).unwrap();
// Ресурс автоматически уничтожается, когда kv выходит из области видимости
} // Здесь вызывается трейт Drop — Kv1Destroy() выполняется автоматически
Система владения Rust предотвращает распространённые ошибки:
let kv1 = test_keyvalues::KeyValues::new(&Str::from("Config1")).unwrap();
let kv2 = kv1; // kv1 перемещён в kv2
// let name = kv1.GetName(); // ОШИБКА: kv1 был перемещён
// Правильно: заимствуйте вместо перемещения
let kv1 = test_keyvalues::KeyValues::new(&Str::from("Config1")).unwrap();
let name = kv1.GetName().unwrap(); // Заимствование
// kv1 всё ещё валиден здесь
Когда вы создаёте объект с помощью new(), структура владеет ресурсом:
let kv = test_keyvalues::KeyValues::new(&Str::from("Config")).unwrap();
// kv владеет ресурсом (ownership = Ownership::Owned)
// Drop вызовет Kv1Destroy(), когда kv выйдет из области видимости
Когда метод возвращает указатель, которым вы не владеете (помечено "owner": false в манифесте):
let parent = test_keyvalues::KeyValues::new(&Str::from("Parent")).unwrap();
let child = parent.FindKey(&Str::from("ChildKey")).unwrap();
// child заимствован (ownership = Ownership::Borrowed)
// Drop у child НЕ вызовет Kv1Destroy()
// Фактическим ресурсом child владеет parent
Важно: заимствованные объекты не должны жить дольше объекта, у которого они заимствованы:
// НЕПРАВИЛЬНО — висячая ссылка
let child = {
let parent = test_keyvalues::KeyValues::new(&Str::from("Parent")).unwrap();
parent.FindKey(&Str::from("Child")).unwrap()
}; // parent уничтожается здесь вместе с child
// child теперь указывает на освобождённую память!
// Правила заимствования Rust помогают предотвратить это на этапе компиляции
Некоторые методы забирают владение объектами (помечено "owner": true в манифесте):
let mut parent = test_keyvalues::KeyValues::new(&Str::from("Parent")).unwrap();
let child = test_keyvalues::KeyValues::new(&Str::from("Child")).unwrap();
parent.AddSubKey(child).unwrap();
// child перемещён в AddSubKey
// AddSubKey внутренне вызывает release(), чтобы передать владение parent
// переменная child больше не валидна
// НЕПРАВИЛЬНО: нельзя использовать child после перемещения
// child.SetName(&Str::from("NewName")); // ОШИБКА: значение использовано после перемещения
Система типов Rust обеспечивает потокобезопасность:
use std::sync::{Arc, Mutex};
// По умолчанию не потокобезопасно
let kv = test_keyvalues::KeyValues::new(&Str::from("Config")).unwrap();
// Сделаем потокобезопасным
let kv = Arc::new(Mutex::new(
test_keyvalues::KeyValues::new(&Str::from("Config")).unwrap()
));
// Клонируем Arc для потоков
let kv_clone = Arc::clone(&kv);
std::thread::spawn(move || {
let kv = kv_clone.lock().unwrap();
kv.SetName(&Str::from("ThreadModified")).unwrap();
});
use plugify::*;
fn on_plugin_start() {
// Создаём объект во владении
let mut root_config = test_keyvalues::KeyValues::new(&Str::from("ServerConfig")).unwrap();
root_config.SetName(&Str::from("Production")).unwrap();
// Создаём подключи и передаём владение
let database = test_keyvalues::KeyValues::new(&Str::from("Database")).unwrap();
database.SetName(&Str::from("PostgreSQL")).unwrap();
root_config.AddSubKey(database).unwrap();
// database больше не валиден — владение передано
let caching = test_keyvalues::KeyValues::new(&Str::from("Caching")).unwrap();
caching.SetName(&Str::from("Redis")).unwrap();
root_config.AddSubKey(caching).unwrap();
// Find возвращает заимствованную ссылку
let db_config = root_config.FindKey(&Str::from("Database")).unwrap();
if db_config.is_valid() {
println!("Database: {}", db_config.GetName().unwrap());
// db_config заимствован — им по-прежнему владеет root_config
}
// root_config автоматически уничтожается по завершении функции
}
register_plugin!(
start: on_plugin_start
);
Проверяйте валидность хэндлов: особенно у заимствованных объектов
let child = parent.FindKey(&Str::from("Child")).unwrap();
if child.is_valid() {
child.SetName(&Str::from("NewName")).unwrap();
}
Не смешивайте модели владения: придерживайтесь системы владения Rust
// Хорошо
let kv = KeyValues::new(&Str::from("Config")).unwrap();
// Избегайте без необходимости
let mut kv = KeyValues::new(&Str::from("Config")).unwrap();
let raw = kv.release();
unsafe { KeyValues::from_raw(raw, Ownership::Owned) };
Причина: заимствованная ссылка живёт дольше владельца.
Решение: убедитесь, что заимствованные значения не живут дольше своих владельцев:
let db_config = {
let parent = KeyValues::new(&Str::from("Parent")).unwrap();
parent.FindKey(&Str::from("Database")).unwrap() // ОШИБКА: parent уничтожается здесь
};
// Правильная версия
let parent = KeyValues::new(&Str::from("Parent")).unwrap();
let db_config = parent.FindKey(&Str::from("Database")).unwrap();
// Используйте db_config, пока parent ещё жив
Обёртки-классы Rust используют систему владения языка, чтобы обеспечить самый безопасный и надёжный способ работы с классами Plugify. Borrow checker гарантирует безопасность памяти на этапе компиляции, предотвращая целые классы ошибок времени выполнения. Следуя принципам владения Rust и рекомендациям из этого руководства, вы сможете уверенно создавать надёжные плагины.