【问题标题】:Lifetime of get method in postgres Rustpostgres Rust 中 get 方法的生命周期
【发布时间】:2021-06-10 11:29:46
【问题描述】:

一些背景(可以跳过):

我是 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 特征是很尴尬的,所以我只是为 Field 实现了适当的函数,而不是实际定义特征(这一切都编译得很好)。这是 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 的文档的链接,以防万一。他们可以找到here

【问题讨论】:

    标签: rust lifetime borrow-checker


    【解决方案1】:

    更高等级的特征界限

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

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

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

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

    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。

    这个SO post 谈到了生命周期和更高级别的特征界限有何不同。

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

    【讨论】:

    • 确实可以编译,谢谢!我不会假装我理解为什么,但我会给你链接的其他文档阅读,希望它会有意义。
    • 好的,阅读了您喜欢的关于 HRTB 的 SO 帖子,我想我开始了解(某种程度上)发生了什么。
    • @JamesBurton 你能让你的代码使用这种方法吗?顺便说一句,在我阅读了我链接的每个参考文献的部分内容之前,我认为这对我来说并没有什么意义。
    • 代码可以编译,但我无法确认它 100% 能按预期工作,直到我整理出其他一些事情,但我一定会在此处留下更新。
    • 可以确认代码按要求运行。
    【解决方案2】:

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

    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 }
    }
    

    我也发誓我之前尝试过几次,但我们现在......

    【讨论】:

      猜你喜欢
      • 2013-07-03
      • 2021-03-23
      • 1970-01-01
      • 2017-05-03
      • 1970-01-01
      • 1970-01-01
      • 2015-09-24
      • 2014-12-29
      • 2014-09-10
      相关资源
      最近更新 更多