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

Idea: Box services internally _and_ store the complete type #393

Closed
wants to merge 5 commits into from

Conversation

davidpdrsn
Copy link
Member

@davidpdrsn davidpdrsn commented Oct 18, 2021

Note this branch currently includes #385. That makes the diff larger than it actually is.

All compile issues we've had comes from building very large types and rust not being very good at evaluating trait bounds on them. A common workaround is to use boxing to erase the types. People have suggested that axum internally box all routes. I've been reluctant to do that because I wanted to keep as much type information as possible so we could use it to do cool stuff, like generating OpenAPI specs purely based on the types.

Today I had an idea though, what if we both boxed all services and kept the type information? For example Route could be defined as:

pub struct Route<S, ReqBody, E, F> {
    // the route's path (this branch is using the pre matchit router)
    pub(crate) pattern: PathPattern,
    // the boxed service behind this route
    pub(crate) svc: CloneBoxService<Request<ReqBody>, Response<BoxBody>, E>,
    // keep the type of the original service so we can use it in trait bounds
    pub(crate) svc_ty: PhantomData<fn() -> S>,
    // the fallback route if this doesn't match
    pub(crate) fallback: F,
}

Service can then be implemented from these trait bounds:

impl<S, F, B, E> Service<Request<B>> for Route<S, B, E, F>
where
    F: Service<Request<B>, Response = Response<BoxBody>, Error = E> + Clone,
    B: Send + Sync + 'static,
{
    // ...
}

Notice here that there is no bounds on S. Thats because Route.svc is already a Service. No new bounds necessary.

Router::boxed can be defined like it used to be:

pub fn boxed<ReqBody, ResBody>(self) -> Router<BoxRoute<ReqBody, S::Error>>
where
    S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + Sync + 'static,
    S::Error: Into<crate::BoxError> + Send,
    S::Future: Send,
    ReqBody: Send + 'static,
    ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
    ResBody::Error: Into<crate::BoxError>,
{
    self.layer(BoxRoute::<ReqBody, S::Error>::layer())
}

With these changes code like this:

let app: Router<BoxRoute> = Router::new()
    .route("/list", get(handle))
    .route("/home", get(handle))
    .route("/create", post(handle))
    .route("/update/:id", post(handle))
    .route("/delete", post(handle))
    .route("/detail", get(handle))
    .route("/earnings/create", post(handle))
    .route("/earnings/delete", post(handle))
    .route("/earnings/find", get(handle))
    .boxed();

compiles quickly. Previously it would take ages.

I think this approach is quite interesting and definitely something I'll look more into. Some todos

@davidpdrsn davidpdrsn added this to the 0.3 milestone Oct 19, 2021
@davidpdrsn davidpdrsn modified the milestones: 0.3, 0.4 Oct 24, 2021
@davidpdrsn
Copy link
Member Author

Superseded by #401

@davidpdrsn davidpdrsn closed this Oct 24, 2021
@davidpdrsn davidpdrsn deleted the box-all-the-things branch October 24, 2021 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant