Введение
Я не верю в GUI на Rust. Тем, кто хочет писать интерфейс, а не изобретать велосипед, скажу: берите Tauri или Electron.
Сам я начинал с вопроса «на чём писать GUI?» и, недолго думая, дошёл до Slint. Если нужно только нарисовать интерфейс, Rust — выбор ещё терпимый. Но эту потребность перерастаешь быстро, а за ней встаёт россыпь задач не менее важных: где хранить состояние, как обновляться, и ещё десяток мелочей. В Tauri они решены, а на нативном десктопе не решены вовсе.
Эта библиотека — мой ответ, один из десятка возможных, на вопрос «как сохранить ширину сайдбара между запусками?». Я читал исходники Halloy, Sniffnet и проектов помельче. Каждый решает этот вопрос на месте, под свои нужды, и, на мой взгляд, кривовато. Я решил поднапрячься — то есть поднапрячь видеокарты на серверах Anthropic — и решить задачу чуть системнее.
Что это
Заголовок раздела «Что это»amethystate хранит состояние GUI-приложения на Rust — реактивное в памяти,
сохранённое на диске. Вы описываете нужное состояние одной структурой;
сохранение, реактивность и миграции — то, ради чего библиотека и существует.
Всё это держит store: файл на диске вместе с тем, что о нём знает память, — буфером записей и подписками. Store открывают при запуске, и структуры живут в нём.
#[amethystate(prefix = "network")]pub struct NetworkState { #[amestate(default = "127.0.0.1".to_string())] pub host: String,
#[amestate(default = 8080u16)] pub port: u16,}state.port().set(9090) возвращается к тому, кто уже может прочитать 9090, и к
подписчикам, которые уже об этом узнали. Диск догоняет сам.
К подписчикам port — и больше ни к кому. Подписываются на поле, на ключ карты
или на путь, но не на структуру и не на store. Запись в host не дойдёт до
того, кто слушал port: вид перерисовывается, когда изменилось то, что он
рисует, а не что-то рядом.
Что решает за вас
Заголовок раздела «Что решает за вас»Куда лечь файлу. Store, открытый по имени, ложится туда, где система держит данные приложений, с тем расширением, которое хочет его движок — то, что пишет значения на диск: redb, SQLite или текстовый документ.
Когда писать. Так же, как ОС обходится с файлом через страничный кэш: запись
ложится в память, читают её оттуда сразу, а на диск её потом переносит
сброс. Сброс идёт раз в окно, отсчитанное от первой несохранённой записи:
ползунок, протащенный по всей шкале, — сброс раз в несколько сотен миллисекунд,
а не на каждом кадре. Есть и
свой fsync — save_now: он ждёт, пока всё, что store держит, ляжет на диск.
Закрываясь, store дописывает всё, что держал.
Чем значение станет на диске. Пять движков, три ответа: redb кладёт значение в MessagePack, SQLite — в JSON, а текстовые пишут его прямо в документ, на языке этого документа. Объявление для всех пяти одно и то же, а о том, чего формат не умеет хранить, вам скажут, а не оставят выяснять потом.
Где поправить значение. rule на поле стоит на каждом входе и выходе:
значение прочитали при запуске, файл поправили руками, ваш код что-то записал.
Правило не отказывает, а поправляет, и поправку записывают обратно, так что на
экране и в файле не бывает разного. Инвариант между двумя полями держат там,
где структуру открывают, — в собственном Open.
Что делать, если структура изменилась. Форма каждой структуры записана рядом с данными. Поднялась версия — структуру проводят через объявленные вами шаги. Поля изменились, а версия та же — это дрейф: о расхождении скажут с диффом, и запуск продолжится.
Что делать, если файл правили снаружи. Текстовые движки за ним следят. Правка со стороны приходит теми же событиями, что и запись, а сохранение, встретившее её, кладёт свои записи поверх того, что в файле сейчас, а не затирает его.
Два вида состояния
Заголовок раздела «Два вида состояния»Различаются они одним — хендл поле или нет, — а хранение, схема и миграции у них общие.
- Реактивное — поле это хендл: объект, через который поле читают, пишут и на него подписываются. Дальше книга подразумевает этот вид.
- Persistent-only — обычные поля обычной структуры, сохраняются вручную. Для фреймворков с явным циклом отрисовки и для состояния, за которым некому следить. Об изменениях, сделанных где-то ещё, оно не узнаёт.
Для чего это и для чего нет
Заголовок раздела «Для чего это и для чего нет»Это для состояния, которое приложение держит о самом себе: раскладка окна, открытая панель, фильтр, список недавних, кэш между запусками. Десятки ключей — его наименьший случай, но не единственный: кэш — это тысячи, записанные пачками и читаемые сканами, и на этот конец библиотека тоже рассчитана.
Это не база данных. Ни запросов, ни индексов, ни транзакций, которые вы могли бы открыть: путь и значение — вся модель данных. Store открывайте сколько угодно — движки расходятся не в этом, а в том, что делать с двумя на одном файле: redb второй не пускает, SQLite файл удерживает, а текстовые впускают обоих и разбираются при сохранении.
Куда дальше
Заголовок раздела «Куда дальше»Быстрый старт — кратчайший путь от пустого места до работающего store.