第三章 医疗数据层:Rust 中的 FHIR、DICOM 与隐私保护的类型安全实践

3.1 引言:医疗数据的类型迷宫与 Rust 的解锁之道
医疗数据是 Harness 必须直面的核心资产,也是区分通用 AI 外壳与专业医疗 AI 系统的关键分水岭。不同于电商订单或社交帖子,一份电子病历可能同时包含非结构化的自然语言主诉、半结构化的检查报告、严格结构化的 FHIR 资源、二进制的 DICOM 影像,以及需要映射到 ICD-11、SNOMED CT、LOINC 的编码术语。这些数据格式之间相互引用、层层嵌套,同时受制于 HIPAA、GDPR、《个人信息保护法》等法规的严格隐私约束。在传统的动态语言(如 Python)中,开发者往往依赖运行时校验、代码规范与人工审查来确保数据处理的安全性与正确性——但数据路径的复杂性使得遗漏几乎不可避免。
Rust 的类型系统为解决这一困境提供了独特的武器。代数数据类型、NewType 模式、typestate 模式、trait 约束等特性,使得我们可以将医疗数据的结构约束、状态转换规则、隐私要求直接编码进类型签名。这样一来,违反约束的代码将在编译期被拒绝,而非等到生产环境触发隐私泄露或临床错误。本章将从 FHIR、DICOM、术语编码三大数据类别入手,深入阐述如何在 Rust 中构建类型安全的医疗数据处理层,并展示隐私保护的编译期强制执行策略。
3.2 FHIR 资源的强类型世界
HL7 FHIR(Fast Healthcare Interoperability Resources)是现代医疗互操作性的基石。它基于 RESTful API,以 JSON 或 XML 格式表达患者、观察、药物请求等资源。一个典型的 FHIR Patient 资源片段如下:
{
\”resourceType\”: \”Patient\”,
\”id\”: \”example\”,
\”identifier\”: [
{
\”use\”: \”usual\”,
\”system\”: \”urn:oid:1.2.36.146.595.217.0.1\”,
\”value\”: \”12345\”
}
],
\”name\”: [
{
\”use\”: \”official\”,
\”family\”: \”Chalmers\”,
\”given\”: [\”Peter\”, \”James\”]
}
],
\”gender\”: \”male\”,
\”birthDate\”: \”1974-12-25\”
}
处理 FHIR 的第一步是将这种半结构化的 JSON 转化为 Rust 中易于操作且类型安全的领域模型。
3.2.1 选择基础:serde 与领域结构体
serde 是 Rust 中数据序列化与反序列化的标准库。我们可以为每一个 FHIR 资源定义对应的 struct,并使用 derive(Deserialize, Serialize) 自动生成解析逻辑。
use serde::{
Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = \”camelCase\”)]
pub struct Patient {
pub resource_type: String,
pub id: Option<String>,
pub identifier: Option<Vec<Identifier>>,
pub name: Option<Vec<HumanName>>,
pub gender: Option<String>,
pub birth_date: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = \”camelCase\”)]
pub struct Identifier {
pub use_: Option<String>,
pub system: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HumanName {
pub use_: Option<String>,
pub family: Option<String>,
pub given: Option<Vec<String>>,
}
然而,这种“朴素”映射存在几个问题:gender 为自由文本,可能传入非法值;birthDate 未解析为日期类型;resourceType 没有强制为 \”Patient\”;各类标识符的 system 与 value 关系松散。我们需要将领域约束熔铸进类型定义。
3.2.2 利用 NewType 与枚举收紧字段类型
首先,将 gender 定义为枚举,避免非法值:
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = \”lowercase\”)]
pub enum AdministrativeGender {
Male,
Female,
Other,
Unknown,
}
// 修改 Patient 结构体
pub struct Patient {
// …
pub gender: Option<AdministrativeGender>,
// …
}
serde 将自动把 JSON 的 \”male\” 映射为 AdministrativeGender::Male,若收到非法值(如 \”alien\”)则反序列化时直接报错,请求被 400 拒绝。这是“快速失败”原则在类型层面的体现。
对于标识符,我们可以创建自定义校验逻辑。例如,对于患者 MRN(Medical Record Number),我们期望 system 为某固定 URI,value 非空。可利用 serde 的 Deserialize 手动实现进行校验:
#[derive(Debug, Clone)]
pub struct MedicalRecordNumber {
pub system: String,
pub value: String,
}
impl<\’de> Deserialize<\’de> for MedicalRecordNumber {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<\’de>,
{
let raw = serde_json::Value::deserialize(deserializer)?;
let system = raw[\”system\”].as_str().ok_or_else(|| serde::de::Error::missing_field(\”system\”))?;
let value


