DEBUG.WIKI
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

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 或其他字段的约束,避免用同一个属性掩盖不同的类型问题。