Relationship between two generic function arguments

I am trying to create a function that takes as arguments a mutable reference to a vector and an element. The function first transforms the element and then pushes it to the vector.
The problem arises from the types. The element is a future, and transforming it creates a new type that can not be named specifically.

Here is a simplified code example:

extern crate futures;
use futures::{Future, future};

fn push_to_vec<F,T>(vec: &mut Vec<T>, elem: F) 
where
    F: Future<Item=u32, Error=()>,
{
    vec.push(elem.map(|x| x + 1));
}

fn main() {
    let mut vec = Vec::new();
    let my_fut = future::ok::<u32,()>(3);
    push_to_vec(&mut vec, my_fut);
}

Compilation error:

error[E0308]: mismatched types                                                                                                                                                                                     
 --> src/main.rs:8:14                                                                                                                                                                                              
  |                                                                                                                                                                                                                
8 |     vec.push(elem.map(|x| x + 1));                                                                                                                                                                             
  |              ^^^^^^^^^^^^^^^^^^^ expected type parameter, found struct `futures::Map`                                                                                                                          
  |                                                                                                                                                                                                                
  = note: expected type `T`                                                                                                                                                                                        
             found type `futures::Map<F, [closure@src/main.rs:8:23: 8:32]>`                

Is there anything I can do to tell the compiler to pick the right type for the contents of the Vector argument? (type T)

My current workaround for this problem is not using functions. This way I don't have to specify types. I inlined much of my logic code into one big function. It is still manageable, but it gets more and more difficult to add code.

I know that I can put some things into boxes, but I don't feel comfortable using this tradeoff when it seems like I don't really need a Box abstraction here. The Vector contents should have exactly the same type all the time.

Any ideas are appreciated!
real.

Type parameter means some type that is chosen by the caller of the function and you don't control what exact type someone else may choose other than the bounds you've specified.

You're getting an error because you've said caller can choose any type as long as it's the Map you want, not the T someone else wants.

You can use dynamic dispatch, Box<dyn Future> as the type in the vec instead of generics.

Hi @Kornel, thanks for your response!
What seems strange to me is that the following will work:

fn main() {
    let mut vec = Vec::new();
    let my_fut = future::ok::<u32,()>(3);
    vec.push(my_fut.map(|x| x + 1));
}

Therefore, I can not understand where is the limitation coming from. It seems like the compiler can figure out things when not using a function.

The generics are not a wildcard for the compiler to guess, but a contract between author of the function and user of the function.

You're expecting that there is some valid solution to the wildcard types and compiler to use that one. That is true for type inference where there are specific, but unwritten types to guess.

But generics by design are saying your function is universal and always valid for all types it allows.
If you say it takes any T, without any qualifiers, then your function must work literally for every type that can possibly exist in Rust: (), array of seven strings, tuple of references to a function — anything you can imagine, and the caller of the function, not function body, chooses which type it might be.

4 Likes

As for solutions to your problem: Your function doesn't want any T, but a very specific one that you can't name. Rust just doesn't have a syntax for naming the exact type of your vec.

Possible workarounds:

  • Type inference as you've figured out, but that limits you to places where inference is allowed, so structs and function arguments are out. impl Trait gives inference for return types, but that isn't enough in your case.

  • Write the full type the hard way. If you use a bare function, not a closure, you'll get an ugly complex, but real nameable type for the Map combinator. This is sometimes a workable solution, especially if you can hide the ugly type inside a newtype/struct you return. I've seen it used for iterators. I'm not sure about futures.

  • Use dynamic dispatch. Box<dyn Trait> hides the actual type and gives you a real type and object that you can work with. It does add some runtime overhead, but for futures that may be fine, and it's extra flexibility may be what you really need, since a Vec of boxed futures can hold any async operation, not just many copies of one identical operation.

4 Likes

I really appreciate the detailed explanation!
I currently use your first proposed solution (Type inference, avoiding function signatures). If my code gets too difficult to maintain without functions I think that I will go with the Box<dyn Trait>.

I’d add a couple of other options:

  1. Define your own type that implements Future, but encapsulates the behavior you want. This is really no different than the existing combinators provided by the futures lib.
  2. Perhaps use macros instead of functions to package up repetitive code that can take advantage of “inline” type inference, rather than using function boundaries.
2 Likes

@vitalyd: I didn't think about macros, this could be a good solution! Thanks!

IDK but maybe OP was looking for something simpler...

extern crate futures;
use futures::{Future, future, Async};

fn push_to_vec<F, T>(vec: &mut Vec<T>, mut elem: F) 
where
    F: Future<Item=T, Error=()>,
{
    match elem.poll().unwrap() {
        Async::Ready(a) => vec.push(a),
        _ => ()
    };
}

fn main() {
    let mut vec = Vec::new();
    let my_fut = future::ok::<i32,()>(3);
    push_to_vec(&mut vec, my_fut);
}

Note that I remove the + 1 that create other (related?) problems