Callback with generic parameters

Hello,

I'd like to know how to create a method that calls a Callback with a generic parameter but without specifying the type "<T>", like axum or actixWeb do to call handlers with generic parameters.

for example, axum's Router can call a handler with a global state variable without having to specify its type :

pub async fn handler(State(AppState { database, .. }): State<AppState>) -> String {
    todo!()
}

fn main() {
...
let app = Router::new().route("/", get(handler)).app_data(global_state);
...
}

so I try to do the same thing, but I end up having to specify the type! :

use std::fmt::Debug;

#[derive(Debug)]
pub struct AppState<T>(T);

pub type CallBackFn<T> = fn(AppState<T>);

pub fn callback1<T: Debug>(state: AppState<T>) {
    println!("{:?}", state.0);
}

pub fn run_callback<T>(callback: CallBackFn<T>, value: T) {
    let app_state = AppState(value);
    callback(app_state);
}

fn main() {
    run_callback(callback1, "Hello");

    run_callback(callback1, 10);
}

how can we do it without Templates, Any, or Macros?

pub fn callback1<T: Debug>(state: AppState<T>) {
    println!("{:?}", state.0);
}

Without to write "<T: Debug>" like axum handler

Thanks in advance.

You might want to read this: Introductions - Dependency Injection like Bevy Engine from Scratch.

1 Like

I've already read this document, and if memory serves me correctly, the trick was to recreate functions dynamically with Macros, but if the code is complicated with several nested calls, we'll end up recreating the whole project in Macro version?

I think you are trying to reimplement Actix-web's Handler and FromRequest traits? I'm not sure what you want exactly from your code snippet, because with axum's State and actix-web's Data extractors you still have to define what T is.

here's an example from the axum documentation: the handler function contains no template like <T> :

use axum::{Router, routing::get, extract::State};

#[derive(Clone)]
struct AppState {}

let state = AppState {};

// create a `Router` that holds our state
let app = Router::new()
    .route("/", get(handler))
    // provide the state so the router can access it
    .with_state(state);

async fn handler( State(state): State<AppState> ) {
    // use `state`...
}

with my version of the code, I'd have to write the handler like this :

async fn handler<T>( State(state): State<AppState<T>> ) {
    // use `state`...
}

so by what magic do they manage to avoid this? is it with macros? or with dyn Any?

Your AppState and the one from the axum example are different types. Yours has a generic parameter T and the one from the axum example doesn't. Your AppState is equivalent to State in the axum example.

2 Likes

You're right, I got too carried away with the generics.

I modified the code, now it looks like what I wanted to do :

use std::fmt::Debug;

#[derive(Debug)]
pub struct AppState<T>(T);

pub type CallBackFn<T> = fn(AppState<T>);

pub fn callback1(AppState(state): AppState<u32>) {
    println!("{:?}", state);
}

pub fn callback2(AppState(state): AppState<&str>) {
    println!("{:?}", state);
}

pub fn run_callback<T>(callback: CallBackFn<T>, value: T) {
    let app_state = AppState(value);
    callback(app_state);
}

fn main() {
    run_callback(callback1, 20);
    run_callback(callback1, 10);
    run_callback(callback2, "Hello");
}

2 Likes