rust - 编写一个特征来使用多个数据库后端给了我“错误[E0597]:`connection`的寿命不够长”
问题描述
我的程序需要能够从多个数据库(Postgres 和 Oracle)中读取数据。
原创尝试
所以我想我会使用特征来隐藏实现细节和通用函数来获取数据。Transaction
可悲的是,对于 Postgres 后端,我需要一个作弊功能:
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 }
}
}
}
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
和相关类型
在 Ömer Erden 和 Kornel 发表一些评论后,我尝试将特征装箱并使用关联类型。哇哦,它编译!:
#![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 })
}
}
}
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
.
解决方案
Nothing good ever comes from putting lifetime on &'a mut self
.
When you find yourself doing this, you've dug to deep in the wrong place.
&mut
isn't just mutable, it means exclusive access. And for safety this exclusivity applies to everything derived from it.With shared borrows (
&
) the compiler is pretty flexible and can shorten the lifetimes in places where this is needed, so you can sprinkle'a
all over the place and it'll work out. But for obscure safety reasons&mut
lifetimes have to be invariant, i.e. totally inflexible. Can't be shortened, can't be extended.&mut self.transaction
is then called a re-borrow, and it creates a new borrow with a new lifetime.
When you have a lifetime on a trait DataSource<'a>
, and &mut
makes this lifetime invariant, it ends up meaning that everything 'a
touches had to exist already before the object implementing DataSource
has been created. And when you mix it with generate<'a>
it expands that scope to mean everything with that lifetime had to be created even before genreate
has been called.
I can't run the code, so I'm not sure if that's enough, but you've probably meant:
fn transaction<'tmp>(&'tmp mut self) -> &'tmp mut postgres::Transaction<'tmp>
simpler written as: (with no <'a>
on DataSource
)
fn transaction(&mut self) -> &mut postgres::Transaction<'_>
which should cast lifetime of Transaction
to a scope started with that method call, which is as good as you can get anyway because of &mut
in the return type.
推荐阅读
- python - 删除 Pandas 中具有特定值的单元格的行
- .net - App Engine Flex - 指定 Dot Net 运行时版本
- javascript - 花括号内的 JSX
- algorithm - 将线条绘制转换为多边形的算法
- r - 将 geom_text 从不同的数据框标签分配给图形
- javascript - PDF 流适用于 Chrome,但不适用于 IE11
- azure - 如果不与 AAD 集成,AKS 群集是否默认不安全?
- android - 如何在 Android Studio IDE 中查看变量的类型
- c# - 合并来自多个控制器的 HttpGet 方法
- git - 如何更改 github repo 的顶级目录