Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FormatInto object safety - or how can I make a collection of Token Streams? #30

Open
rrichardson opened this issue Sep 14, 2022 · 6 comments
Labels

Comments

@rrichardson
Copy link

I am creating a custom HTTP handler class from a schema specification which has about 100 routes.

Because of reasons, my main dispatcher is basically a match from a usize to an function invocation:
e.g.

match index {
     0 => {  
         let input = FromRequest::from_request(req).await;
         let res = handler_0(input).await; 
         IntoResponse::into_response(res).await
      }, 
      1  =>   {  
         let input = FromRequest::from_request(req).await;
         let res = handler_0(input).await; 
         IntoResponse::into_response(res).await
      }, 
      N => {
          ... 
      }
}

I want to create a Vec<Box<dyn FormatInto<Rust>> of handlers and then iterate through them and create each match block.
However, I can't, because format_into takes self

It would be nice if there was an alternative to format_into that didn't consume self. Or maybe there is a better overall approach?

What is the recommended way to create a vector of token streams that I can inject into here?

@udoprog
Copy link
Owner

udoprog commented Sep 15, 2022

Interesting.

Have you tried to write your code so that you use a Vec<impl FormatInto> instead? It would require uniform types, but this can be accomplished by using say enums and matching over them.

If you want to use it multiple times you could also do: Vec<impl FormatInto + Copy>. I usually find it to be easy enough to have it be Copy since it just requires that you only use references in the corresponding quote_fn!. Or even Clone.

I appreciate you reporting on your experience and please continue doing so! This might feed into future choices on API design. I can definitely see how support for trait objects might be legitimately useful.

@udoprog
Copy link
Owner

udoprog commented Sep 15, 2022

Actually looking over the docs of FromFn it seems to be missing a forwarding Clone and Copy impl. I was sure it had one but I'm clearly misremembering. That makes the pattern I described a bit harder to implement since it can only be used for types for which you impl FormatInto manually.

I'd suggest adding those impls ASAP. I'll look closer into this tomorrow.

@udoprog
Copy link
Owner

udoprog commented Sep 15, 2022

Also of you have the time: posting a representative example of how you are trying to use genco would also be helpful. I might be able to suggest a different way to write it.

Looking at this repo and how it uses genco might also be helpful!

@rrichardson
Copy link
Author

Adding the + Clone constraint to the trait object was the first thing that I tried. However, I think it'd need to be + Sized as well, otherwise the compiler wouldn't know how big to make the self to move out of. Fundamentally, I'm actually not sure it would be possible. I think making a FormatAs that works like FormatInto, but copies the internal bits instead of moving them, might be the most flexible approach.

I was able to get my code working by using a for loop calling quote_in in succession on a mut Token. It works, but it doesn't have the beautiful, functional flavor that my first attempt had. :)

Overall my experience with genco has been great. No surprises, it is well documented and does what it says.

@udoprog
Copy link
Owner

udoprog commented Sep 15, 2022

Cheers!

I was able to get my code working by using a for loop calling quote_in in succession on a mut Token. It works, but it doesn't have the beautiful, functional flavor that my first attempt had. :)

So maybe this is helpful. The typical way I'd do that pattern is instead of doing something like this:

let mut out = Tokens::new();

for thing in things {
    match thing {
        Thing::First => quote_in!(out => /* do first*/),
        Thing::Second => quote_in!(out => /* do second */),
    }
}

I'd do:

let mut out = Tokens::new();

quote_in! { out =>
    $(for thing in things join ($['\n']) => match thing {
        Thing::First => /* do first */,
        Thing::Second => /* do second */,
    })
}

And if one of them was a bit more involved, I'd break it out into a function:

fn do_third() -> impl FormatInto<Rust> {
    quote_fn! {
        /* do third */
    }
}

quote_in! { out =>
    $(for thing in things => match thing {
        Thing::First => /* do first */,
        Thing::Second => /* do second */,
        Thing::Third => $(do_third()),
    })
}

Note that due to the design of the FormatInto trait it doesn't require additional allocations to do so, since it will interact with the same Tokens collection.

I hope this helps!

@rrichardson
Copy link
Author

rrichardson commented Sep 15, 2022

I don't know what the match cases are at compile time, only after I parse them out of the schema. So I have this:

       let mut fn_matches = rust::Tokens::new();
       for (idx, op) in ops.iter().enumerate() { 
           let fun = to_snake_case(op.name.as_str());
           let input = &op.input; 
           if input == "()" {
               quote_in!{ fn_matches =>
                   $idx => { 
                       $fun(bucket, key, req)
                           .and_then($into_resp::into_response).await 
                   },$['\n']
               };
           }else {
               quote_in!{ fn_matches =>
                   $idx => { 
                       $shapes::$input::from_request(req)
                           .and_then(|req| $fun(bucket, key, req))
                           .and_then($into_resp::into_response).await 
                   },$['\n']
               }; 
           }
       }
       ```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants