Rust模板方法模式实现:PostgreSQL读取的生命周期问题
Rust模板方法模式下PostgreSQL读取的生命周期问题
我们在Rust中实现模板方法模式:库定义Command trait作为扩展接口,提供核心执行逻辑execute函数,用户通过实现该trait定制数据读取行为。
核心定义
Command trait
trait Command { fn open(&mut self, file: &str); fn read(&mut self, buf: &mut [u8]) -> bool; fn close(&mut self); }
库提供的核心执行函数
fn execute<C: Command>(mut command: C, path: &str) -> String { command.open(path); let mut sink = Sink::new(); let mut buf = [0u8; 512]; while command.read(&mut buf) { sink.send(&buf); } command.close(); // 注:原代码省略sink转String的逻辑,此处保留结构 }
文件读取实现(可正常运行)
use std::fs::File; use std::io::Read; struct ReadFromFile { stream: Option<Box<dyn Read>>, } impl ReadFromFile { fn new() -> ReadFromFile { ReadFromFile { stream: None } } } impl Command for ReadFromFile { fn open(&mut self, path: &str) { self.stream = Some(Box::new(File::open(path).unwrap())); } fn close(&mut self) { self.stream = None; } fn read(&mut self, buf: &mut [u8]) -> bool { match &mut self.stream { Some(stream) => stream.read(buf).unwrap() > 0, None => false, } } }
PostgreSQL读取实现(编译报错)
尝试基于postgres crate实现数据库数据读取:
use postgres::{Client, NoTls}; use std::io::Read; struct ReadFromDatabase { client: Client, stream: Option<Box<dyn Read>>, } impl ReadFromDatabase { fn new() -> Result<ReadFromDatabase, postgres::Error> { let client = Client::connect("host=localhost user=mats", NoTls)?; Ok(ReadFromDatabase { client, stream: None, }) } } impl Command for ReadFromDatabase { fn open(&mut self, path: &str) { let stmt = format!("COPY {} TO stdout", path); self.stream = Some(Box::new(self.client.copy_out(&stmt).unwrap())); } fn close(&mut self) { self.stream = None; } fn read(&mut self, buf: &mut [u8]) -> bool { match &mut self.stream { Some(stream) => stream.read(buf).unwrap() > 0, None => false, } } }
编译错误信息
error: lifetime may not live long enough --> examples/template-method.rs:74:28 | 72 | fn open(&mut self, path: &str) { | - let's call the lifetime of this reference `'1` 73 | let stmt = format!("COPY {} TO stdout", path); 74 | self.stream = Some(Box::new(self.client.copy_out(&stmt).unwrap())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`
疑问
- 是否可以通过添加生命周期参数实现
ReadFromDatabase?如果可以,具体怎么实现? - 不修改
Commandtrait的前提下,有没有更合适的模式实现ReadFromDatabase?
解答
问题1:添加生命周期参数不可行
错误根源是copy_out返回的CopyOutReader持有对传入的&stmt的引用,而stmt是open方法内的局部变量,生命周期仅局限于方法内部。Box<dyn Read>默认要求内部类型的生命周期为'static,但stmt的生命周期'1无法满足这一要求——self.stream的生命周期远长于stmt,导致引用失效。添加生命周期参数无法解决局部变量生命周期不足的问题,因为Command trait的open方法签名不允许绑定结构体与参数的生命周期。
问题2:不修改Command trait的可行方案
方案1:预构造SQL语句(推荐)
将表名提前传入ReadFromDatabase的构造函数,预先构建COPY语句,让语句的生命周期与结构体实例一致:
use postgres::{Client, NoTls}; use std::io::Read; struct ReadFromDatabase { client: Client, stmt: String, stream: Option<Box<dyn Read>>, } impl ReadFromDatabase { fn new(table_name: &str) -> Result<ReadFromDatabase, postgres::Error> { let client = Client::connect("host=localhost user=mats", NoTls)?; let stmt = format!("COPY {} TO stdout", table_name); Ok(ReadFromDatabase { client, stmt, stream: None, }) } } impl Command for ReadFromDatabase { fn open(&mut self, _path: &str) { // 忽略path参数,使用预构造的stmt self.stream = Some(Box::new(self.client.copy_out(&self.stmt).unwrap())); } fn close(&mut self) { self.stream = None; } fn read(&mut self, buf: &mut [u8]) -> bool { match &mut self.stream { Some(stream) => stream.read(buf).unwrap() > 0, None => false, } } }
此方案逻辑简洁,完全适配现有Command trait的约束,同时避免了生命周期问题。
方案2:存储动态生成的语句到结构体
如果必须在open方法中根据path动态生成语句,可以将语句存储为结构体成员,确保其生命周期与实例一致:
use postgres::{Client, NoTls}; use std::io::Read; struct ReadFromDatabase { client: Client, stmt: Option<String>, stream: Option<Box<dyn Read>>, } impl ReadFromDatabase { fn new() -> Result<ReadFromDatabase, postgres::Error> { let client = Client::connect("host=localhost user=mats", NoTls)?; Ok(ReadFromDatabase { client, stmt: None, stream: None, }) } } impl Command for ReadFromDatabase { fn open(&mut self, path: &str) { self.stmt = Some(format!("COPY {} TO stdout", path)); self.stream = Some(Box::new( self.client.copy_out(self.stmt.as_ref().unwrap()).unwrap() )); } fn close(&mut self) { self.stream = None; self.stmt = None; } fn read(&mut self, buf: &mut [u8]) -> bool { match &mut self.stream { Some(stream) => stream.read(buf).unwrap() > 0, None => false, } } }
此方案保留了open方法动态生成语句的能力,同时通过将stmt存入结构体,确保CopyOutReader引用的语句生命周期足够长。
内容的提问来源于stack exchange,提问作者Mats Kindahl
相关产品推荐
相关产品推荐

