AI 驱动的 Rust 代码生成:从接口描述自动生成样板代码的边界探索

AI2周前发布 beixibaobao
14 0 0

AI 驱动的 Rust 代码生成:从接口描述自动生成样板代码的边界探索

一、CRUD CRUD 还是 CRUD

大家好,我是一铭。我相信每个后端程序员都有这样的经历:一个新项目启动,第一周的工作就是——建表、写 model、写 repository、写 service、写 controller、写测试。七八个文件,几百行代码,全是一个模子刻出来的。

Rust 更"痛苦"——因为它的类型系统严格,一个 model 定义要配上一个 FromRow 实现、一个 NewXxx 结构体、一个 UpdateXxx 结构体、还要写一堆的 implSerializeDeserialize……手工写一遍,无聊又容易出错。

那能不能让 AI 来干这件事?给它一个 OpenAPI 或者 protobuf 的接口描述文件,让它自动生成 Rust 样板代码?

这篇文章,我来探索这个方向的可能性。

二、为什么不用宏(macro_rules / proc_macro)

你可能会问:Rust 不是有过程宏(proc_macro)吗?用 #[derive] 自动生成不好吗?

好问题。宏确实能解决一部分问题,但也有局限:

  1. 宏只能处理 Rust 内部结构,无法理解 OpenAPI、SQL schema、protobuf 这些外部描述文件。
  2. 宏的调试体验很差cargo expand 才能看到生成的代码,编译器报错位置不直观。
  3. 宏无法生成"上下文感知"的代码——比如根据字段名推断业务逻辑、根据关联关系生成 JOIN 查询。
  4. AI 生成更灵活,可以处理非标准模式(比如"这个接口需要加缓存"、"这个字段需要脱敏")。

AI 代码生成不是替代宏,而是宏的补充——宏处理规则明确的重复代码,AI 处理需要语义理解的复杂场景。

三、方案一:基于 OpenAPI 生成 axum handler

3.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: email

3.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): State<PgPool>,
    Query(pagination): Query<Pagination>,
) -> 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): State<PgPool>,
    // 使用 Json 提取器 + Validate 中间件自动校验参数
    Json(payload): Json<CreateUser>,
) -> 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() -> Router<PgPool> {
    Router::new()
        .route("/users", get(list_users).post(create_user))
}

四、方案二:基于 SQL Schema 生成 Repository 层

这是更常见的场景——已经有了数据库表结构,需要生成 Rust 的数据访问层。

4.1 输入:SQL Schema

CREATE 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 生成的 Repository

use 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: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}
/// 创建订单的参数
#[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: Option<String>,
    pub amount: Option<Decimal>,
}
/// 订单查询条件
#[derive(Debug, Default)]
pub struct OrderFilter {
    pub user_id: Option<i64>,
    pub status: Option<String>,
    pub start_date: Option<DateTime<Utc>>,
    pub end_date: Option<DateTime<Utc>>,
    pub limit: Option<i64>,
    pub offset: Option<i64>,
}
/// 订单数据访问层
pub struct OrderRepo;
impl OrderRepo {
    /// 创建订单并返回完整实体
    pub async fn create(
        pool: &PgPool,
        params: CreateOrderParams,
    ) -> Result<Order, 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,
    ) -> Result<Option<Order>, 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,
    ) -> Result<Vec<Order>, 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 1=1"
        );
        let mut params: Vec<String> = 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,
    ) -> Result<Option<Order>, 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: Vec<String> = 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 代码生成的边界与现实

我的实践心得

  1. AI 生成 + 人工审核是最佳组合。AI 生成 80% 的样板代码,工程师只需要关注 20% 的业务逻辑和安全细节。
  2. Prompt 的质量决定代码的质量。描述越具体(表结构、字段校验规则、错误处理策略),生成的代码越贴近需求。
  3. 把 AI 生成的代码当作"第一版草稿",而不是"最终代码"。编译器、clippy、集成测试是第二道防线。
  4. Process Macro 和 AI 各司其职。能用 #[derive] 搞定的(Serialize/Deserialize/FromRow),优先用宏。需要语义理解的(业务逻辑、SQL 拼接、错误处理),才交给 AI。

实际使用中的一个意外发现:AI 生成的 Repository 代码,在 find_by_filter 里用了字符串拼接 SQL 而不是 sqlx::QueryBuilder。虽然代码看起来"能用",但绑定参数的方式有 SQL 注入风险。这恰恰说明 AI 生成的"边界"——它能写出看起来正确的代码,但不能保证安全。编译通过只是及格线,clippy lint + 安全审计才是上线标准。

五、总结

  1. 从 OpenAPI 生成 axum handler:数据模型、参数校验、路由注册一气呵成。
  2. 从 SQL Schema 生成 Repository 层:完整的 CRUD、动态查询、分页排序。

AI 代码生成不是要取代程序员,而是要消灭那些浪费程序员生命的重复劳动。Rust 的类型系统严格,恰好是 AI 代码生成的好搭档——编译器会在编译期把所有类型错误揪出来,AI 写错的代码根本过不了编译。

这就是我目前探索到的边界——AI 可以帮我省掉 70% 的重复琐碎工作,但架构决策、业务建模、安全把控这些核心能力,暂时还是工程师的独特价值。

未来随着 AI Agent 能力增强,"从需求文档到可运行代码"的全自动流程也许真的不远了。但在那一天到来之前,我们要做的是:用好 AI 工具,把精力花在更有创造性的工作上

有什么想法欢迎评论区交流!

© 版权声明

相关文章