开发例程

复现开发文档中【开发例程】

主机环境

  • Windows10_x86-64
  • WSL2
  • Debian
  • Ubuntu 20.04

需要注意,如果在 Windows + WSL 的环境,一定要用 WSL2,WSL2 包含完整的 Linux 内核,如果是 WSL(WSL1),部分系统调用不识别,例程无法编译。

WSL2 安装方式:https://learn.microsoft.com/zh-cn/windows/wsl/install-manual

比较 WSL1 和 WSL2:https://learn.microsoft.com/zh-cn/windows/wsl/compare-versions

注意:

根据官方开发文档上的说明,最终测试会放在 Ubuntu 20.04 上进行,所以建议使用该版本,避免不必要的问题

编译例程

0. 安装 Rust

参考官网教程 https://www.rust-lang.org/zh-CN/tools/install

可以更换 Cargo 源,提高开发效率。

1. 下载

https://h5.vivo.com.cn/blueos-atom/c2rust/开发范例.zip
https://h5.vivo.com.cn/blueos-atom/c2rust/测试用例.zip

解压后,资源都是中文名文件夹,为了后续方便可以改为较短的英文名:

$ mkdir vivo-c2rust
$ wget https://h5.vivo.com.cn/blueos-atom/c2rust/开发范例.zip
$ unzip 开发范例.zip
$ mv 开发范例 dev-demo
$ cd dev-demo
$ mv 基于ChatGLM的命令行工具 cl-base-ChatGLM
$ cd cl-base-ChatGLM
Cargo.lock  Cargo.toml  LICENSE  src

2. 编译

cl-base-ChatGLM 是一个典型 cargo 生成的目录结构,直接运行:

$ cargo build
...
   Compiling mio v0.8.10
error: failed to run custom build command for `openssl-sys v0.9.101`

Caused by:
...
  run pkg_config fail: Could not run `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags openssl`
  The pkg-config command could not be found.
  
  Most likely, you need to install a pkg-config package for your OS.
  Try `apt install pkg-config`, or `yum install pkg-config`,
  or `pkg install pkg-config`, or `apk add pkgconfig` depending on your distribution.
...
  Make sure you also have the development packages of openssl installed.
  For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
...

根据提示需要安装 libssl-devpkg-config

$ sudo apt install libss-dev pkg-config

继续编译:

$ cargo build
...
warning: `C2RustGLM` (bin "C2RustGLM") generated 16 warnings (run `cargo fix --bin "C2RustGLM"` to apply 11 suggestions)
    Finished dev [unoptimized + debuginfo] target(s) in 23.95s

编译成功,但有很多警告,多数都是定义但未用到的变量,暂时不是处理。

2. 运行

$ cargo run
...
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/C2RustGLM`
Usage: target/debug/C2RustGLM <cpp_file_path>

根据提示,需要提供C文件,【测试用例.zip】中只有pdf文件,按照 多线程安全代码转换.pdf 编写一份C文件:

// c-src/thread-safe.c

#include <pthread.h>
#include <stdlib.h>

int data = 0;
void *increment(void *v) {
    for (int i = 0; i < 1000000; i++) {
        data++;
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
}

执行时传入文件:

$ mkdir c-src
$ vim thread-safe.c
$ cargo run -- c-src/thread-safe.c
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/C2RustGLM c-src/thread_safe.c`
    ```rust
use std::sync::{Arc, Mutex};
use std::thread;

fn increment(data: Arc<Mutex<i32>>) {
    for _ in 0..1_000_000 {
        let mut data = data.lock().unwrap();
        *data += 1;
    }
}

fn main() {
    let data = Arc::new(Mutex::new(0));
    let data_clone = Arc::clone(&data);

    let handle1 = thread::spawn(move || increment(data));
    let handle2 = thread::spawn(move || increment(data_clone));

    handle1.join().unwrap();
    handle2.join().unwrap();
}
    ```

结果和测试有区别,但逻辑没有错误,对输出结果进行验证,编译、运行均没有错误。

3. 更换 API key

在不做任何修改的情况下,已经可以正常运行,但建议按开发文档上的说明,将工程中用到的智谱 AI 开放平台的 API key 更换成自己的。

https://maas.aminer.cn/usercenter/apikeys

登录后首页即可查看到自己的 API key,复制、修改工程中的 src/sse_invoke_method/sse_invoke/constant_value.rs:8:28

重新编译、运行。

后记

在第二遍执行后,多数情况为以下结果,即没有 main 函数翻译:

    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/C2RustGLM c-src/thread-safe.c`
    ```rust
use std::sync::{Arc, Mutex};
use std::thread;

let data = Arc::new(Mutex::new(0));

fn increment(data: Arc<Mutex<i32>>) {
    for _ in 0..1_000_000 {
        let mut data = data.lock().unwrap();
        *data += 1;
    }
}

let data_clone = Arc::clone(&data);
let handle1 = thread::spawn(move || increment(data));
let handle2 = thread::spawn(move || increment(data_clone));

handle1.join().unwrap();
handle2.join().unwrap();
    ```

其它模型

TODO

开发例程

整体流程

  1. 获取输入参数,命令不正确则输入提示信息,并退出;
  2. 将第二个参数作为 C 文件的路径,并初始化 Prompt
  3. 打开文件,成功则将读出的内容追加到 Prompt 中,错误则输出错误信息(未退出,可以直接退出);
  4. 创建 GLM 实例,将 API keyPrompt 发送给开放平台,并等待回复;
  5. 最后输出平台返回的内容。

与 AI 平台的交互

  1. 根据 API key 创建 JWT
  2. JWTPrompt 发送给开放平台,并等待回复;

API Key

  1. 智谱 AI 开放平台的 API key,同时包含 “用户标识 id” 和 “签名密钥 secret”,即格式为 {id}.{secret}
  2. 用户 ID 用来标识用户,密钥用来加密鉴权。

JWT

详细内容见智谱AI官网说明文档 https://maas.aminer.cn/dev/api#nosdk

  1. Json Web Token,用于网络传输中数据鉴权、防篡改;
  2. 这里的 JWT 是平台自定义类型,与标准JWT 有一点区别;
  3. JWT 由三部分组成:头部、负载、签名,字符串值格式为:{header}.{payload}.{signature},其中 headerpayloadsignature,都是经过 Base64URL 编码后的字符串。

JWT Header

{
    "alg": "HS256",
    "sign-type": "SIGN"
}

alg:表示签名使用的算法,默认为 HMAC SHA256(写为HS256); sign_type:表示令牌的类型,JWT 令牌统一写为 SIGN 。

JWT Payload

{
    "api_key": "{ApiKey.id}",
    "exp": 1682503829130,
    "timestamp":1682503820130
}

api_key:表示用户标识 id,即用户API Key的 {id} 部分; exp:表示生成的JWT的过期时间,客户端控制,单位为毫秒; timestamp:表示当前时间戳,单位为毫秒。

JWT Signature

要创建签名部分,必须获取头部、负载、密钥,使用头部中的指定算法进行签名。

例如,如果使用 HMAC SHA256 算法,将按以下方式创建签名:

HMACSHA256(
    base64UrlEncode(JWT.header) + "." + base64UrlEncode(JWT.payload),
    ApiKey.id
)

开放平台 http 接口

  1. URL:https://open.bigmodel.cn/api/paas/v4/chat/completions

  2. 对该 URL 发起 post 请求,详细内容见官方文档 https://maas.aminer.cn/dev/api#glm-4

    以下为示例代码中的内容,head 内容如下

    "Cache-Control":"no-cache"
    "Connection", "keep-alive"
    "Accept", "text/event-stream"
    "Content-Type", "application/json;charset=UTF-8"
    "Authorization", "Bearer {token}"
    

    其中 token 就是 JWT 的 base64URL 字符串值。

    body 内容如下:

    {
        "model":"{val}",
        "messages":[
            {
                "role":"system",
                "content":"{SYSTEM_CONTENT}"
            }
            {
                "role":"user",
                "content":"{prompt}"
            }
        ],
        "stream":"{val}",
        "do_sample":"{val}",
        "temperature":"{val}",
        "top_p":"{val}"
    }
    

    其中:

    prompt即 C 语言代码内容,即输入的 C 文件内容;

    SYSTEM_CONTENT:是提前写好的身份描述信息,让 AI 模型扮演 c/c++ 和 rust 专家。

其它模型

micru

micru means Master in C/C++ and Rust, a tool that uses AI to translate C/C++ into Rust.

功能

  1. 增加 clap,用于解析命令行参数。看上去更像一个命令行工具,且方便后期扩展命令;
  2. 增加 jwt 模块,用于统一处理 JWT;
  3. 后端抽象,设计 invoke, outcome 处理平台请求和结果;
  4. 结果不再是单一的转换结果代码,还带有 AI 输出的提示信息;
  5. 作品思路:不追求翻译的准确性,定位是转译助手,可以提供多个转译结果和提示,辅助用户工作。

文档要求

spec1

spec2

spec2

提交要求:

  1. https://atomgit.com/ 上有一个以仓库,
  2. 仓库里有作品的源码、文档
  3. 使用了大模型等AI技术,要求使用开源技术

文档要求:

  1. 作品描述、方案目标、实现方案
  2. 同类型现有方案的对比
  3. 作品使用、部署方法
  4. 作品里的测试说明(主要在代码中要有测试)
  5. 软件整体设计思路
  6. 流程图、时序图、类图
  7. 关键数据结构说明
  8. 演示视频

作品介绍

C2Rust-Assistant 是一款具有 Web 界面,用于转译 C/C++ 到 Rust 的辅助编程工具。

作品地址:https://atomgit.com/vivoblueos/000041-JianRuJiaJing

展示地址:https://c2rust.cn

C2Rust-Assistant 采用 Web 的形式 提供 C/C++ 到 Rust 的转译服务。界面上有 C 代码输入区Rust 代码输出区,内置了一些测试用例用来快速展示转译效果。工具自身可以编译运行原代码和转译后的代码,对于错误能将信息反馈进行迭代转译,追求转译后代码能编译、运行输出与C代码相同。

现阶段提供了3种转译后端可供选择,可以手动输入提示信息,以提高转译效果。并可以设置重复次数,限制在测评可信度时自动转译的迭代次数。

作品特色:

  • 已集成3种后端转译工具,包含2个大模型和1个命令行工具;
  • 支持多轮对比和自动转译;
  • 支持用户传入运行参数,并执行代码后能查看运行结果;
  • 内置的15个测例中,除了考察常用的逻辑、功能性代码,还覆盖了C/C++的继承、弱指针、宏、可变参数、指针、结构体地址、联合体、位域和函数指针这些难点语法特性。

作品功能

  • C/C++ 代码输入;

  • Rust 转译代码展示;

  • C/C++ 编译、运行,及运行结果展示;

  • Rust 编译、运行,及运行结果展示;

  • 作品提供多个内置示例代码,方便快速体验、验证;

  • 作品提供多个转译工具以供选择,现阶段包括:

    1. 智谱AI
    2. 百川AI
    3. immunant/c2rust

    其中智谱AI、百川AI为服务端API调用,immunant/c2rust 为本地部署应用。

    immunant/c2rust 是按经典编译原理进行转译,其它方式均为调用大模型能力进行转译。

  • 重复转译,用户可以输出提示信息,并自动追加编译错误信息,反馈给模型进行迭代转译;

  • 测评可信度,简单对比两边的输出,当输出不同时,根据设置的重复次数,自动进行迭代转译。

img

需求分析

C/C++ 是目前使用最广泛的编程语言,尤其是 C语言,因为其简单的语言、优异的性能,几乎是操作系统、嵌入式开发、驱动程序等对性能、资源有严格要求的场合下的首选开发语言。

C/C++ 又是古老的语言,当前已有巨大的代码存量,几乎渗透的所有行业,然而使用 C/C++ 编写的系统大约有 70% 的严重安全漏洞和内存使用不当情况。Rust 是一门能够构建可靠且高效软件的现代化语言,其丰富的类型系统和所有权模型保证了内存安全和线程安全,在编译期就能够消除各种各样的错误。用 Rust 代替 C/C++ 编写更安全的代码,已经成为行业趋势。

出于以下原因,需要一款 C/C++ 到 Rust 的转译工具:

  1. 转译工程源码:Rust 要实现的功能,C/C++ 已有实现,希望最大化利用已有的 C/C++ 代码;
  2. 转译功能代码:Rust 特有的丰富的类型系统和所有权模型,导致其学习成本巨大、学习曲线陡峭,程序员对于想要的功能,可以先用 C/C++ 实现,再通过转译工具快速得到 Rust 代码。

对于转译工具有以下要求:

  1. 正确:转译后 Rust 代码可以运行,结果正确;
  2. 效率:转译后 Rust 代码的不能有太大的性能丢失;
  3. 辅助:因为转译后的 Rust 代码一定不会直接使用,只是为程序员提供方向明确的参考,所以要有一种方便使用的方式。

作品思路

对于代码转译,在很久之前只有一种解决方案:按编译原理,对原语言进行分析,再按目标语言进行点对点翻译。这种方式对于语法简单的语言可以使用,效果也会很好,例如多种脚本语言。但对 Rust 这种在设计上就极其复杂的语言,就会力不从心。C/C++ 会很容易得到分析结果,但对 Rust 的点对点翻译,会有巨大的工作量,短时间、少人力的情况下无法完成,且抛开性能,转译结果可能无法符合 Rust 的编程思想,简单的说就是不够优雅。

在如今 AI 大模型全面崛起的时代里,却又另一种解决方案:有许多用于代码生成、辅助编程的模型,完全可以借助这些模型的能力来实现代码转译。并且这是短时间内容易出成果的方式。

然而在实际使用过程中,发现有以下问题:

  1. AI 不是每次都能生成相同的结果,而且对于一些代码,也无法做到正确的转译,转译结果需要人为修改;
  2. 模型本身是有质量、性能差异的,选用不同的基座模型,天然得就会的到不同的转译结果;

对于以上问题,我们的解决思路是:

  1. 首先对原代码进行编译、运行,得到目标结果;再对转译的代码用 rustc 进行编译,对于编译错误,将错误信息反馈给模型,进行反复迭代,争取得到能够通过编译的代码,然后将运行结果与目标结果进行比较,结果不同也反馈给模型进行迭代;
  2. 如果经过较长时间无法得到正确输出,会将过程代码进行展示,同样可以起到辅助程序员转译的作用;
  3. 前期调研多个模型,对模型进行初步筛选,在试用过后留下少量几个模型,作品中提供这几个模型的选择,程序员可以对结果进行综合比较。

其中最重要的是:如何让模型输出可以编译、运行结果正确的转译代码,目前的方案是将编译提示信息和结果对比信息反馈模型,实现自动化快速迭代。

行业内更优的方式是:通过 Word2Vec 工具、向量数据库、知识库、RAG等方式,在基座模型不变的情况,限制模型的输出范围,提高转译的命中率。

作品目标

从时间和难度方面考虑,对于初赛阶段的目标:

  1. 能转换代码片段;
  2. 能转换单个文件,对于完整的代码可以得到相同的输出;

因为作品有【测评可信度】的概念,目前阶段,简单的认为转译后代码能有与原代码相同的输出,所以要求原代码要是本身可以编译通过的代码,对于功能片段,可以编写成测试用例,使之成为可以编译的完整代码。

如果有幸通过初赛,我们的下一阶段目标是:

  1. 能够转换多个文件组成的复杂项目;
  2. 能直接转换一个复杂的 git 仓库;
  3. 能对多个大模型转译结果进行输入、输出对比测试,给出更好结果;
  4. 重构工具软件架构,能非常方便的集成更多大模型API,提供转译服务;

完成以上目标,C2Rust-Assistant 本身不管是从方向探索、实用价值,都是很用意义的一个作品,已经可以作为一个产品来初步使用,所以后期的目标是:

  1. 能对某个知名开源 C 项目,给出完整转译结果,如 RTOS 中的 FreeRTOS、RT-Thread,工控协议中的 Modbus、DPN3.0等。

同类产品对比

https://c2rust.com/

项目地址:https://github.com/immunant/c2rust

这是一个开源的C到Rust转译工具项目,从内部代码中可以看出,这个工具的方案是编译原理的思路,先解析C代码,再翻译成Rust代码。

下面使用一个排序算法来对比两个工具,C 代码:

#include <stdio.h>

void insertion_sort(int const n, int * const p) {
    for (int i = 1; i < n; i++) {
        int const tmp = p[i];
        int j = i;
        while (j > 0 && p[j-1] > tmp) {
            p[j] = p[j-1];
            j--;
        }
        p[j] = tmp;
    }
}

int main(void) {
    int val[10] = {1, 3, 8, 6, 7, 9, 2, 4, 5, 0};
    
    insertion_sort(10, val);

    for (int i = 0; i < 10; i++) {
        printf("%d ", val[i]);
    }

    return 0;
}

/**
 * 运行结果:
 * 0 1 2 3 4 5 6 7 8 9
 */

img

这是 immunant/c2rust 的输出结果,里面使用到了 Rust 提供的 C/C++ 混合编程的特性,比如 extern "C",用 unsafe 包裹经过一对一严格翻译得到的Rust代码。并且出于作者的考虑,加入了 Rust 测试性功能 #![register_tool(c2rust)] 和在 stable 版本禁止使用的 #![feature(register_tool)],去除这两行代码后,可以编译运行,结果与 C 语言输出结果相同。

img

这是C2Rust-Assistant的转译结果,可以看出转译的代码完全正确,可以编译、和原代码有相同的输出,代码质量也很高。

immunant/c2rust 的转译结果是严格符合 Rust 与 C/C++ 混合编程的特性,虽然结果正确,但其实代码本身可读性很差,也很不符合 Rust 的编程习惯。相对来讲,C2Rust-Assistant的转译结果更符合人类语言中的 “信、达、雅” 翻译标准。

对比汇总:

项目C2Rust-Assistantimmunant/c2rust
UI(Web)
编辑输入、输出代码
多轮转译
编译运行代码
自定义运行参数
测评转译结果
加载本地文件
多种后端工具
内置多种测试用例

测试说明

内置测例

作品为了方便展示,内置了一些测例,可以快速加载进行转译体验,测例主要有两种类型:逻辑功能型C语言特性型

除了赛事提供的测试用例,另外选取了常用算法的C实现:

  • hello 字符串输出;
  • add 加法;
  • word_number 单词计数;
  • prime_number 最大素数。

由于本人是一名 MCU 端的嵌入式软件工程师,产品中经常使用 RT-Thread,针对C语言在嵌入式操作系统中常用的特性,从 RT-Thread 内核中选取了部分代码片段,简单修改后作为测试用例:

  • list 双向链表;
  • container_of 获取结构体地址宏;
  • union 联合体;
  • bit_fields 位域;
  • func_ptr 函数指针。

以下是这些测例的汇总说明:

序号名称类型说明
1hello逻辑功能一次转译成功
2add逻辑功能一次转译成功
3traitC语言特性一次转译成功
4weak_ptrC语言特性一次转译成功
5macroC语言特性多次转译成功
6argsC语言特性一次转译成功
7heap逻辑功能一次转译成功
8thread逻辑功能多次转译成功
9word_number逻辑功能一次转译成功
10prime_number逻辑功能一次转译成功
11*listC语言特性多次转译不成功
12*container_ofC语言特性多次转译成功
13*unionC语言特性多次转译成功
14*bit_fieldsC语言特性多次转译成功
15*func_ptrC语言特性多次转译成功

* :RT-Thread 内核中选取的测例。

具体内容可以查看测例转译结果文件

从中可以看出,对于具有明确逻辑、功能的代码,AI 大模型可以很好的理解原代码,并作出正确转译;

而对于带有语言特性的代码,一般不能一次转译成功,需要人为的将编译错误信息、运行结果反馈给模型,进行多次转译,因为转译成功的判据是与原代码有相同输出,简单且片面,所以有些特性的转译不能很好的体现原代码中的C语言特性,出现了“知道结果凑过程”的现象,如 container_ofbit_fields等;

这些测例中难度最高的应该是 list 双向链表,在 rust 中实现链表本身属于高阶内容,多次转译后始终无法得到可以通过编译的代码。

代码说明

1. 流程图

flow chart

其中,转译Rust代码根据选择的后端不一样:

  • AI模型平台为:连接平台 - 发送请求 - 获取结果;
  • immunant/c2rust 调用编译好的命令行工具,以c2rust transpile ./build/compile_commands.json 的格式转译,并获取结果。

2. 数据结构

  1. 拓展 AI 模型平台
// 用于流式返回数据
app.post('/api/transcode/stream', async (req, res) => {
    if (req.body.tool === 'chatglm') {
        await chatglm_stream(req.body, res);
    } else if (req.body.tool === 'baichuan') {
        await baichuan_stream(req.body, res);
    }
})

将与平台的交互抽象为请求内容req,和输出结果 res,为每个平台编写一个模块,并导出 xxx_stream 函数。

  1. 拓展测例
interface CodeSample {
    name: string;
    kind: string;
    code: string;
}

const C_SAMPLE: CodeSample[]  = [
    // ...
]

定义 CodeSample 接口,包括 namekindcode,其中 king 现阶段只有一个值 sample,为将来做拓展用,可以定义不同类型的测例,如不需要通过编译的代码片段。

演示视频

UI 介绍、主要功能演示:

手动输入提示,附带编译错误,再次转译:

自动转译功能演示:

测例转译结果

共15个内置测例,其中:

  • hello 是所有语言的第一段代码,作为转译的第一个测例,一次转移成功;
  • addword_countprime_number,用C语言实现的算法,是逻辑功能型代码,一次转译成功;
  • heapthread,赛方提供的测例,是逻辑功能型代码,一次转译成功;
  • traitweak_ptrmacroargs,赛方提供的测例,是语言特性型代码,基本都需要至少2次转译;
  • heapthread,赛方提供的测例,是逻辑功能型代码,一次转译成功;
  • listcontainer_ofunionbit_fieldsfunc_ptr,都是从 RT-Thread 中提取的代码,是语言特性型代码,且都是用C语言编写操作系统是必用的语言特性,除了 list 无法转译,其余都需要多次转译。

1. hello

hello

2. add

add

3. trait

内容过长,图片无法展示,以下是具体代码:

/**
 * @brief C++的抽象与继承
 * @class 语言特性型
 */
#include <iostream>

class AbstractK
{
public:
    double r;
    double p;
    AbstractK(const double &r, const double &p) : r(r), p(p){};
    virtual const bool isInside() const = 0;
    virtual const bool overlap() const = 0;
};

class K2D : public AbstractK
{
public:
    K2D(const double &r, const double &p) : AbstractK(r, p){};
};

class K3D : public AbstractK
{
public:
    K3D(const double &r, const double &p) : AbstractK(r, p){};
};

class SquareK: public K2D
{
public:
    SquareK(const double &r, const double &p) : K2D(r, p){};
    const bool isInside() const override
    {
        std::cout << "SquareK::isInside()" << "\n";
        return false;
    }
    const bool overlap() const override
    {
        std::cout << "SquareK::overlap()" << "\n";
        return false;
    }
};

class CubeK: public K3D
{
public:
    CubeK(const double &r, const double &p) : K3D(r, p){};
    const bool isInside() const override
    {
        std::cout << "CubeK::isInside()" << "\n";
        return false;
    }
    const bool overlap() const override
    {
        std::cout << "CubeK::overlap()" << "\n";
        return false;
    }
};

int main(void) {
    SquareK *squark = new SquareK(1.0,2.0);
    CubeK *cubek = new CubeK(1.0,2.0);
    squark->isInside();
    squark->overlap();
    cubek->isInside();
    cubek->overlap();
}

Rust 转译结果:

trait AbstractK {
    fn is_inside(&self) -> bool;
    fn overlap(&self) -> bool;
}

struct K2D {
    r: f64,
    p: f64,
}

impl K2D {
    fn new(r: f64, p: f64) -> Self {
        K2D { r, p }
    }
}

struct K3D {
    r: f64,
    p: f64,
}

impl K3D {
    fn new(r: f64, p: f64) -> Self {
        K3D { r, p }
    }
}

struct SquareK {
    k2d: K2D,
}

impl SquareK {
    fn new(r: f64, p: f64) -> Self {
        SquareK { k2d: K2D::new(r, p) }
    }
}

impl AbstractK for SquareK {
    fn is_inside(&self) -> bool {
        println!("SquareK::isInside()");
        false
    }

    fn overlap(&self) -> bool {
        println!("SquareK::overlap()");
        false
    }
}

struct CubeK {
    k3d: K3D,
}

impl CubeK {
    fn new(r: f64, p: f64) -> Self {
        CubeK { k3d: K3D::new(r, p) }
    }
}

impl AbstractK for CubeK {
    fn is_inside(&self) -> bool {
        println!("CubeK::isInside()");
        false
    }

    fn overlap(&self) -> bool {
        println!("CubeK::overlap()");
        false
    }
}

fn main() {
    let squark = SquareK::new(1.0, 2.0);
    let cubek = CubeK::new(1.0, 2.0);

    squark.is_inside();
    squark.overlap();
    cubek.is_inside();
    cubek.overlap();
}

原代码有一定的复杂度,涉及到了抽象与继承,转译非一次完成,设置到重试次数,在经过两轮转译后得到相同输出,此时还剩余2次重试机会。

trait

4. weak_ptr

weak_ptr

5. macro

macro

6. args

args

7. heap

heap

这个测例考察的是面对C代码中的内存安全问题,如申请到的内存重复释放,Rust 转译代码需要处理掉这个问题。

事实也是如此,在C代码有错误、无法编译的情况,Rust 代码中处理了该问题,并得到了可以编译、运行的代码。

但代码质量还可以改进:

fn main() {
    let mut ptr = Box::new(0);

    *ptr = 10;

    println!("The value before free was: {}", *ptr);
    // 可以调用 `drop` 手动释放,也可以在退出作用域后让 Rust 自动释放
    //drop(ptr);
}

8. thread

thread

9. word_count

word_count

10. prime_number

prime_number

11. list

内容过长,图片无法展示,以下是具体代码:

/**
 * @brief 双向链表。C语言没有链表类的数据类型,需要手动实现,这几乎是每个工程必须的组件,存在非常广泛,
 *        但用 Rust 实现链表其实是一个高难度操作,Rust 新手往往无法独立完成。
 * @class 语言特性型
 */
#include <stdio.h>

/**
 * Double List structure
 */
struct rt_list_node
{
    struct rt_list_node *next;                          /**< point to next node. */
    struct rt_list_node *prev;                          /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t;                  /**< Type for lists. */

/**
 * @brief initialize a list
 *
 * @param l list to be initialized
 */
void rt_list_init(rt_list_t *l)
{
    l->next = l->prev = l;
}

/**
 * @brief insert a node after a list
 *
 * @param l list to insert it
 * @param n new node to be inserted
 */
void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev = n;
    n->next = l->next;

    l->next = n;
    n->prev = l;
}

/**
 * @brief insert a node before a list
 *
 * @param n new node to be inserted
 * @param l list to insert it
 */
    void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next = n;
    n->prev = l->prev;

    l->prev = n;
    n->next = l;
}

/**
 * @brief remove node from list.
 * @param n the node to remove from the list.
 */
    void rt_list_remove(rt_list_t *n)
{
    n->next->prev = n->prev;
    n->prev->next = n->next;

    n->next = n->prev = n;
}

/**
 * @brief tests whether a list is empty
 * @param l the list to test.
 */
    int rt_list_isempty(const rt_list_t *l)
{
    return l->next == l;
}

/**
 * @brief get the list length
 * @param l the list to get.
 */
    unsigned int rt_list_len(const rt_list_t *l)
{
    unsigned int len = 0;
    const rt_list_t *p = l;
    while (p->next != l)
    {
        p = p->next;
        len ++;
    }

    return len;
}

int main(void) {
    rt_list_t list = {0}, after = {0}, before = {0};
    const char *isempty[] = {"not empty", "is empty"};

    rt_list_init(&list);
    printf("%s\n", isempty[rt_list_isempty(&list)]);

    rt_list_insert_after(&list, &after);
    printf("%s\n", isempty[rt_list_isempty(&list)]);
    printf("%d\n", rt_list_len(&list));

    rt_list_insert_before(&list, &before);
    printf("%d\n", rt_list_len(&list));

    rt_list_remove(&after);
    printf("%d\n", rt_list_len(&list));

    rt_list_remove(&before);
    printf("%d\n", rt_list_len(&list));

    return 0;
}

Rust 转译结果:

use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::fmt;

type Node = Rc<RefCell<ListNode>>;

struct ListNode {
    next: Weak<RefCell<ListNode>>,
    prev: Weak<RefCell<ListNode>>,
}

impl ListNode {
    fn new() -> Node {
        Rc::new(RefCell::new(ListNode {
            next: Weak::new(),
            prev: Weak::new(),
        }))
    }

    fn init(&mut self) {
        self.next = Weak::new();
        self.prev = Weak::new();
    }

    fn insert_after(&mut self, new_node: Node) {
        if let Some(mut next) = self.next.upgrade() {
            new_node.borrow_mut().prev = Rc::downgrade(&self);
            next.borrow_mut().prev = Rc::downgrade(&new_node);
            self.next = Rc::downgrade(&new_node);
            new_node.borrow_mut().next = next;
        } else {
            // If self.next is None, we are the last node.
            // This makes us point to the new node, and it back to us.
            self.next = Rc::downgrade(&new_node);
            new_node.borrow_mut().prev = Rc::downgrade(self);
        }
    }

    fn insert_before(&mut self, new_node: Node) {
        if let Some(mut prev) = self.prev.upgrade() {
            new_node.borrow_mut().next = Rc::downgrade(self);
            prev.borrow_mut().next = Rc::downgrade(&new_node);
            self.prev = Rc::downgrade(&new_node);
            new_node.borrow_mut().prev = prev;
        } else {
            // If self.prev is None, we are the first node.
            // This makes us point back to the new node, and it to us.
            self.prev = Rc::downgrade(&new_node);
            new_node.borrow_mut().next = Rc::downgrade(self);
        }
    }

    fn remove(&mut self) {
        if let Some(mut next) = self.next.upgrade() {
            if let Some(mut prev) = self.prev.upgrade() {
                next.borrow_mut().prev = prev;
                prev.borrow_mut().next = next;
            }
            self.next = Weak::new();
            self.prev = Weak::new();
        }
    }

    fn is_empty(&self) -> bool {
        self.next.upgrade().is_none() && self.prev.upgrade().is_none()
    }

    fn len(&self) -> usize {
        let mut len = 0;
        let mut current = self.next.upgrade();
        while let Some(node) = current {
            len += 1;
            current = node.borrow().next.upgrade();
            if node.borrow()..upgrade() == Some(self.clone()) {
                break; // We've looped back to the start.
            }
        }
        len
    }
}

impl fmt::Debug for ListNode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "ListNode .. }}")
    }
}

fn main() {
    let list = ListNode::new();
    let after = ListNode::new();
    let before = ListNode::new();

    list.borrow_mut().init();
    println!("{:?}", list.borrow().is_empty());

    list.borrow_mut().insert_after(after);
    println!("{:?}", list.borrow().is_empty());
    println!("{:?}", list.borrow().len());

    list.borrow_mut().insert_before(before);
    println!("{:?}", list.borrow().len());

    list.borrow_mut().remove();
    println!("{:?}", list.borrow().len());

    list.borrow_mut().remove();
    println!("{:?}", list.borrow().len());
}

这是 RT-Thread 内核源码中的双向链表,将以简单处理后形成一个链表测例。

因为代码中涉及指针操作,且涉及大量变量或值转移的情况,在 C 语言中可以自由操作,但在 Rust 中,由于其所有权概念,这些操作暴露出大量内存安全隐患。经过多轮转译后仍无法获取通过编译的代码。

12. container_of

/**
 * @brief 如果在C代码中使用了面向对象编程的思想,就一定会有 `container_of` 宏,
 *        这个宏也是 linux 源码中的一段经典代码,
 *        这个宏用来根据已知的成员地址和结构体类型,计算出结构体的地址,原理上很简单,使用也非常广泛,
 *        但因为涉及指针、类型强制转换这些不安全操作,在rust中难以实现。
 * 
 *        如果用rust重写某个功能,根据 rust 的内置类型和语法,不会碰到这个问题,
 *        但如果转译已有的c代码,则必须用 rust 的方式实现这个宏。
 * @class 语言特性型
 */
#include <stdio.h>

#define rt_container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))

struct rt_node {
    int n;
} ;
/**
 * Base structure of Kernel object
 */
struct rt_object
{
    const char *name;                           /**< name of kernel object */
    int        type;                            /**< type of kernel object */
    int        flag;                            /**< flag of kernel object */

    struct rt_node  node;                       /**< list node of kernel object */
};

int main(void) {
    struct rt_object obj = {0}; 
    struct rt_node *ptr = &obj.node;

    obj.name = "tty1";
    obj.type = 1;
    obj.flag = 2;
    obj.node.n = 3;

    struct rt_object *container = rt_container_of(ptr, struct rt_object, node);
    printf("%s, %d, %d, %d\n", container->name, container->type, container->flag, container->node.n);

    return 0;

}

13. union

union

14. bit_fields

bit_fields

Rust 中没有位域的概念,这个测例只是考察转译工具面对位域时会如何操作。Rust 代码中,将对结构体内位域成员变量的读写操作转译成了结构体内的方法。

15. func_ptr

内容过长,图片无法展示,以下是具体代码:

/**
 * @file 05func_ptr.c
 * @brief C代码中往往通过在结构体中定义函数指针的形式来实现面向对象编程的效果,Rust的trait可以更优雅的实现相同效果
 * 
 * @note 智谱AI 转译无法一次完成。因为涉及指针,几轮过后还是有错误,
 *       通过更换C代码的书写形式,可以转译两次得到正确输出。
 *       迭代过程见 `05func_ptr.md`
*/

#include <stdio.h>

typedef struct rt_device *rt_device_t;
/**
 * operations set for device object
 */
struct rt_device_ops
{
    /* common device interface */
    int  (*init)   (rt_device_t dev);
    int  (*open)   (rt_device_t dev, int oflag);
    int  (*close)  (rt_device_t dev);
    unsigned int (*read)   (rt_device_t dev, int pos, void *buffer, unsigned int size);
    unsigned int (*write)  (rt_device_t dev, int pos, const void *buffer, unsigned int size);
    int  (*control)(rt_device_t dev, int cmd, void *args);
};

struct rt_device {
    int flag;
    int open_flg;
    int ref_count;
    int device_id;
    // struct rt_device_ops ops;

    int  (*init)   (rt_device_t dev);
    int  (*open)   (rt_device_t dev, int oflag);
    int  (*close)  (rt_device_t dev);
    unsigned int (*read)   (rt_device_t dev, int pos, void *buffer, unsigned int size);
    unsigned int (*write)  (rt_device_t dev, int pos, const void *buffer, unsigned int size);
    int  (*control)(rt_device_t dev, int cmd, void *args);
};

int  init   (rt_device_t dev) {
    dev->flag = 0;
    printf("init\n");
    return 0;
}

int  open   (rt_device_t dev, int oflag) {
    dev->open_flg = oflag;
    printf("open, 0x%x\n", dev->open_flg);
    return 0;
}

int  close  (rt_device_t dev) {
    dev->open_flg = 0;
    printf("close\n");
    return 0;
}

unsigned int read   (rt_device_t dev, int pos, void *buffer, unsigned int size) {
    printf("read\n");
    return 0;
}

unsigned int write  (rt_device_t dev, int pos, const void *buffer, unsigned int size) {
    printf("write\n");
    return 0;
}

int  control(rt_device_t dev, int cmd, void *args) {
    printf("control\n");
    return 0;
}

int main(void) {
    struct rt_device dev = {0};
    dev.init = init;
    dev.open = open;
    dev.close = close;
    dev.read = read;
    dev.write = write;
    dev.control = control;

    dev.init(&dev);
    dev.open(&dev, 0x0102);
    dev.close(&dev);
    dev.read(&dev, 0, 0, 0);
    dev.write(&dev, 0, 0, 0);
    dev.control(&dev, 0, 0);

    return 0;
}

Rust 转译结果:

trait RtDeviceOps {
fn init(&mut self) -> i32;
fn open(&mut self, oflag: i32) -> i32;
fn close(&mut self) -> i32;
fn read(&mut self, pos: i32, buffer: &mut [u8]) -> usize;
fn write(&mut self, pos: i32, buffer: &[u8]) -> usize;
fn control(&mut self, cmd: i32, args: &mut [u8]) -> i32;
}

struct RtDevice {
flag: i32,
open_flg: i32,
ref_count: i32,
device_id: i32,
ops: Box<dyn RtDeviceOps>,
}

impl RtDevice {
fn new() -> Self {
    RtDevice {
        flag: 0,
        open_flg: 0,
        ref_count: 0,
        device_id: 0,
        ops: Box::new(RealRtDevice {}),
    }
}
}

struct RealRtDevice;

impl RtDeviceOps for RealRtDevice {
    fn init(&mut self) -> i32 {
        println!("init");
        0
    }

    fn open(&mut self, oflag: i32) -> i32 {
        println!("open, 0x{:x}", oflag);
        0
    }

    fn close(&mut self) -> i32 {
        println!("close");
        0
    }

    fn read(&mut self, _pos: i32, _buffer: &mut [u8]) -> usize {
        println!("read");
        0 // Replace with actual read logic.
    }

    fn write(&mut self, _pos: i32, _buffer: &[u8]) -> usize {
        println!("write");
        0 // Replace with actual write logic.
    }

    fn control(&mut self, _cmd: i32, _args: &mut [u8]) -> i32 {
        println!("control");
        0
    }
}

fn main() {
    let mut dev = RtDevice::new();
    dev.ops.init();
    dev.ops.open(0x0102);
    dev.ops.close();
    let mut buffer = [0u8; 1024]; // Example buffer, replace with actual usage.
    dev.ops.read(0, &mut buffer);
    dev.ops.write(0, &buffer);
    let mut args = [0u8; 1024]; // Example args buffer, replace with actual usage.
    dev.ops.control(0, &mut args);
}

可以看出,对于C语言中的函数指针,特别时结构内的函数指针成员,转译代码中很好的将它们转译成了 trait。如果可以进一步去除 RtDevice::ops 成员会时更好的结果。

另外,这个测例转换过程中有一个修改C代码过程,将函数指针直接定义在结构体内,而不是一个独立的结构体,大大提高了转译正确率。


这里详细记录以下这个测例的详细迭代流程:

第一次转译

struct RtDeviceOps {
    init: fn(&mut RtDevice) -> i32,
    open: fn(&mut RtDevice, i32) -> i32,
    close: fn(&mut RtDevice) -> i32,
    read: fn(&mut RtDevice, i32, *mut libc::c_void, u32) -> u32,
    write: fn(&mut RtDevice, i32, *const libc::c_void, u32) -> u32,
    control: fn(&mut RtDevice, i32, *mut libc::c_void) -> i32,
}

struct RtDevice {
    flag: i32,
    open_flg: i32,
    ref_count: i32,
    device_id: i32,
    ops: RtDeviceOps,
}

impl RtDevice {
    fn new() -> Self {
        RtDevice {
            flag: 0,
            open_flg: 0,
            ref_count: 0,
            device_id: 0,
            ops: RtDeviceOps {
                init: Self::init,
                open: Self::open,
                close: Self::close,
                read: Self::read,
                write: Self::write,
                control: Self::control,
            },
        }
    }

    fn init(dev: &mut RtDevice) -> i32 {
        dev.flag = 0;
        println!("init");
        0
    }

    fn open(dev: &mut RtDevice, oflag: i32) -> i32 {
        dev.open_flg = oflag;
        println!("open, 0x{:x}", dev.open_flg);
        0
    }

    fn close(dev: &mut RtDevice) -> i32 {
        dev.open_flg = 0;
        println!("close");
        0
    }

    fn read(dev: &mut RtDevice, pos: i32, buffer: *mut libc::c_void, size: u32) -> u32 {
        println!("read");
        0
    }

    fn write(dev: &mut RtDevice, pos: i32, buffer: *const libc::c_void, size: u32) -> u32 {
        println!("write");
        0
    }

    fn control(dev: &mut RtDevice, cmd: i32, args: *mut libc::c_void) -> i32 {
        println!("control");
        0
    }
}

fn main() {
    let mut dev = RtDevice::new();
    dev.ops.init(&mut dev);
    dev.ops.open(&mut dev, 0x0102);
    dev.ops.close(&mut dev);
    dev.ops.read(&mut dev, 0, std::ptr::null_mut(), 0);
    dev.ops.write(&mut dev, 0, std::ptr::null(), 0);
    dev.ops.control(&mut dev, 0, std::ptr::null_mut());
}

经过多轮转译后仍得不到正确代码,尝试修改C代码:

struct rt_device {
    int flag;
    int open_flg;
    int ref_count;
    int device_id;
    // struct rt_device_ops ops; // 不使用结构体作为成员

    //改为将函数指针直接定义在结构体中
    int  (*init)   (rt_device_t dev);
    int  (*open)   (rt_device_t dev, int oflag);
    int  (*close)  (rt_device_t dev);
    unsigned int (*read)   (rt_device_t dev, int pos, void *buffer, unsigned int size);
    unsigned int (*write)  (rt_device_t dev, int pos, const void *buffer, unsigned int size);
    int  (*control)(rt_device_t dev, int cmd, void *args);
};

第一次输出结果编译错误,再次点击转译按钮,第二次输出结果通过编译,输出也一致

trait RtDeviceOps {
    fn init(&mut self) -> i32;
    fn open(&mut self, oflag: i32) -> i32;
    fn close(&mut self) -> i32;
    fn read(&mut self, pos: i32, buffer: &mut [u8]) -> usize;
    fn write(&mut self, pos: i32, buffer: &[u8]) -> usize;
    fn control(&mut self, cmd: i32, args: &mut [u8]) -> i32;
}

struct RtDevice {
    flag: i32,
    open_flg: i32,
    ref_count: i32,
    device_id: i32,
    ops: Box<dyn RtDeviceOps>,
}

impl RtDevice {
    fn new() -> Self {
        RtDevice {
            flag: 0,
            open_flg: 0,
            ref_count: 0,
            device_id: 0,
            ops: Box::new(RealRtDevice {}),
        }
    }
}

struct RealRtDevice;

impl RtDeviceOps for RealRtDevice {
    fn init(&mut self) -> i32 {
        println!("init");
        0
    }

    fn open(&mut self, oflag: i32) -> i32 {
        println!("open, 0x{:x}", oflag);
        0
    }

    fn close(&mut self) -> i32 {
        println!("close");
        0
    }

    fn read(&mut self, _pos: i32, _buffer: &mut [u8]) -> usize {
        println!("read");
        0 // Replace with actual read logic.
    }

    fn write(&mut self, _pos: i32, _buffer: &[u8]) -> usize {
        println!("write");
        0 // Replace with actual write logic.
    }

    fn control(&mut self, _cmd: i32, _args: &mut [u8]) -> i32 {
        println!("control");
        0
    }
}

fn main() {
    let mut dev = RtDevice::new();
    dev.ops.init();
    dev.ops.open(0x0102);
    dev.ops.close();
    let mut buffer = [0u8; 1024]; // Example buffer, replace with actual usage.
    dev.ops.read(0, &mut buffer);
    dev.ops.write(0, &buffer);
    let mut args = [0u8; 1024]; // Example args buffer, replace with actual usage.
    dev.ops.control(0, &mut args);
}

Role: 程序员

Profile

  • Author: THY
  • Version: 0.1
  • Language: 中文
  • Description: 精通C/C++和Rust语言的程序员,了解计算机语言的编译原理、C/C++语言特性、算法、数据结构以及系统级开发相关的功能项:例如:基础操作:文件操作、共享内存;中级操作:网络操作、多线程;高级操作:多进程、IO多路复用等。擅长将能够将C语言转译成功能相同的Rust语言。

擅长C语言:

  1. C 语言是一种通用的、面向过程式的计算机程序设计语言;
  2. C 语言有很高的执行效率,通常是算法、操作系统的编程语言
  3. 很多系统调用使用 C 语言。

擅长Rust语言

  1. Rust 语言是一种高效、可靠的通用高级语言,具有性能高、可靠性高、生产力高的特点,可以用于开发传统命令行程序、Web应用、网路服务器、嵌入式设备;
  2. Rust 有丰富的数据类型和所有权模型;

擅长转译代码

  1. 将C代码转译成rust代码
  2. C 语言的特性转译成 Rust 相同的特性

Rules

  1. 能够保证 Rust 代码符合语法规则,通过编译。
  2. Rust 代码运行后与C语言有相同的结果;
  3. C 代码中使用了宏定义,Rust 代码中也使用宏来实现相同功能;
  4. C 代码中的联合体,Rust 代码中也使用联合体来实现相同功能;
  5. C 代码的函数指针,转译成 Rust 代码中的 trait;
  6. C 代码中的标准库函数,转译成 Rust 标准库函数;
  7. C 代码中的系统调用,转译成具有相同功能的 Rust 代码。

Workflow

  1. 用户以 "将以下C代码转译成Rust代码:[]" 的方式指定需要转译的C代码。
  2. 针对用户给定的C代码进行转译,转译后直接给出转译后的Rust代码结果,不要输出其他的内容也不要对代码做额外的解释。

Initialization

作为角色 <Role>, 严格遵守 <Rules>, 使用默认 <Language> 与用户对话。以 <Workflow> 完成转译任务。