rusqlite. Структурка Connection позволяет исполнять запросы через немутабельный интерфейс:
fn execute(&self, ...) // Тут ссылка немутабельная
Но ведь в сущности состояние базы меняется.
В обертке у меня методы высокоуровневые (add_something, update_something, update_stuff, ...). Некоторые из них требуют начала транзакций, которые уже требуют мутабельную ссылку на Connection.
И вот получается, что какие-то методы обертки принимают &self, а какие-то -- &mut self. В общем криво.
Ясно, что можно заюзать RefCell, это будет работать, но я задумался... А вот какую семантику раст вкладывает в mut?
Правильно ли принимать мутабельную ссылку во все методы, которые как-то изменяют состояние базы, даже если этого не требует внутренний экземпляр Connection?
То есть:
fn update_something(&mut self, ...) {
// Тут ссылка на мутабельный
// коннекшн не требуется!
self.connection.execute(...);
}
fn update_stuff(&mut self, ...) {
// А тут нужна!
let tx = self.connection
.transaction();
...
}
Будет ли это правильно с точки зрения идеологии раста?
В тех же плюсах я бы методы, обновляющие данные в БД, делал бы неконстантными, а те, что просто читают -- константными. А как тут?
update_stuff требует мутабельную ссылку из-за внутренней реализации метода transaction у коннекшна По «идеологии» раста не подскажу, но вы, мне кажется, не о том состоянии говорите Состояние коннекшна меняется, а не базы, но по сути оба апдейта изменяют состояние базы, но с состоянием коннекшна работают по разному
Да, это в принципе понятно, почему где-то нужна мутабельная ссылка на коннекшн, а где-то -- нет. Это мутабельность языковая. А вот с точки зрения бизнес-логики и там, и там меняется база. Это логическая мутабельность получается. Обертка над базой отвечает за бизнес-логику. И вот правильно ли в решениях о "константности" методов этой обертки отталкиваться от бизнес-логики, а не от конкретной реализации внутренностей? В обычном случае так бы и поступил, но раст много где вкладывает свою идеологию. Останется ли верным и это правило?
Так опять же - &mut self это не база, а коннекшн к базе Как это вам бизнес-логику ломает
Это ломает красоту и консистентность, лишние вопросы вызывает: fn update_something() { let engine = build_engine(); engine.update_something(); } fn update_stuff() { let mut engine = build_engine(); engine.update_stuff() } Вот как бы два однородных метода используются, но сразу же у меня возникает вопрос: ну а чего один из них требует мутабельности, а второй -- нет? И это надо лезть в реализацию, смотреть, как сделано и пр. Это если не расстрельная статья, то немалый срок точно. Вызывать у читателя лишнии вопросы -- нехорошо. Вот и вопрос: правильно сделать везде &mut self в update_* или использовать RefCell?
Вы так паритесь по поводу красоты, тогда уж задумайтесь и про название методов Почему (если мутабельности не избежать) вам в названии самого метода не обозначить что это апдейт, но не такой апдейт как другой Это, выражаясь вашими формулировками, тоже статья - так методы называть
странный пример update без mut? это как... (учитывая что в функцию ничего не передается)
Пример упрощенный, аргументы там есть
С точки зрения базнес-логики это как раз два одинаковых по сути апдейта, но просто для разных сущностей. Только в одном случае надо сделать доп. действия, о которых юзеру не надо знать.
так если они оба одинаковы, то как один из них - мутабельный, а другой нет?
В одном для доп. действий надо транзакцию к бд открыть, а она требует мутабельности внутреннего экземпляра подключения.
это получается "простые" транзакции с бд делаются на стороне клиента "сложные" на стороне сервера и при том они оба идут к серверу в конце концов, и обе требуют ссылки на engine? я запутался уже)) я в архитектуре не мастак, но почему не сделать "интерфейс" операций с бд на стороне сервера и чтобы юзер дергал через него нужные операции под "защищенной" функцией или чем-то подобным?
сорри, я могу просто не понимать сам логики, то ли я туповат, то ли информации мало)) прост помочь пытаюсь)
Ладно, подробнее: trait Storage { fn update_something(&self, args: Args) -> Result<()>; fn update_other(&mut self, args: Args) -> Result<()>; } struct DbStorage { db: rusqlite::Connection, } impl Storage for DbStorage { fn update_something(&self, args: Args) -> Result<()> { self.db .execute("...", args)?; } fn update_other(&mut self, args: Args) -> Result<()> { let tx = self.db .transaction()?; tx.execute("...", args)?; tx.execute("...", args)?; tx.commit(); Ok(()) } } struct<S: Storage> Engine { storage: S, } impl Engine { fn update_something(&self, args: RawArgs) -> Result<()> { let args = convert(args); self.storage .update_something(args); } fn update_stuff(&mut self, args: RawArgs) -> Result<()> { let args = convert(args); self.storage .update_stuff(args); } } Engine содержит ещё ряд полей, но в данном контексте это неважно. Суть в следующем: - хранилище можно менять, реализация может меняться (требования к мутабельности storage, соответственно, -- тоже) - используется в клиентском коде сугубо Engine. С точки зрения пользователя разницы в обновлении something и stuff быть не должно. Отсюда не должно быть различий между интерфейсами вызовов update_* функций (ну какая разница, юзается там внутри транзакция или нет). Соответственно они все либо &self, либо &mut self. А вот как идеологически верно то? Вот в трейте что правильно будет из этого написать? Если отталкиваться от бизнес-логики, то это &mut self, и везде, где обновление идет, использовать let mut engine. Если же так не принято, и mut это плохая практика в данном случае, то внутри DbStorage надо будет db завернуть в RefCell. А снаружи везде будет &self и let engine. Так тоже работает.
"Even though we don’t mutate the connection, we take a &mut Connection so as to prevent nested transactions on the same connection. For cases where this is unacceptable, Transaction::new_unchecked is available." отсюда
так что я думаю ты прав, приходится залазить в документацию и читать почему так это не проблема раста, а крейта им стоило бы, возможно, сделать не new() для транзакций, а new_checked() и переименовать метод в transaction_checked(), но я не знаю насколько это сходится с "семантикой" самого sqlite он же специально сделан под "простые задачи", поэтому и тут тоже таким принципом руководствовались) ну и документация в расте очень приятная как для глаза, так и для обработки информации, так что это немного заняло по времени найти это
method и method_unchecked это в целом стандартный нейминг
да, согласен в std такого нет, там checked и unchecked везде
О, вот это я пролистал мимо Спасибо) Вот теперь всё сходится
Ну да, ключевое — сначала трахаемся, а только потом читаем доку 😂
Обсуждают сегодня