Rust 的强项不是“写得少”,而是“让错误和状态变得可见”。这份文档讲的是如何用类型表达约束,而不是用注释和约定表达约束。
task_domain::TaskId 是一个围绕 Uuid 的新类型:
- 可复制、可比较、可哈希
- 可以序列化/反序列化
- 可以从字符串解析
- 可以通过
Display直接输出
这类 newtype 的价值是把“任务 ID”从普通字符串里区分出来,减少误用。
task_domain::Task 表示已经验证过的任务:
id() -> TaskIdtitle() -> &stris_completed() -> boolcomplete() -> bool
其中 complete() 会返回一个布尔值,表示这次调用是否改变了状态。
这意味着“完成任务”是幂等的:第一次会改变状态,后续再次调用不会继续改变结果。
task_domain::TaskError 有两个域内错误:
EmptyTitleTitleTooLong { max, actual }
这种错误设计的好处是:
- 调用者可以分辨失败原因
- 测试可以直接比较枚举值
- API 可以把域内错误稳定地映射成 HTTP 响应
Task::new 会在构造时完成校验:
- 去掉首尾空白
- 拒绝空标题
- 拒绝超过
MAX_TITLE_LEN的标题
这比“先创建一个不合法对象,后面再想办法修正”更安全。
labs/java-to-rust/examples/errors.rs 里的模式如下:
fn describe_creation(title: &str) -> Result<String, TaskError> {
let task = Task::new(title)?;
Ok(format!("created {} ({})", task.title(), task.id()))
}这说明:
- 函数签名直接暴露失败可能性
?负责把错误沿调用链向上传递- 成功和失败都被显式建模
apps/task-api 里的错误响应是结构化的,不是裸字符串:
| 情况 | HTTP 状态 | 响应 code |
响应 message |
|---|---|---|---|
| 标题非法 | 422 Unprocessable Entity |
invalid_task |
来自 TaskError::to_string() |
| 找不到任务 | 404 Not Found |
task_not_found |
task {id} does not exist |
这类设计的目标是:前端或 CLI 可以稳定处理错误,而不是解析一段不稳定的文本。
- 用 newtype 保护关键 ID
- 用枚举表达有限错误集合
- 用
Result表达会失败的构造或操作 - 用方法代替直接暴露内部字段
- 让“合法状态”比“非法状态”更容易构造
Java 里常见的做法是“先 new 出来,再靠运行时异常兜底”。Rust 更像是:
- 先把类型设计对
- 再让非法输入在入口处失败
- 再把错误作为数据向上传递
如果你把 Rust 的错误处理继续写成“到处抛异常”的样子,你会错过 Rust 最有价值的部分。