Serde 读取 Base64 到 48 字节数组,编译却缺 Deserialize
同样的泛型包装能读取 Vec 和 32 字节数组,换成 48 字节后编译失败。检查 derive 自动生成的泛型约束,而不是改动 Base64 内容。
Base64Encoded<Vec<u8>> 和 Base64Encoded<[u8; 32]> 都能通过编译,改成 [u8; 48] 却提示没有实现 Deserialize。若包装类型已经声明 #[serde(try_from = "String")],先检查自动派生的泛型约束。
这条读取路径实际需要先反序列化一个 String,再用业务的 Base64 转换取得 T。它并没有要求把 JSON 数组直接读成 T;为 48 字节数组补一套 JSON Deserialize,可能是在解决并不需要的接口。
让 bound 对应真正调用的转换
Serde derive 会根据泛型字段推断 Deserialize 约束,推断不总是符合中间转换的需求。正式 1.0.229 支持用 #[serde(bound(deserialize = "..."))] 明确替换这项推断。
下面只展示包装层,假定项目已经有 FromBase64 trait 及 Vec、固定长度数组的实现:
use serde::Deserialize;
use std::fmt::Display;
use std::convert::TryFrom;
trait FromBase64: Sized {
type Error;
fn from_base64(value: &str) -> Result<Self, Self::Error>;
}
#[derive(Deserialize)]
#[serde(try_from = "String")]
#[serde(bound(deserialize = "T: FromBase64, T::Error: Display"))]
struct Base64Encoded<T: FromBase64>(T);
impl<T> TryFrom<String> for Base64Encoded<T>
where
T: FromBase64,
T::Error: Display,
{
type Error = T::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
T::from_base64(&value).map(Base64Encoded)
}
}
这里仍要求转换错误能显示为文字,以便 Serde 构造格式错误;删掉整个 bound 并不等于可以删掉实际的 trait 要求。若 trait 还有生命周期或其他关联类型约束,应把真实条件写全。
48 字节是目标长度,不是允许截断的长度
固定数组的 FromBase64 实现仍应先严格解码,再检查解码后长度。长度不足补零、过长截断,都会把非法输入变成另一个合法值,尤其不适合密钥、摘要或协议字段。
因此应分别核对编译与内容校验:包装类型可以实例化,不代表任何字符串都应成功。有效的 48 字节输入、32 字节输入交给 48 字节目标、非法 Base64 字符和错误 JSON 类型,应按各自契约处理。
也不要把所有目标先改为 Vec 后直接向下游传递,除非业务允许可变长度。Vec 可以作为内部解码缓冲,但固定长度约束应在业务转换结束前恢复。
这项修改只调整 derive 的条件。仍有 E0277 时,检查报错要求的是 T 的 Deserialize,还是 FromBase64 本身、关联 Error 的 Display 或其他字段的约束,避免用同一个属性掩盖不同的类型问题。


