-
Notifications
You must be signed in to change notification settings - Fork 1.2k
ServiceBuilder vs Tuples for composing layers #3397
-
SummaryHi! The docs highlights top-to-bottom ordering using ServiceBuilder as preferable. It strikes me that the same is achievable by using a tuple with one call to Other than readability, is there a practical difference between the options, e.g. performance? I experimented adding ServiceBuilder: router.layer(
ServiceBuilder::new()
.layer(layerA)
.layer(layerB)
.layer(layerC)
) Equivalently, using a tuple: router.layer((layerA, layerB, layerC)) Or, equivalent, calling layer multiple times: router
.layer(layerC)
.layer(layerB)
.layer(layerA)
) axum version0.8.4 |
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment · 7 replies
-
Runtime performance after optimizations is going to be exactly the same (probably w/o optimizations too). I personally prefer the tuples because they're less verbose and don't lead to typenames going out of control (relevant in compiler error messages). They're newer though, which is one of the main reasons the docs use |
Beta Was this translation helpful? Give feedback.
All reactions
-
Yes, request extensions. Glancing at the code, it appears that there are many clones that might occur via calls to In any case, it's not worth any time to dig in. I'm not suffering, just curious. fwiw, my test looked something like this: router
.route("/foo", hanlder)
.layer(Extension(TestExtA("A".into()))) // Cloned 5 times when /foo is called
.layer(
ServiceBuilder::new()
.layer(Extension(TestExtB("B".into()))) // Cloned twice when /foo is called
.layer(...) // more middleware
.layer(Extension(TestExtC("C".into()))) // Cloned three times when /foo is called
)
.fallback_service(...)
// Not referenced anywhere anywhere else.
struct TestExtA(String);
impl Clone for TestExtA {
fn clone(&self) -> Self {
tracing::debug!("Cloning: {}", self.0);
Self(self.0.clone() + "_1")
}
}
// Not referenced anywhere anywhere else.
struct TestExtB(String);
impl Clone for TestExtB {
fn clone(&self) -> Self {
tracing::debug!("Cloning: {}", self.0);
Self(self.0.clone() + "_1")
}
}
// etc |
Beta Was this translation helpful? Give feedback.
All reactions
-
@jplatte I think I found at least a partial answer. Extensions are cloned for each
|
Beta Was this translation helpful? Give feedback.
All reactions
-
Note that when you do |
Beta Was this translation helpful? Give feedback.
All reactions
-
@mladedav thanks for the reply! I experimented with using a closure to add the extension and it didn't seem to make a difference. In fact, I think it resulted in one additional cloned. It was a When handling a call, the minimum number of clones I can achieve is two, even with the extension unused, with something like: Router::new()
.route("/handle", get(handler))
.layer( Extension(TestExtA("TWO".into())));
async fn handler() -> ApiResult<Json<SomeStruct>> {
Ok(Json(SomeStruct {}))
} But, if the extension is nested in a sub-router, the number of clones increases for each nesting. For example, the following sees the extension cloned 6 times: Router::new()
.nest(
"/nest1",
Router::new().nest(
"/nest2",
Router::new().nest(
"/nest3",
Router::new()
.route("/handle", get(handler))
.layer( Extension(TestExtA("TWO".into()))),
),
),
);
async fn handler() -> ApiResult<Json<SomeStruct>> {
Ok(Json(SomeStruct {}))
} This strikes me as odd! I wouldn't have expected nesting to make a difference in how many times an extension is cloned for each request! The backtraces all look a little different. A few full examples below, but here are the lines preceding the call to clone:
One of the backtraces for the clone call looks like:
or
Or:
|
Beta Was this translation helpful? Give feedback.
All reactions
-
There are some tests counting the number of times a state is cloned. You could add some equivalent tests for extensions. |
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
Runtime performance after optimizations is going to be exactly the same (probably w/o optimizations too). I personally prefer the tuples because they're less verbose and don't lead to typenames going out of control (relevant in compiler error messages). They're newer though, which is one of the main reasons the docs use
ServiceBuilder
.