AI 驱动的 Rust 代码生成:从接口描述自动生成样板代码的边界探索
AI 驱动的 Rust 代码生成从接口描述自动生成样板代码的边界探索一、CRUD CRUD 还是 CRUD大家好我是一铭。我相信每个后端程序员都有这样的经历一个新项目启动第一周的工作就是——建表、写 model、写 repository、写 service、写 controller、写测试。七八个文件几百行代码全是一个模子刻出来的。Rust 更痛苦——因为它的类型系统严格一个 model 定义要配上一个 FromRow 实现、一个 NewXxx 结构体、一个 UpdateXxx 结构体、还要写一堆的impl、Serialize、Deserialize……手工写一遍无聊又容易出错。那能不能让 AI 来干这件事给它一个 OpenAPI 或者 protobuf 的接口描述文件让它自动生成 Rust 样板代码这篇文章我来探索这个方向的可能性。二、为什么不用宏macro_rules / proc_macro你可能会问Rust 不是有过程宏proc_macro吗用#[derive]自动生成不好吗好问题。宏确实能解决一部分问题但也有局限宏只能处理 Rust 内部结构无法理解 OpenAPI、SQL schema、protobuf 这些外部描述文件。宏的调试体验很差cargo expand才能看到生成的代码编译器报错位置不直观。宏无法生成上下文感知的代码——比如根据字段名推断业务逻辑、根据关联关系生成 JOIN 查询。AI 生成更灵活可以处理非标准模式比如这个接口需要加缓存、这个字段需要脱敏。AI 代码生成不是替代宏而是宏的补充——宏处理规则明确的重复代码AI 处理需要语义理解的复杂场景。三、方案一基于 OpenAPI 生成 axum handler3.1 输入OpenAPI 描述openapi: 3.0.0 info: title: 用户管理 API version: 1.0.0 paths: /users: get: summary: 获取用户列表 parameters: - name: page in: query schema: type: integer default: 1 - name: page_size in: query schema: type: integer default: 20 responses: 200: description: 用户列表 post: summary: 创建用户 requestBody: content: application/json: schema: $ref: #/components/schemas/CreateUser responses: 201: description: 创建成功 components: schemas: User: type: object properties: id: type: integer name: type: string email: type: string created_at: type: string format: date-time CreateUser: type: object required: [name, email] properties: name: type: string minLength: 1 maxLength: 50 email: type: string format: email3.2 AI 生成的 Rust 代码把上面的 OpenAPI 描述喂给 AI让它生成 Rust 代码use axum::{ extract::{Query, Json, State}, http::StatusCode, response::IntoResponse, routing::{get, post}, Router, }; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use validator::Validate; // 参数校验库 use chrono::NaiveDateTime; // 数据模型 /// 用户实体数据库映射 #[derive(Debug, Serialize, sqlx::FromRow)] pub struct User { pub id: i64, pub name: String, pub email: String, pub created_at: NaiveDateTime, } /// 创建用户请求体 #[derive(Debug, Deserialize, Validate)] pub struct CreateUser { #[validate(length(min 1, max 50, message 姓名长度必须在1-50之间))] pub name: String, #[validate(email(message 邮箱格式不正确))] pub email: String, } /// 分页查询参数 #[derive(Debug, Deserialize)] pub struct Pagination { /// 页码从 1 开始 #[serde(default default_page)] pub page: i64, /// 每页大小 #[serde(default default_page_size)] pub page_size: i64, } fn default_page() - i64 { 1 } fn default_page_size() - i64 { 20 } // Handler 实现 /// GET /users - 获取用户列表分页 async fn list_users( State(pool): StatePgPool, Query(pagination): QueryPagination, ) - impl IntoResponse { // 计算偏移量 let offset (pagination.page - 1) * pagination.page_size; // 查询用户列表 总数 let users sqlx::query_as::_, User( SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2 ) .bind(pagination.page_size) .bind(offset) .fetch_all(pool) .await; match users { Ok(users) { // 构造分页响应 let response serde_json::json!({ data: users, page: pagination.page, page_size: pagination.page_size, total: users.len() // 实际应单独查询总数 }); (StatusCode::OK, Json(response)).into_response() } Err(e) { let err serde_json::json!({ error: 查询失败, detail: e.to_string() }); (StatusCode::INTERNAL_SERVER_ERROR, Json(err)).into_response() } } } /// POST /users - 创建新用户 async fn create_user( State(pool): StatePgPool, // 使用 Json 提取器 Validate 中间件自动校验参数 Json(payload): JsonCreateUser, ) - impl IntoResponse { // 手动校验生产环境建议用 axum-valid 中间件自动校验 if let Err(errors) payload.validate() { let err serde_json::json!({ error: 参数校验失败, fields: errors.field_errors() }); return (StatusCode::UNPROCESSABLE_ENTITY, Json(err)).into_response(); } // 插入数据库 let result sqlx::query_as::_, User( INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at ) .bind(payload.name) .bind(payload.email) .fetch_one(pool) .await; match result { Ok(user) { (StatusCode::CREATED, Json(user)).into_response() } Err(e) { let detail e.to_string(); let status if detail.contains(duplicate key) { StatusCode::CONFLICT } else { StatusCode::INTERNAL_SERVER_ERROR }; let err serde_json::json!({ error: 创建用户失败, detail: detail }); (status, Json(err)).into_response() } } } /// 注册路由 pub fn user_routes() - RouterPgPool { Router::new() .route(/users, get(list_users).post(create_user)) }四、方案二基于 SQL Schema 生成 Repository 层这是更常见的场景——已经有了数据库表结构需要生成 Rust 的数据访问层。4.1 输入SQL SchemaCREATE TABLE orders ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id), product_id BIGINT NOT NULL, amount DECIMAL(10, 2) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT pending, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_orders_user_id ON orders(user_id); CREATE INDEX idx_orders_status ON orders(status); CREATE INDEX idx_orders_created_at ON orders(created_at);4.2 AI 生成的 Repositoryuse sqlx::{PgPool, Postgres, Transaction}; use chrono::{DateTime, Utc}; use rust_decimal::Decimal; /// 订单实体 #[derive(Debug, Clone, sqlx::FromRow, serde::Serialize, serde::Deserialize)] pub struct Order { pub id: i64, pub user_id: i64, pub product_id: i64, pub amount: Decimal, pub status: String, pub created_at: DateTimeUtc, pub updated_at: DateTimeUtc, } /// 创建订单的参数 #[derive(Debug, Deserialize)] pub struct CreateOrderParams { pub user_id: i64, pub product_id: i64, pub amount: Decimal, } /// 更新订单的参数所有字段可选只更新非空字段 #[derive(Debug, Deserialize)] pub struct UpdateOrderParams { pub status: OptionString, pub amount: OptionDecimal, } /// 订单查询条件 #[derive(Debug, Default)] pub struct OrderFilter { pub user_id: Optioni64, pub status: OptionString, pub start_date: OptionDateTimeUtc, pub end_date: OptionDateTimeUtc, pub limit: Optioni64, pub offset: Optioni64, } /// 订单数据访问层 pub struct OrderRepo; impl OrderRepo { /// 创建订单并返回完整实体 pub async fn create( pool: PgPool, params: CreateOrderParams, ) - ResultOrder, sqlx::Error { sqlx::query_as::_, Order( INSERT INTO orders (user_id, product_id, amount, status) VALUES ($1, $2, $3, pending) RETURNING id, user_id, product_id, amount, status, created_at, updated_at ) .bind(params.user_id) .bind(params.product_id) .bind(params.amount) .fetch_one(pool) .await } /// 根据 ID 查找订单 pub async fn find_by_id( pool: PgPool, order_id: i64, ) - ResultOptionOrder, sqlx::Error { sqlx::query_as::_, Order( SELECT id, user_id, product_id, amount, status, created_at, updated_at FROM orders WHERE id $1 ) .bind(order_id) .fetch_optional(pool) .await } /// 按条件查询订单列表 /// 动态构建 WHERE 子句只添加有值的条件 pub async fn find_by_filter( pool: PgPool, filter: OrderFilter, ) - ResultVecOrder, sqlx::Error { // 使用 query_builder 动态构建 SQL let mut sql String::from( SELECT id, user_id, product_id, amount, status, created_at, updated_at FROM orders WHERE 11 ); let mut params: VecString Vec::new(); let mut param_idx 1; // 动态追加条件 if let Some(ref user_id) filter.user_id { sql.push_str(format!( AND user_id ${}, param_idx)); params.push(user_id.to_string()); param_idx 1; } if let Some(ref status) filter.status { sql.push_str(format!( AND status ${}, param_idx)); params.push(status.clone()); param_idx 1; } if let Some(ref start) filter.start_date { sql.push_str(format!( AND created_at ${}, param_idx)); params.push(start.to_rfc3339()); param_idx 1; } if let Some(ref end) filter.end_date { sql.push_str(format!( AND created_at ${}, param_idx)); params.push(end.to_rfc3339()); param_idx 1; } sql.push_str( ORDER BY created_at DESC); if let Some(limit) filter.limit { sql.push_str(format!( LIMIT ${}, param_idx)); params.push(limit.to_string()); param_idx 1; } if let Some(offset) filter.offset { sql.push_str(format!( OFFSET ${}, param_idx)); params.push(offset.to_string()); } // 使用 sqlx::query 动态执行注意这里用了简单的字符串拼接 // 生产环境建议用 sqlx::QueryBuilder 防注入 let mut query sqlx::query_as::_, Order(sql); for p in params { query query.bind(p); } query.fetch_all(pool).await } /// 更新订单只更新非空字段 pub async fn update( pool: PgPool, order_id: i64, params: UpdateOrderParams, ) - ResultOptionOrder, sqlx::Error { // 构建动态 UPDATE SET 子句 let mut sets Vec::new(); let mut param_idx 1; // 用 query_builder 构建安全的参数化查询 if let Some(ref status) params.status { sets.push((format!(status ${}, param_idx), status.clone())); param_idx 1; } if let Some(ref amount) params.amount { sets.push((format!(amount ${}, param_idx), amount.to_string())); param_idx 1; } if sets.is_empty() { // 无事可更新直接返回原记录 return Self::find_by_id(pool, order_id).await; } // 总是更新 updated_at sets.push((format!(updated_at NOW()), String::new())); let set_clause: VecString sets.iter() .map(|(s, _)| s.clone()) .collect(); let sql format!( UPDATE orders SET {} WHERE id ${} RETURNING id, user_id, product_id, amount, status, created_at, updated_at, set_clause.join(, ), param_idx ); let mut query sqlx::query_as::_, Order(sql); for (_, value) in sets { if !value.is_empty() { query query.bind(value); } } query query.bind(order_id); query.fetch_optional(pool).await } }AI 代码生成的边界与现实我的实践心得AI 生成 人工审核是最佳组合。AI 生成 80% 的样板代码工程师只需要关注 20% 的业务逻辑和安全细节。Prompt 的质量决定代码的质量。描述越具体表结构、字段校验规则、错误处理策略生成的代码越贴近需求。把 AI 生成的代码当作第一版草稿而不是最终代码。编译器、clippy、集成测试是第二道防线。Process Macro 和 AI 各司其职。能用#[derive]搞定的Serialize/Deserialize/FromRow优先用宏。需要语义理解的业务逻辑、SQL 拼接、错误处理才交给 AI。实际使用中的一个意外发现AI 生成的 Repository 代码在find_by_filter里用了字符串拼接 SQL 而不是sqlx::QueryBuilder。虽然代码看起来能用但绑定参数的方式有 SQL 注入风险。这恰恰说明 AI 生成的边界——它能写出看起来正确的代码但不能保证安全。编译通过只是及格线clippy lint 安全审计才是上线标准。五、总结从 OpenAPI 生成 axum handler数据模型、参数校验、路由注册一气呵成。从 SQL Schema 生成 Repository 层完整的 CRUD、动态查询、分页排序。AI 代码生成不是要取代程序员而是要消灭那些浪费程序员生命的重复劳动。Rust 的类型系统严格恰好是 AI 代码生成的好搭档——编译器会在编译期把所有类型错误揪出来AI 写错的代码根本过不了编译。这就是我目前探索到的边界——AI 可以帮我省掉 70% 的重复琐碎工作但架构决策、业务建模、安全把控这些核心能力暂时还是工程师的独特价值。未来随着 AI Agent 能力增强从需求文档到可运行代码的全自动流程也许真的不远了。但在那一天到来之前我们要做的是用好 AI 工具把精力花在更有创造性的工作上。有什么想法欢迎评论区交流

相关新闻