unprintable error: (unprintable type)
主线位置:查询主线的最后一步,
FromRow把PgRow变成 Rust 值的时候。#[derive(FromRow)]怎么展开、__spec_error!的五层实现,见Row 与 FromRow;本页讲怎么认出它、怎么绕开。
flowchart TB get["try_get 中间类型"] --> conv["TryFrom 转换"] conv -->|失败| spec["__spec_error!"] spec --> lost["固定字符串,原因丢失"] fix1["修法 A:字段用中间类型"] --> own["自己转换,错误原样"] fix2["修法 B:newtype 实现 Decode"] --> cd["ColumnDecode 带原错误"]
本页实验在 M2 Max、macOS、rustc 1.99.0、Postgres 16.14 上运行,依赖 sqlx = "=0.9.0";换成 =0.8.6 输出逐字相同。
现象
字段上写了 #[sqlx(try_from = "...")],数据库里的值转不过来时,错误只说哪一列,原因永远是同一句话:
error occurred while decoding column qty: unprintable error: (unprintable type)
最小复现
src/bin/try_from.rs,一个用标准库的 TryFromIntError,一个用自己写的、Display 和 Error 都实现了的错误类型:
#[derive(Debug, FromRow)]
struct Item {
#[sqlx(try_from = "i64")]
qty: u8,
}
#[derive(Debug)]
struct PercentError(i64);
impl std::fmt::Display for PercentError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} is not a percentage (0..=100)", self.0)
}
}
impl std::error::Error for PercentError {}
impl TryFrom<i32> for Percent {
type Error = PercentError;
// 0..=100 之外返回 Err(PercentError(v))
}
#[derive(Debug, FromRow)]
struct Score {
#[sqlx(try_from = "i32")]
pct: Percent,
}
show("Item 300", sqlx::query_as::<_, Item>("select 300::int8 as qty").fetch_one(&mut conn).await);
show("Score 250", sqlx::query_as::<_, Score>("select 250::int4 as pct").fetch_one(&mut conn).await);
show 打印错误的 Display,是 ColumnDecode 时再打印 source 的 Debug:
Item 300: error occurred while decoding column qty: unprintable error: (unprintable type)
Item 300: source = "unprintable error: (unprintable type)"
Score 250: error occurred while decoding column pct: unprintable error: (unprintable type)
Score 250: source = "unprintable error: (unprintable type)"
source 里就只是这个字符串,TryFromIntError 和 PercentError 都没有被保留。
原因
try_from 字段展开成先 try_get 中间类型、再 TryFrom,失败时包成 ColumnDecode,source 由 sqlx::__spec_error!(e) 生成(row.rs)。__spec_error! 本该按「实现了 Error 就装箱、否则 Display、否则 Debug、否则类型名」挑一个实现,可它在 SpecErrorWrapper(e) 本身上调方法,自动引用一层就匹配上了没有任何约束的那个实现:
impl<E> SpecError<E> for SpecErrorWrapper<E> {
fn __sqlx_spec_error(
&self,
) -> fn(SpecErrorWrapper<E>) -> Box<dyn Error + Send + Sync + 'static> {
|_e| "unprintable error: (unprintable type)".into()
}
}
所以不管错误类型实现了什么,结果都一样。逐层的分析和「从 &&&& 开始调就对了」的对照实验在Row 与 FromRow。flatten 加 try_from 的组合走同一个宏,同样受影响。
这是 sqlx 的 bug,0.8.6 和 0.9.0 都能复现。用 gh search issues -R transact-rs/sqlx 搜 unprintable error、unprintable type、spec_error、FromRow try_from、try_from error message,都没有结果,未找到上游 issue;本地仓库的 origin/main(到 2026-10-23)里 src/spec_error.rs 在 v0.9.0 之后没有改动。
修法
修法 A:字段用数据库那一侧的类型,FromRow 之后自己转。 错误在自己手里,想怎么报就怎么报:
#[derive(Debug, FromRow)]
struct RawItem {
qty: i64,
}
let raw = sqlx::query_as::<_, RawItem>("select 300::int8 as qty").fetch_one(&mut conn).await?;
match u8::try_from(raw.qty) {
Ok(q) => println!("修法 A: Ok({q})"),
Err(e) => println!("修法 A: qty = {}: {e}", raw.qty),
}
修法 B:newtype 自己实现 Type 和 Decode。 Decode::decode 返回的 BoxDynError 会原样成为 ColumnDecode 的 source:
struct Qty(u8);
impl Type<Postgres> for Qty {
fn type_info() -> PgTypeInfo {
<i64 as Type<Postgres>>::type_info()
}
}
impl<'r> Decode<'r, Postgres> for Qty {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let v = <i64 as Decode<Postgres>>::decode(value)?;
Ok(Qty(u8::try_from(v)?))
}
}
#[derive(Debug, FromRow)]
struct ItemB {
qty: Qty,
}
修法 A: qty = 300: number too large to fit in target type
修法 B 300: error occurred while decoding column "qty": number too large to fit in target type
修法 B 300: source = TryFromIntError(PosOverflow)
修法 B 42: Ok(ItemB { qty: Qty(42) })
修法 B 的 source 就是原来的 TryFromIntError。列名这次带了引号(column "qty"),因为这条错误走的是 try_get 的 {index:?},不是 try_from 分支里的 #id_s.to_string()。Type 这里只实现了 type_info;想和 i64 接受完全一样的列,把 compatible 也转发给 <i64 as Type<Postgres>>::compatible。Type / Decode 各管什么见类型:Type、Encode、Decode。
怎么确认自己没中招
grep -rn 'try_from *=' src/:每一处#[sqlx(try_from = ...)]失败时都只会报这句话。转换几乎不会失败的(i64转u64的主键)可以留着;会失败、需要知道原因的,换成上面两种写法。- 日志或告警里出现
unprintable error: (unprintable type),原因不在错误里,只能按列名回去查数据。
小结
#[sqlx(try_from)]转换失败时报ColumnDecode,source永远是字符串unprintable error: (unprintable type),原始错误被丢掉。- 原因是
__spec_error!在SpecErrorWrapper本身上调方法,自动引用落到了无约束的兜底实现;0.8.6 和 0.9.0 都是这样,未找到上游 issue。 - 修法:字段用中间类型、
FromRow之后自己转;或者给 newtype 实现Type+Decode,错误原样进source。