-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Open
Labels
A-diagnosticsArea: Messages for errors, warnings, and lintsArea: Messages for errors, warnings, and lintsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.fixed-by-higher-ranked-assumptionsFixed by `-Zhigher-ranked-assumptions`Fixed by `-Zhigher-ranked-assumptions`
Description
Code
use askama::Template;
use askama_web::WebTemplate;
use async_trait::async_trait;
use axum::Router;
use axum::extract::{Path, Request, State, FromRequestParts};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use snafu::prelude::*;
#[derive(Clone, Debug)]
pub struct AppState;
impl AppState {
pub async fn context(&self) -> Result<AppStateContext, AppStateError> {
// We simulate a global non-recoverable app error
if Self::random_failure() {
return Err(AppStateError::RandomFail);
}
Ok(AppStateContext {
errors: vec!(),
foo: Self::foo().to_string(),
})
}
pub fn foo() -> &'static str {
"foo"
}
// A runtime failure that affects the application globally (non-recoverable).
pub fn random_failure() -> bool {
rand::random_bool(0.5)
}
// A runtime failure which the route will decide to ignore or not.
pub async fn db_operation() -> Result<(), AppStateError> {
if Self::random_failure() {
Err(AppStateError::DBFail)
} else {
Ok(())
}
}
}
#[derive(Debug)]
pub struct AppStateContext {
pub errors: Vec<AppStateError>,
pub foo: String,
}
#[derive(Debug, Snafu)]
pub enum AppStateError {
#[snafu(display("Connection to the database failed."))]
DBFail,
#[snafu(display("Random global failure."))]
RandomFail,
#[snafu(display("Other error: {source}"))]
Other {
source: Box<dyn snafu::Error + Send + Sync + 'static>
},
}
impl IntoResponse for AppStateError {
fn into_response(self) -> Response {
let view = AppStateErrorView { error: self };
view.into_response()
}
}
/// Helper struct because Snafu and Template have conflicting derives
/// (both implement Display).
///
/// ```askama
/// ERROR:: {{ error }}
/// ```
#[derive(Debug, Template, WebTemplate)]
#[template(ext = "html", in_doc = true)]
pub struct AppStateErrorView {
pub error: AppStateError,
}
#[derive(Debug)]
pub struct AppRouteView<T> {
pub route: T,
pub state: AppStateContext,
}
impl<T: IntoAppRouteView> AppRouteView<T> {
pub async fn new(req: Request, state: AppState) -> Result<impl IntoResponse, AppStateError> {
let context = state.context().await?;
Ok(T::from_app_request(req, state, context).await?)
}
}
#[async_trait]
pub trait IntoAppRouteView {
async fn from_app_request(req: Request, state: AppState, mut context: AppStateContext) -> Result<impl IntoResponse, AppStateError>;
}
#[derive(Debug)]
pub struct IndexRouteContext {
pub id: usize,
}
#[async_trait]
impl IntoAppRouteView for IndexRouteContext {
async fn from_app_request(req: Request, state: AppState, mut context: AppStateContext) -> Result<impl IntoResponse, AppStateError> {
// Simulate an operation that fails without affecting global operations
if rand::random_bool(0.5) {
context.errors.push(AppStateError::DBFail);
}
let (mut parts, body) = req.into_parts();
// This is a route-specific operation that can fail and affects global operations
let Path(id) = Path::<usize>::from_request_parts(&mut parts, &state).await.boxed().context(OtherSnafu)?;
let _req = Request::from_parts(parts, body);
Ok(IndexRouteView {
ctx: AppRouteView {
route: IndexRouteContext {
id,
},
state: context,
}
})
}
}
/// ```askama
/// <div>Hello, user n° {{ ctx.route.id }}!</div>
/// ```
#[derive(Debug, WebTemplate, Template)]
#[template(ext = "html", in_doc = true)]
pub struct IndexRouteView {
ctx: AppRouteView<IndexRouteContext>,
}
// #[axum::debug_handler]
pub async fn index_route(State(app_state): State<AppState>, req: Request) -> Result<impl IntoResponse, AppStateError> {
Ok(AppRouteView::<IndexRouteContext>::new(req, app_state).await?)
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let app = Router::new()
.route("/{id}", get(index_route))
.with_state(AppState);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Current output
$ cargo run
Compiling axum-test-askama v0.1.0 (/home/user/Prog/angrynode/axum-test-askama)
error: lifetime bound not satisfied
--> src/main.rs:155:25
|
155 | .route("/{id}", get(index_route))
| ^^^^^^^^^^^^^^^^
|
= note: this is a known limitation that will be removed in the future (see issue #100013 <https://github.com/rust-lang/rust/issues/100013> for more information)
error: could not compile `axum-test-askama` (bin "axum-test-askama") due to 1 previous error
Desired output
???
Rationale and extra context
This problem is similar to #100013, #143971 (etc) but compared with other tickets i found the difference here is i have zero explicit lifetime in my code (before macro expansion) and the rustc error is very mysterious: which lifetime bound is not satisfied? satisfied by what?
Compiling on nightly with -Zhigher-ranked-assumptions
works just fine so maybe we don't need to improve diagnostic output on stable if stabilization for this feature is coming soon?
Other cases
Rust Version
rustc 1.89.0 (29483883e 2025-08-04)
binary: rustc
commit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2
commit-date: 2025-08-04
host: x86_64-unknown-linux-gnu
release: 1.89.0
LLVM version: 20.1.7
Anything else?
No response
Metadata
Metadata
Assignees
Labels
A-diagnosticsArea: Messages for errors, warnings, and lintsArea: Messages for errors, warnings, and lintsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.fixed-by-higher-ranked-assumptionsFixed by `-Zhigher-ranked-assumptions`Fixed by `-Zhigher-ranked-assumptions`