Написание свойства для использования нескольких баз данных дает мне «ошибку [E0597]:` connection` не живет достаточно долго » - PullRequest
0 голосов
/ 04 февраля 2020

Моя программа должна иметь возможность считывать свои данные из нескольких баз данных (Postgres и Oracle).

Оригинальная попытка

Поэтому я подумал, что буду использовать черты, чтобы скрыть реализации детали и обобщенную c функцию для получения данных. К сожалению, мне нужна чит-функция, чтобы получить Transaction в случае Postgres backend:

trait DataSource<'a> {
    fn get_data(&mut self) -> String;
    fn transaction(&mut self) -> &mut postgres::Transaction<'a> {
        unimplemented!()
    }
}

trait BackendConnection<'a, TS>
where
    TS: DataSource<'a>,
{
    fn data_source(&'a mut self) -> TS;
}

trait BackendConfiguration<'a, TC, TS>
where
    TC: BackendConnection<'a, TS>,
    TS: DataSource<'a>,
{
    fn connect(&self) -> TC;
}

fn generate<'a, TF, TC, TS>(config: &TF)
where
    TF: BackendConfiguration<'a, TC, TS>,
    TC: BackendConnection<'a, TS> + 'a,
    TS: DataSource<'a> + 'a,
{
    let mut connection = config.connect();
    let mut source = connection.data_source();
    println!("{:?}", source.get_data());
}

// You can ignore all this, it is there just to show the reason why the lifetime is needed in `data_source(&'a mut self)` above.
mod pg {
    pub struct PgSource<'a> {transaction: postgres::Transaction<'a>}
    impl<'a> super::DataSource<'a> for PgSource<'a> {
        fn get_data(&mut self) -> String {
            let mut data = String::new();
            for row in self.transaction.query("SELECT CURRENT_TIMESTAMP", &[]).unwrap() {
                let st: std::time::SystemTime = row.get(0);
                data.push_str(&format!("{:?}\n", st));
            }
            data
        }
        fn transaction(&mut self) -> &mut postgres::Transaction<'a> {
            &mut self.transaction
        }
    }

    pub struct PgConnection {client: postgres::Client}
    impl<'a> super::BackendConnection<'a, PgSource<'a>> for PgConnection {
        fn data_source(&'a mut self) -> PgSource<'a> {
            let transaction = self.client.transaction().unwrap();
            PgSource { transaction }
        }
    }

    pub struct PgConfiguration {config: postgres::Config}
    impl PgConfiguration {
        pub fn new(params: &str) -> Self {
            let config = params.parse::<postgres::Config>().unwrap();
            Self { config }
        }
    }
    impl<'a> super::BackendConfiguration<'a, PgConnection, PgSource<'a>> for PgConfiguration {
        fn connect(&self) -> PgConnection {
            let client = self.config.connect(postgres::tls::NoTls).unwrap();
            PgConnection { client }
        }
    }
}

Но компилятор Rust не принимает это :

error[E0597]: `connection` does not live long enough
  --> src/lib.rs:22:22
   |
17 | fn generate<'a, TF, TC, TS>(config: &TF)
   |             -- lifetime `'a` defined here
...
22 |     let mut source = connection.data_source();
   |                      ^^^^^^^^^^--------------
   |                      |
   |                      borrowed value does not live long enough
   |                      argument requires that `connection` is borrowed for `'a`
23 |     println!("{:?}", source.get_data());
24 | }
   | - `connection` dropped here while still borrowed

Как я могу описать, что connection перекрывает source? Мои попытки ввести область видимости вокруг source или 'b: 'a для connection не дали положительных результатов.

Еще одна попытка с Box и связанными типами

После некоторых комментариев Омера Эрден и Корнел Я попробовал боксировать черты и используя связанные типы. Woohoow, он компилируется! :

#![feature(generic_associated_types)]
trait DataSource {
    fn get_data(&mut self) -> String;
    fn transaction(&mut self) -> postgres::Transaction<'_> { unimplemented!() }
}
trait BackendConnection {
    type Source<'a>;
    fn data_source(&mut self) -> Self::Source<'_>;
}
trait BackendConfiguration {
    type Connection;
    fn connect(&self) -> Self::Connection;
}

fn generate<TF>(config: &TF)
where
    TF: BackendConfiguration<Connection=Box<dyn BackendConnection<Source=Box<dyn DataSource>>>>
{
    let mut connection = config.connect();
    let mut source = connection.data_source();
    println!("{:?}", source.get_data());
}

// You can ignore all this, it is there just to show the reason why
// the lifetime is needed in `data_source(&'a mut self)` above.

mod pg {
    pub struct PgSource<'a> {transaction: postgres::Transaction<'a>}
    impl super::DataSource for PgSource<'_> {
        fn get_data(&mut self) -> String {
            let mut data = String::new();
            for row in self.transaction.query("SELECT CURRENT_TIMESTAMP", &[]).unwrap() {
                let st: std::time::SystemTime = row.get(0);
                data.push_str(&format!("{:?}\n", st));
            }
            data
        }
        fn transaction(&mut self) -> postgres::Transaction<'_> {
            self.transaction.transaction().unwrap()
        }
    }

    pub struct PgConnection {client: postgres::Client}
    impl super::BackendConnection for PgConnection {
        type Source<'a> = Box<PgSource<'a>>;
        fn data_source(&mut self) -> Self::Source<'_> {
            let transaction = self.client.transaction().unwrap();
            Box::new(PgSource { transaction })
        }
    }

    pub struct PgConfiguration {config: postgres::Config}
    impl PgConfiguration {
        pub fn new(params: &str) -> Self {
            let config = params.parse::<postgres::Config>().unwrap();
            Self { config }
        }
    }
    impl super::BackendConfiguration for PgConfiguration {
        type Connection = Box<PgConnection>;
        fn connect(&self) -> Self::Connection {
            let client = self.config.connect(postgres::tls::NoTls).unwrap();
            Box::new(PgConnection { client })
        }
    }
}

Но все равно не компилируется, когда я использую generi c:

fn main() {
    let cfg = pg::PgConfiguration::new("host=host.example user=myself");
    generate(&cfg);
}

Ошибка:

error[E0271]: type mismatch resolving `<pg::PgConfiguration as BackendConfiguration>::Connection == std::boxed::Box<(dyn BackendConnection<Source = std::boxed::Box<(dyn DataSource + 'static)>> + 'static)>`
  --> src/lib.rs:26:5
   |
15 | fn generate<TF>(config: &TF)
   |    --------
16 | where
17 |     TF: BackendConfiguration<Connection=Box<dyn BackendConnection<Source=Box<dyn DataSource>>>>
   |                              ----------------------------------------------------------------- required by this bound in `generate`
...
26 |     generate(&cfg);
   |     ^^^^^^^^ expected trait object `dyn BackendConnection`, found struct `pg::PgConnection`
   |
   = note: expected struct `std::boxed::Box<(dyn BackendConnection<Source = std::boxed::Box<(dyn DataSource + 'static)>> + 'static)>`
              found struct `std::boxed::Box<pg::PgConnection>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0271`.

Примечание

Если я мономорфизирую generate вручную, это работает :

fn generate_for_pg(config: &pg::PgConfiguration) {
    let mut connection = config.connect();
    let mut source = connection.data_source();
    println!("{:?}", source.get_data());
}

Но, конечно Я хочу избежать этого, потому что это создает дублирование кода (мне нужно написать generate_for_oracle).

1 Ответ

0 голосов
/ 04 февраля 2020

Ничего хорошего никогда не придет, если на &'a mut self поставить жизнь.

Когда вы обнаружите, что делаете это, вы закопались глубоко не в том месте.

  • &mut не просто изменчив, это означает эксклюзивный доступ. А в целях безопасности эта исключительность распространяется на все производные от нее.

  • При использовании общих заимствований (&) компилятор довольно гибкий и может сократить время жизни в местах, где это необходимо, поэтому Вы можете посыпать 'a повсюду, и это сработает. Но по неясным причинам безопасности &mut время жизни должно быть инвариантным, то есть абсолютно негибким. Не может быть сокращено, не может быть расширено. &mut self.transaction тогда называется повторным заимствованием и создает новый заем с новым сроком жизни.

Если у вас есть жизненный цикл с признаком DataSource<'a> и &mut делает это время жизни инвариантным, в конечном итоге это означает, что все, что касалось 'a, должно было существовать еще до того, как был создан объект, реализующий DataSource. И когда вы смешиваете это с generate<'a>, это расширяет эту область, чтобы означать, что все с этим временем жизни должно было быть создано даже до того, как был вызван genreate.

Я не могу запустить код, поэтому я не уверен, что этого достаточно, но вы, вероятно, имели в виду:

fn transaction<'tmp>(&'tmp mut self) -> &'tmp mut postgres::Transaction<'tmp>

проще записать как: (без <'a> на DataSource)

fn transaction(&mut self) -> &mut postgres::Transaction<'_>

, что должно привести к жизни из Transaction в область, начатую этим вызовом метода, что в любом случае хорошо, так как в возвращаемом типе &mut.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...