LINUX.ORG.RU

Rust, вложенные ссылки и все дела

 ,


1

6

Допустим, я хочу хранить данные в SQLite. Также я не хочу дёргать SQLite напрямую из main, а хочу сделать задаче-специфичную обёртку, предоставляющую методы вида «положить яблоки», «запросить количество груш», а не SQL-запросы. Потому что мне в целом не принципиально, что там под капотом и с тем же успехом вместо БД я мог бы использовать текстовый файл (просто БД будет лучше себя вести на большом объёме данных не влезающем в ОЗУ).

Ну что ж, берём SQLite:

let connection = sqlite::open("db.sqlite").unwrap();
let mut statement = connection.prepare("SELECT COUNT(*) FROM pears").unwrap();

А теперь мы хотим положить connection и statement в одну структуру FruitStorage, у которой будет метод count_pears(). И не можем. Потому что statement требует лайфтайм connection. И не позволяет их Rust класть в одну структуру.

Можно, конечно, сделать две структуры. FruitStorage содержащий connection с методом get_fruit_counter, возвращающим FruitCounter, который уже содержит в себе statement и имеет метод count_pears(), при этом имеет лайфтайм FruitStorage. Но это явное очень сильное протекание абстракции. У нас потом могут быть ещё более высокоуровневые абстракции, а деталь реализации «для работы с SQLite нужно два отдельных объекта - соединение и подготовленный запрос» будет и дальше нас приследовать.

Как такое решается?

★★★★★

Последнее исправление: KivApple (всего исправлений: 1)

А теперь мы хотим положить connection и statement в одну структуру FruitStorage, у которой будет метод count_pears(). И не можем. Потому что statement требует лайфтайм connection. И не позволяет их Rust класть в одну структуру.

Раскрой мысль. Какие проблемы, если у них у всех троих одинаковый лайфтайм?

t184256 ★★★★★
()
Ответ на: комментарий от t184256

Минимальный пример:

struct A;

struct B<'a> {
    a: &'a A
}

impl A {
    fn make_b(&self) -> B<'_> {
        B { a: self }
    }
}

struct C<'a> {
    a: A,
    b: B<'a>
}

impl<'a> C<'a> {
    fn new() -> Self {
        let a = A {};
        let b = a.make_b();
        Self {
            a,
            b
        }
    }
}

pub fn main() {
    let _c = C::new();
}

Ошибки компиляции:

  Compiling playground v0.0.1 (/playground)
error[E0515]: cannot return value referencing local variable `a`
  --> src/main.rs:22:9
   |
21 |           let b = a.make_b();
   |                   ---------- `a` is borrowed here
22 | /         Self {
23 | |             a,
24 | |             b
25 | |         }
   | |_________^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `a` because it is borrowed
  --> src/main.rs:23:13
   |
18 |   impl<'a> C<'a> {
   |        -- lifetime `'a` defined here
19 |       fn new() -> Self {
20 |           let a = A {};
   |               - binding `a` declared here
21 |           let b = a.make_b();
   |                   ---------- borrow of `a` occurs here
22 | /         Self {
23 | |             a,
   | |             ^ move out of `a` occurs here
24 | |             b
25 | |         }
   | |_________- returning this value requires that `a` is borrowed for `'a`

Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.
error: could not compile `playground` (bin "playground") due to 2 previous errors

Тип то так описать можно, а вот конструктор для него написать нет.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от unC0Rr

То что мы не видим static это не значит что его там нет. Как только мы добавим хоть одно не констатное поле в A то конструкция ломается потому что 'static пропадает.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&am...

pftBest ★★★★
()
Ответ на: комментарий от unC0Rr

Чтобы проверить, что не статик, добавил в A данные:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&am...

И...

   Compiling playground v0.0.1 (/playground)
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:28:25
   |
26 | impl<'a> C<'a> {
   |      -- lifetime `'a` defined here
27 |     fn new(param: i32) -> Self {
28 |         let a: &'a A = &A { param };
   |                -----    ^^^^^^^^^^^ creates a temporary value which is freed while still in use
   |                |
   |                type annotation requires that borrow lasts for `'a`
...
34 |     }
   |     - temporary value is freed at the end of this statement

For more information about this error, try `rustc --explain E0716`.
error: could not compile `playground` (bin "playground") due to previous error
KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Куча людей раньше использовали для такой задачи крейт owning_ref даже компилятор rustc его использовал одно время, но как оказалось есть дофига способов прострелить себе ногу в таких структурах данных. Что теперь популярно вместо owning_ref не знаю, давно не следил. Нагуглил какой-то ouroboros но хз.

Как по мне лучше уже две ссылки сохранить чем использовать self referential структуры.

pftBest ★★★★
()

Как такое решается?

Постепенным пониманием того, что в расте можно абстрагировать, а что нельзя. struct Foo<'a> означает, что структура содержит указатель на переменную, которая не может лежать в той-же структуре что и переменная с типом Foo.

Так что это не деталь реализации, а контракт «для получения данных нужно два раздельных объекта: соединение и запрос».

И все более высокоуровневые абстракции должны это учитывать.

Да, не особо удобно на первый взгляд. Но с использованием такой абстракции будет проще реализовать какое-нибудь in-memory storage, не требующее подключения, чем костылить подкапотное подключение к базе данных, использую абстракцию не предусматривающую отдельное подключение к базе данных.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Это всё здорово, но по мере роста сложности приложения можно оказаться с каким-нибудь коннекшеном к коннекшену к коннекшену. Также эта текущая абстракция будет заражать трейты и т. п.

Возможность упаковать всю логику в один объект-оболочку предоставляющий высокоуровневые методы (а под капотом там может быть что-угодно, в том числе оболочка может быть вообще трейтом с несколькими реализациями) критическа важна при разработке сложного ПО. Чтобы потом уже эти высокоуровневые блоки с собой стыковать. В том числе динамически на основе какого-нибудь скрипта или конфига.

В целом мне уже ответили про ouroboros и owning_ref, которые решают проблему.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)

Какой же дебильный язык. Для элементарных вещей приходится собирать консилиумы.

Подобные темы намекают, что нельзя решить все вопросы с памятью на стадии компиляции. Это похоже на потуги МЦСТ с vliw и диспетчеризацией команд по исполнительным блокам на этапе компиляции.

ox55ff ★★★★★
()