首页 > 解决方案 > postgres Rust 中 get 方法的生命周期

问题描述

一些背景(随意跳过):

我对 Rust 很陌生,我来自 Haskell 背景(以防万一让您了解我可能有的任何误解)。

我正在尝试编写一个程序,给定来自数据库的大量输入,可以创建可自定义的报告。为此,我想创建一个Field可以以某种 DSL 样式组合的数据类型。在 Haskell 中,我的直觉是创建Field一个实例,Functor这样Applicative就可以编写这样的东西:

type Env = [String]
type Row = [String]

data Field a = Field
    { fieldParse :: Env -> Row -> a }

instance Functor Field where
    fmap f a = Field $
        \env row -> f $ fieldParse a env row

instance Applicative Field where
    pure = Field . const . const
    fa <*> fb = Field $
        \env row -> (fieldParse fa) env row
            $ (fieldParse fb) env row

oneField :: Field Int
oneField = pure 1

twoField :: Field Int
twoField = fmap (*2) oneField

tripleField :: Field (Int -> Int)
tripleField = pure (*3)

threeField :: Field Int
threeField = tripleField <*> oneField

实际问题:

我知道在 Rust中实现Functor和trait是很尴尬的,所以我只是实现了适当的函数而不是实际定义特征(这一切都编译得很好)。这是一个非常简化的Rust 实现,没有任何or东西。ApplicativeFieldFieldFunctorApplicative

use std::result;
use postgres::Row;
use postgres::types::FromSql;

type Env = Vec<String>;

type FieldFunction<A> = Box<dyn Fn(&Env, &Row) -> Result<A, String>>;

struct Field<A> {
    field_parse: FieldFunction<A>
}

我可以轻松地创建一个函数,它只是从输入字段中获取值并Field使用它创建一个报告:

fn field_good(input: u32) -> Field<String> {
    let f = Box::new(move |_: &Env, row: &Row| {
        Ok(row.get(input as usize))
    });

    Field { field_parse: f }
}

但是当我尝试使这个多态而不是使用时,String我得到了一些我不明白的非常奇怪的生命周期错误:

fn field_bad<'a, A: FromSql<'a>>(input: u32) -> Field<A> {
    let f = Box::new(move |_: &Env, row: &Row| {
        Ok(row.get(input as usize))
    });

    Field { field_parse: f }
}
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/test.rs:36:16
   |
36 |         Ok(row.get(input as usize))
   |                ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 35:22...
  --> src/test.rs:35:22
   |
35 |       let f = Box::new(move |_: &Env, row: &Row| {
   |  ______________________^
36 | |         Ok(row.get(input as usize))
37 | |     });
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/test.rs:36:12
   |
36 |         Ok(row.get(input as usize))
   |            ^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the function body at 34:14...
  --> src/test.rs:34:14
   |
34 | fn field_bad<'a, A: FromSql<'a>>(input: FieldId) -> Field<A> {
   |              ^^
note: ...so that the types are compatible
  --> src/test.rs:36:16
   |
36 |         Ok(row.get(input as usize))
   |                ^^^
   = note: expected `FromSql<'_>`
              found `FromSql<'a>`

任何帮助解释这个错误实际上是什么或如何解决它都将不胜感激。我包含了 Haskell 的东西,这样我的设计意图就很清楚了,这样如果问题是我使用的编程风格在 Rust 中并不真正适用,那么可以向我指出。

编辑:忘记包含文档的链接postgres::Row::get以防万一。他们可以在这里找到。

标签: rustlifetimeborrow-checker

解决方案


更高等级的特征界限

我不熟悉正在使用的 postgresql crate,因此我无法就代码所采用的方法提供任何一般性指导。

但无论如何,坚持当前实现中生命周期的直接问题,更高级别的特征界限可能是可行的。不过,我需要一个工作示例来确认这一点。

这对我来说是一个新的。我试用了您的代码,并收到了一些有趣的错误消息,表明for在声明边界时使用了。这将我带到Rust 参考。

所以我试了一下,它编译了。

fn field_bad<A>(input: u32) -> Field<A> 
where A: for<'a> FromSql<'a>
{
    let f = Box::new(move |_: &Env, row: &Row| {
        Ok(row.get(input as usize))
    });

    Field { field_parse: f }
}

Rustnomicon 中的这一部分讨论了 HRTB。

这篇SO 帖子讨论了生命周期和更高级别的特征界限如何不同。

这里发生的事情field_bad()是没有返回任何FromSql实例——直接或间接通过Field. FromSql只是Ok(T)闭包返回类型的一部分,它作为一个字段存在于Field. 所以for<..>语法允许我们提供适用于闭包在调用时返回的类型的生命周期。


推荐阅读