I am implementing a routing abstraction and having trouble expressing HRTB, lifetimes, and generics. This is my first time using HRTB, so I am probably missing something.
The Router
trait
The Router
trait is defined as follows:
pub trait Router<R> {
fn route(&self, request: &R) -> Option<String>;
}
A leaf router works with a specific request type (MyRequest<'request>
) and is defined as follows:
pub struct SubRouter {}
impl<'request> Router<MyRequest<'request>> for SubRouter {
fn route(&self, _request: &MyRequest<'request>) -> Option<String> {
None
}
}
pub struct MyRequest<'a> {
method: &'a str,
}
I have a general-purpose router that works with any "request" implementation. For example:
pub struct LoggingRouter<R> {
underlying: Box<dyn Router<R>>,
}
impl<R> Router<R> for LoggingRouter<R> {
fn route(&self, _request: &R) -> Option<String> {
println!("start");
// ...self.underlying.route(..)
None
}
}
Finally, I have a top-level SystemRouter
that works with an underlying router of a specific request type. However, I run into issues when I try to implement it.
Attempt 1: Lifetime on SystemRouter
The SystemRouter
needs the underlying router to work with a specific request type (which has a lifetime). So, I defined it as follows:
pub struct SystemRouter<'request> {
underlying: Box<dyn Router<MyRequest<'request>>>,
}
impl<'request> Router<String> for SystemRouter<'request> {
fn route(&self, request: &String) -> Option<String> {
self.underlying.route(&MyRequest {
method: request.as_str(),
})
}
}
This produces the following error:
error: lifetime may not live long enough
--> src/main.rs:52:9
|
50 | impl<'request> Router<String> for SystemRouter<'request> {
| -------- lifetime `'request` defined here
51 | fn route(&self, request: &String) -> Option<String> {
| - let's call the lifetime of this reference `'1`
52 | / self.underlying.route(&MyRequest {
53 | | method: request.as_str(),
54 | | })
| |__________^ argument requires that `'1` must outlive `'request`
I think putting a lifetime on SystemRouter
is a mistake (none of the routers have references of any lifetime).
Attempt 2: HTRB
To avoid putting any lifetime on SystemRouter
, the second attempt uses HTRB.
pub struct SystemRouter {
underlying: Box<dyn for<'request> Router<MyRequest<'request>>>,
}
impl<'request> Router<String> for SystemRouter {
fn route(&self, request: &String) -> Option<String> {
self.underlying.route(&MyRequest {
method: request.as_str(),
})
}
}
This fails with the following error:
error: implementation of `Router` is not general enough
--> src/main.rs:6:21
|
6 | underlying: Box::new(logging_router),
| ^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Router` is not general enough
|
= note: `LoggingRouter<MyRequest<'2>>` must implement `Router<MyRequest<'1>>`, for any lifetime `'1`...
= note: ...but it actually implements `Router<MyRequest<'2>>`, for some specific lifetime `'2`
The main function
In either case, this is how I am trying to compose them:
fn main() {
let logging_router = LoggingRouter {
underlying: Box::new(SubRouter {}),
};
let system_router = SystemRouter::new(Box::new(logging_router));
let result = system_router.route(&());
println!("{:?}", result);
}
I'd appreciate any pointers on how to fix this or an alternative design that allows me to keep the LoggingRouter
general (in the real system, I have several such routers).