postgres Rust 中 get 方法的生命周期

Lifetime of get method in postgres Rust

一些背景知识(随意跳过):

我是 Rust 的新手,我来自 Haskell 背景(以防万一让您了解我可能有的任何误解)。

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

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 中实现 FunctorApplicative traits 很尴尬所以我只是为 Field 实现了适当的函数而不是实际定义 traits(这一切都编译得很好).这是 Rust 中 Field 的非常简化的实现,没有任何 FunctorApplicative 东西。

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 的文档中包含 link 以防相关。可以找到它们 here.

所以我似乎已经修复了它,尽管我仍然不确定我是否完全理解我所做的...

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

struct Field<'a, A> {
    field_parse: FieldFunction<'a, A>
}

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

    Field { field_parse: f }
}

我也发誓我之前试过几次但是我们...

更高等级的特征界限

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

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

这对我来说是新的。我尝试了您的代码,并收到了一些有趣的错误消息,指示在声明边界时使用了 for。这让我 here 进入了 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 }
}

section in the Rustnomicon 谈论 HRTB。

而这个 讨论了生命周期和更高等级的特征界限有何不同。

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