Some Background (feel free to skip):
I'm very new to Rust, I come from a Haskell background (just in case that gives you an idea of any misconceptions I might have).
I am trying to write a program which, given a bunch of inputs from a database, can create customisable reports. To do this I wanted to create a Field
datatype which is composable in a sort of DSL style. In Haskell my intuition would be to make Field
an instance of Functor
and Applicative
so that writing things like this would be possible:
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
Actual Question:
I know that it's quite awkward to implement Functor
and Applicative
traits in Rust so I just implemented the appropriate functions for Field
rather than actually defining traits (this all compiled fine). Here's a very simplified implementation of Field
in Rust, without any of the Functor
or Applicative
stuff.
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>
}
I can easily create a function which simply gets the value from an input field and creates a report Field
with it:
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 }
}
But when I try to make this polymorphic rather than using String
I get some really strange lifetime errors that I just don't understand:
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>`
Any help explaining what this error is actually getting at or how to potentially fix it would be much appreciated. I included the Haskell stuff so that my design intentions are clear, that way if the problem is that I'm using a programming style that doesn't really work in Rust, then that could be pointed out to me.
EDIT:
Forgot to include a link to the docs for postgres::Row::get
in case it's relevant. They can be found here.