Skip to content

[ICE]: (unstable fingerprints in evaluate_obligation) when a lifetime error occurs in async handler returning Result<_, impl Error> (Rust 2024 edition) #163269

Open

Description

@cztomsik

rustc version

rustc 1.98.1 (48a229cea 2026-09-01)
cargo 1.98.1 (797e8a9bc 2026-08-05)
aarch64-apple-darwin

Compiler flags

--crate-type bin -C embed-bitcode=no -C debuginfo=2 -C split-debuginfo=unpacked -C incremental=[REDACTED]

(Note: some flags are provided/hidden by cargo; run with RUST_BACKTRACE=1 for the full query stack.)

Error output

The build first reports a (legitimate) lifetime error, then rustc panics with an
internal compiler error during incremental compilation:

error[E0597]: `pool` does not live long enough
   --> src/api.rs:38:21
    |
 37 | async fn get_stores(State(pool): State<SqlitePool>) -> Result<Json<Vec<Store>>, ApiError> {
    |                           ---- binding `pool` declared here
 38 |     Store::find_all(&pool).await.map(Json).map_err(Into::into)
    |     ----------------^^^^^-------------------------------------
    |     |               |
    |     |               borrowed value does not live long enough
    |     argument requires that `pool` is borrowed for `'static`
 39 | }
    | - `pool` dropped here while still borrowed
    |
note: requirement that the value outlives `'static` introduced here
   --> /Users/cztomsik/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:962:12
    |
962 |         O: [const] FnOnce(E) -> F + [const] Destruct,
    |            ^^^^^^^^^^^^^^^^^^^^^^
note: this call may capture more lifetimes than intended, because Rust 2024 has adjusted the `impl Trait` lifetime capture rules
   --> src/api.rs:38:5
    |
 38 |     Store::find_all(&pool).await.map(Json).map_err(Into::into)
    |     ^^^^^^^^^^^^^^^^^^^^^^
help: use the precise capturing `use<...>` syntax to make the captures explicit
   --> src/db.rs:15:78
    |
 15 |     pub async fn find_all(pool: &SqlitePool) -> Result<Vec<Store>, impl Error + use<>> {
    |                                                                               +++++++

error: internal compiler error: encountered incremental compilation error with evaluate_obligation(72315dfcc89c95ea-6ce2a7b955f286e6)
  |
  = note: please follow the instructions below to create a bug report with the provided information
  = note: for incremental compilation bugs, having a reproduction is vital
  = note: an ideal reproduction consists of the code before and some patch that then triggers the bug when applied and compiled again
  = note: as a workaround, you can run `cargo clean -p bo_main` or `cargo clean` to allow your project to compile

thread 'rustc' (896020) panicked at /rustc-dev/48a229ceaefd4985c50990b14116b6d856af0985/compiler/rustc_middle/src/verify_ich.rs:82:9:
Found unstable fingerprints for evaluate_obligation(72315dfcc89c95ea-6ce2a7b955f286e6): Ok(EvaluatedToOk)
stack backtrace:
   0:        0x10ae5688c - <<std[5d97c59e5e5fafcc]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[663bb9e1522ba5ef]::fmt::Display>::fmt
   1:        0x108123e94 - core[663bb9e1522ba5ef]::fmt::write
   2:        0x10ae6d5ec - <std[5d97c59e5e5fafcc]::sys::stdio::unix::Stderr as core[663bb9e1522ba5ef]::io::Write>::write_fmt
   3:        0x10ae2b1b8 - std[5d97c59e5e5fafcc]::panicking::default_hook::{closure#0}
   4:        0x10ae47d2c - std[5d97c59e5e5fafcc]::panicking::default_hook
   5:        0x10ae482f8 - std[5d97c59e5e5fafcc]::panicking::update_hook::<alloc[cc4db322bb1ac55d]::boxed::Box<rustc_driver_impl[6570ee80102c2b15]::install_ice_hook::{closure#1}>>::{closure#0}
   6:        0x10ae482f8 - std[5d97c59e5e5fafcc]::panicking::panic_with_hook
   7:        0x10ae2b23c - std[5d97c59e5e5fafcc]::panicking::panic_handler::{closure#0}
   8:        0x10ae21b1c - <std[5d97c59e5e5fafcc]::sys::backtrace::__rust_end_short_backtrace::<std[5d97c59e5e5fafcc]::panicking::panic_handler::{closure#0}, !>
   9:        0x10ae2c764 - __rustc[100742bb89c490cb]::rust_begin_unwind
  10:        0x10af12760 - core[663bb9e1522ba5ef]::panicking::panic_fmt
  11:        0x10aff3040 - rustc_middle[5cf943406889ed]::verify_ich::incremental_verify_ich_failed
  12:        0x10a20690c - rustc_middle[5cf943406889ed]::verify_ich::incremental_verify_ich::<rustc_middle[5cf943406889ed]::query::erase::ErasedData<[u8; 2usize]>>
  13:        0x10a2801b0 - rustc_query_impl[2b0e3be29859969b]::execution::try_execute_query::<rustc_middle[5cf943406889ed]::query::caches::DefaultCache<rustc_type_ir[a678fd3fb3f11]::canonical::CanonicalQueryInput<rustc_middle[5cf943406889ed]::ty::context::TyCtxt, rustc_middle[5cf943406889ed]::ty::ParamEnvAnd<rustc_middle[5cf943406889ed]::ty::predicate::Predicate>>, rustc_middle[5cf943406889ed]::query::erase::ErasedData<[u8; 2usize]>>, true>
  14:        0x10a421960 - rustc_query_impl[2b0e3be29859969b]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace
  15:        0x10abd40b8 - <rustc_infer[1800e56c0cab7b30]::infer::InferCtxt as rustc_trait_selection[dd66be20177c957b]::traits::query::evaluate_obligation::InferCtxtExt>::evaluate_obligation
  16:        0x10abd4474 - <rustc_infer[1800e56c0cab7b30]::infer::InferCtxt as rustc_trait_selection[dd66be20177c957b]::traits::query::evaluate_obligation::InferCtxtExt>::evaluate_obligation_no_overflow
  17:        0x10ab3e19c - <rustc_trait_selection[dd66be20177c957b]::traits::fulfill::FulfillProcessor>::process_trait_obligation
  18:        0x10abf214c - <rustc_trait_selection[dd66be20177c957b]::traits::fulfill::FulfillProcessor as rustc_data_structures[566e056540f4032a]::obligation_forest::ObligationProcessor>::process_obligation
  19:        0x10a961608 - <rustc_data_structures[566e056540f4032a]::obligation_forest::ObligationForest<rustc_trait_selection[dd66be20177c957b]::traits::fulfill::PendingPredicateObligation>>::process_obligations::<rustc_trait_selection[dd66be20177c957b]::traits::fulfill::FulfillProcessor>
  20:        0x1093c95e8 - <rustc_trait_selection[dd66be20177c957b]::traits::fulfill::FulfillmentContext<rustc_trait_selection[dd66be20177c957b]::traits::FulfillmentError> as rustc_infer[1800e56c0cab7b30]::traits::engine::TraitEngine<rustc_trait_selection[dd66be20177c957b]::traits::FulfillmentError>>::try_evaluate_obligations
  21:        0x109327c6c - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_argument_types
  22:        0x1092e869c - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::confirm_builtin_call
  23:        0x1092cf8fc - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_kind
  24:        0x1092e52dc - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  25:        0x109327f70 - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_argument_types
  26:        0x1092ccd9c - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_kind
  27:        0x1092e52dc - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  28:        0x1092c453c - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_kind
  29:        0x1092e52dc - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  30:        0x109298a08 - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_block::{closure#0}
  31:        0x1093253e8 - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_block
  32:        0x1092e52dc - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  33:        0x1092e3210 - <rustc_hir_typeck[ea3a3b088ed04c48]::fn_ctxt::FnCtxt>::check_return_or_body_tail
  34:        0x1093aab14 - rustc_hir_typeck[ea3a3b088ed04c48]::check::check_fn
  35:        0x10928eaa8 - rustc_hir_typeck[ea3a3b088ed04c48]::typeck_with_inspect::{closure#0}
  36:        0x1092b586c - rustc_hir_typeck[ea3a3b088ed04c48]::typeck_root
  ...

query stack during panic:

#0 [evaluate_obligation] evaluating trait selection obligation `core::option::Option<sqlx_core::pool::Pool<sqlx_sqlite::database::Sqlite>>: core::marker::Send`
#1 [typeck_root] type-checking `api::router`
... and 1 other queries... use `env RUST_BACKTRACE=1` to see the full query stack
error: could not compile `bo_main` (bin "bo_main") due to 3 previous errors

Workaround

cargo clean -p bo_main (or a full cargo clean) makes the project compile again
(the remaining E0597 is a genuine, expected user error — see below).

Reproduction steps

  1. Use the crate below (Cargo.toml + 4 source files, all inline at the bottom).
  2. cargo build — the crate does not compile: Store::find_all returns
    Result<_, impl Error> where the anonymous error captures a non-'static
    lifetime derived from &pool (Rust 2024 impl Trait capture rules). This
    produces the E0597 error above.
  3. Run cargo build again (incremental) — rustc panics with the ICE
    "Found unstable fingerprints for evaluate_obligation".

The combination that appears to trigger the ICE:

  • edition = "2024"
  • an async fn whose return type contains impl Trait with the 2024
    (all-capture) lifetime rules, producing a type/lifetime error during
    typeck, followed by a second incremental compilation.
  • The failing obligation is an auto-trait (Send) evaluation, consistent
    with async-fn desugaring (polling of async fn bodies requires Send).

Expected behavior

rustc should report the (real) lifetime error without panicking; the
evaluate_obligation fingerprint should be stable across incremental
compilations.

Crate: Cargo.toml

[package]
name = "bo_main"
version = "0.1.0"
edition = "2024"

[dependencies]
axum = { version = "0.8.9", features = ["macros"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
anyhow = "1"
serde_json = "1"
serde = { version = "1", features = ["derive"] }
sqlx = { version = "0.9.0", features = ["sqlite", "runtime-tokio"] }

Crate: src/main.rs

mod api;
mod config;
mod db;

use config::Config;

fn env_flag(name: &str) -> bool {
    std::env::var(name).is_ok_and(|v| v == "1")
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let data = std::fs::read_to_string("config.json")?;
    let config: Config = serde_json::from_str(&data)?;
    println!("config: {:?}", config);

    let pool = db::init(&config.db_path).await?;
    if env_flag("MIGRATE") {
        db::migrate(&pool).await?;
        println!("database migrations applied");
    }
    if env_flag("SEED") {
        db::seed(&pool).await?;
        println!("database seeded");
    }

    // TODO: config
    let addr = "127.0.0.1:3000";
    let listener = tokio::net::TcpListener::bind(addr).await?;
    println!("bo_admin listening on http://{}", addr);
    axum::serve(listener, api::router(pool)).await?;

    Ok(())
}

Crate: src/api.rs

use crate::db::Store;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::{Json, Router};
use sqlx::SqlitePool;

struct ApiError(anyhow::Error);

impl<E> From<E> for ApiError
where
    E: Into<anyhow::Error>,
{
    fn from(err: E) -> Self {
        Self(err.into())
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Something went wrong: {}", self.0),
        )
            .into_response()
    }
}

pub fn router(pool: SqlitePool) -> axum::Router {
    Router::new()
        .route("/stores", get(get_stores))
        .with_state(pool)
}

#[axum::debug_handler]
async fn get_stores(State(pool): State<SqlitePool>) -> Result<Json<Vec<Store>>, ApiError> {
    Store::find_all(&pool).await.map(Json).map_err(Into::into)
}

Crate: src/db.rs

use serde::Serialize;
use sqlx::Executor;
use sqlx::FromRow;
use sqlx::SqlitePool;
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
use std::error::Error;

#[derive(Debug, Clone, Serialize, FromRow)]
pub struct Store {
    pub id: Option<i64>,
    pub name: String,
    pub url: String,
}
impl Store {
    pub async fn find_all(pool: &SqlitePool) -> Result<Vec<Store>, impl Error> {
        sqlx::query_as::<_, Store>("SELECT id, name, url FROM store")
            .fetch_all(pool)
            .await
    }
}

pub async fn init(db_path: &str) -> anyhow::Result<SqlitePool> {
    // TODO: mkdir -p parent dir from db_path
    let options = SqliteConnectOptions::default()
        .filename(db_path)
        .create_if_missing(true)
        .journal_mode(SqliteJournalMode::Wal)
        .foreign_keys(true);

    let pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect_with(options)
        .await?;

    Ok(pool)
}

pub async fn migrate(pool: &SqlitePool) -> anyhow::Result<()> {
    // TODO: real migration, once we get out of the PoC phase.

    let sql = r#"
      ...
    "#;

    pool.execute(sql).await?;

    Ok(())
}

pub async fn seed(pool: &SqlitePool) -> anyhow::Result<()> {
    todo!()
}

Crate: src/config.rs

use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct Config {
    pub db_path: String,
}

Activity

  1. added
    I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    C-bugCategory: This is a bug.
    on Sep 24, 2026
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Sep 24, 2026
  3. Dnreikronos commented on Sep 26, 2026

    @Dnreikronos
    Contributor

    Hey, I spent some time on this and got it to reproduce locally, on stable 1.98.1 (same build as yours) and on nightly-2026-08-10.

    At first building the broken code twice didn't ICE for me, I just got the E0597 both times. What does trigger it is having a build that actually compiles first and then switching to the broken version. So if find_all starts out returning Result<Vec<Store>, impl Error + use<>> (or plain sqlx::Error), you build once, then change it to just impl Error and build again, and it panics on the same Option<Pool<Sqlite>>: Send query from the report. I'm guessing that's pretty much what happened on your side while editing. It also still happens without #[axum::debug_handler] btw.

    Newer nightlies don't ICE anymore, so I bisected that and it points to #160619, which turned the next solver on by default on nightly. So nightly doesn't really fix it, it just stops using the old solver in typeck, and evaluate_obligation only gets used by the old solver. To double check I built current main with -Znext-solver=coherence so typeck goes back to the old solver, and it ICEs the same way. So stable and beta are still hit by this.

    After that I looked around and this seems to be a duplicate of #157854, which already has a smaller repro and the fixed-by-next-solver label. Someone there bisected the original regression to #155355, the change that lets the old solver save cycle results in the global cache once an error has happened. That makes the result depend on whether an error showed up earlier in the session, and incremental catches the mismatch on the next build. I think that matches what's going on here too, since the E0597 comes from borrowck of get_stores, and that borrowck runs in the middle of the trait solving for the Send check.

    Personally I think closing this as a duplicate of #157854 is fine, the next solver is where things are heading and I get the feeling nobody wants to mess with the old solver's caching again. It does kinda suck for people on stable in the meantime though, there are quite a few reports of this exact ICE. Until then cargo clean -p bo_main gets you unstuck, like the ICE message says.

  4. cztomsik commented on Sep 27, 2026

    @cztomsik
    Author

    Yes, long story short I've been trying some stupid things (mostly refreshing my memory, I've not been around Rust for 3+ years I think) and changing signatures, and it worked before, and then at some point, compiler panicked and only cargo clean helped. I am not experienced with compiler dev enough to tell if it's duplicate, but feel free to close this. I just wanted to let somebody know (before I clean the evidence).

    and IIRC, yes, it was after adding impl Error as you say and I am not stuck anymore, I simply changed signatures to return sqlx::Error instead, which is what I should do anyway. But thank you for looking into this quickly :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.I-ICEIssue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions