Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to define generic closure?

Tags:

rust

Does Rust support closures with generic return types? For example, I want to write something like this:

let get<T: FromValue> = |s: &str| -> Option<T> { ... } 

But that syntax is clearly wrong.

What I'm trying to do

I'm working with rust-mysql-simple, and I'm writing a from_row method for my User struct, to build a user from a database row.

The library doesn't provide (as far as I can tell) a way to look up query result row values by column name. So to work around this, my method looks like (this compiles and works correctly):

fn from_row(result: &QueryResult, row: Vec<Value>) -> User {      let mut map: HashMap<_, _> = row.into_iter().enumerate().collect();      let mut get = |s: &str|  {         result.column_index(s)            .and_then(|i| map.remove(&i) )     };      User {         id: get("id").and_then(|x| from_value_opt(x).ok() )     } } 

Here, result is an object that contains information about the query's column names (used to find the column index for a column name), and the row contains the ordered values from a query result row. from_value_opt is a method provided by the library that takes a Value and returns a Result<T, MyError>. The value is coerced to the field's type.

I was trying to move the .and_then(|x| from_value_opt(x).ok() ) into the get closure just to clean up the code some. However, when I do so, the closure return type is interpreted to be the result of the first occurrence of the get call.

I rewrote the closure as a nested method that looks like:

fn get<T: FromValue>(r: &QueryResult, m: &mut HashMap<usize, Value>, s: &str)     -> Option<T> { ... } 

which also worked fine, but didn't help cutting the verbosity much.

like image 551
Jacob Brown Avatar asked Jan 15 '16 15:01

Jacob Brown


2 Answers

No, AFAIK you can't. I mean, you can define a generic closure, what you can't do is create a let binding with a generic left-hand side.

A fn get<T>, as the one you mention rewriting, undergoes monomorphisation, i.e. when compiling it, rustc generates a different version of get for every actual T that is used to call it. By the time you assign the result of that get (let a = get(...)), that result has a concrete type and size.

A let binding does not get monomorphised, so you can't have a let a<T> = ... and have a different version of a generated for you by the compiler.

What I think might enable this is the introduction of higher-kinded types, which is one of the highly desired but not yet fully fleshed out new features for Rust. They would enable you to write something like:

// does not work as of Rust 1 let a = for<T> |s: &str, t: T| {...} 

i.e. return a closure that I can later parametrize with a T (which is what you're asking for).

like image 164
Paolo Falabella Avatar answered Sep 18 '22 10:09

Paolo Falabella


The closure type is anonymous, so you won’t be able to write it down, and seems that the compiler won’t be able to infer it, so no luck.

But is there any particular reason you want to use a closure? If I understood your question correctly, you just use this function to factor out some repeating actions, and you are not actually going to pass it around. Thus, an inner fn should work just fine. The downside is that you’ll have to pass all the values that used to be automatically captured by your closure.

It would be something like that (the example is still pretty complex, so I didn’t try to compile it):

fn from_row(result: &QueryResult, row: Vec<Value>) -> User {     let mut map: HashMap<_, _> = row.into_iter().enumerate().collect();      fn get<T: FromValue>(s: &str, result: &QueryResult, map: &mut HashMap<_, _>)        -> T {         result.column_index(s)             .and_then(|i| map.remove(&i))             .and_then(|x| from_value_opt(x)).ok()     };      User {         id: get("id", &result, &mut map)     } } 
like image 34
kirelagin Avatar answered Sep 18 '22 10:09

kirelagin