最近几年,Rust语言在系统编程领域越来越受欢迎。作为一名长期使用C++的开发者,我第一次接触Rust时就被它的内存安全特性和出色的性能所吸引。特别是在开发鸿蒙应用时,我们经常需要处理一些对性能要求较高的核心模块,这时候Rust就成为了一个非常理想的选择。
Rust与ArkTS的结合可以充分发挥两者的优势。ArkTS作为鸿蒙应用开发的主要语言,提供了丰富的UI组件和便捷的开发体验;而Rust则擅长处理底层逻辑和性能敏感的任务。通过NAPI(Native API)这座桥梁,我们可以让这两种语言完美配合。
在实际项目中,我遇到过很多需要Rust的场景。比如处理大量数据计算、实现复杂算法、或者需要精确控制内存使用的模块。在这些情况下,使用Rust开发的模块通常比纯ArkTS实现快2-3倍,而且由于Rust的编译时检查,还能避免很多潜在的内存错误。
首先需要确保你的开发环境已经准备就绪。我推荐使用最新版本的DevEco Studio(目前是4.0+版本),它提供了完整的鸿蒙应用开发支持。同时,你还需要安装Rust工具链:
bash复制curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装完成后,添加ohos目标支持。这是我在实际项目中踩过的一个坑——鸿蒙系统需要特殊的交叉编译目标:
bash复制rustup target add aarch64-unknown-linux-ohos
在DevEco Studio中创建一个新的Native C++模板工程。这个模板已经包含了NAPI的基本配置,我们可以在此基础上进行修改:
entry/src/main/cpp目录,因为我们不会使用C++entry/build-profile.json5文件,删除C++相关的构建配置bash复制cargo new --lib hello
Rust项目的核心配置文件需要特别设置。以下是我的推荐配置:
toml复制[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[lib]
name = "hello"
crate-type = ["dylib"]
[dependencies]
oh-napi-sys = "0.1"
ctor = "0.1"
这里有几个关键点需要注意:
crate-type必须设置为dylib,因为我们需要生成动态链接库oh-napi-sys是鸿蒙NAPI的Rust绑定,它提供了与ArkTS交互的必要接口ctor库用于实现模块的自动注册让我们实现一个简单的加法函数作为示例。在lib.rs中:
rust复制use std::ffi::{CString};
use std::ptr::{null_mut};
use oh_napi_sys::*;
use ctor::ctor;
extern "C" fn add(env: napi_env, info: napi_callback_info) -> napi_value {
let mut args: [napi_value; 2] = [null_mut(); 2];
let mut argc = args.len();
unsafe {
napi_get_cb_info(env, info, &mut argc, args.as_mut_ptr(), null_mut(), null_mut());
// 参数类型检查
let mut valuetype0 = napi_valuetype_napi_undefined;
napi_typeof(env, args[0], &mut valuetype0);
let mut valuetype1 = napi_valuetype_napi_undefined;
napi_typeof(env, args[1], &mut valuetype1);
if valuetype0 != napi_valuetype_napi_number || valuetype1 != napi_valuetype_napi_number {
let mut undefined: napi_value = null_mut();
napi_get_undefined(env, &mut undefined);
return undefined;
}
// 获取参数值
let mut value0 = 0f64;
napi_get_value_double(env, args[0], &mut value0);
let mut value1 = 0f64;
napi_get_value_double(env, args[1], &mut value1);
// 执行加法运算
let native_sum = value0 + value1;
// 返回结果
let mut sum = null_mut();
napi_create_double(env, native_sum, &mut sum);
sum
}
}
这段代码展示了Rust与ArkTS交互的几个关键步骤:
为了让ArkTS能够调用我们的Rust函数,需要注册模块:
rust复制type Callback = extern "C" fn(env: napi_env, info: napi_callback_info) -> napi_value;
unsafe fn new_func_descriptor(name: &'static str, f: Callback) -> napi_property_descriptor {
let name = CString::new(name).unwrap();
napi_property_descriptor {
utf8name: CString::into_raw(name),
name: null_mut(),
method: Some(f),
getter: None,
setter: None,
value: null_mut(),
attributes: napi_property_attributes_napi_default,
data: null_mut(),
}
}
#[ctor]
fn register_hello_module() {
let name = CString::new("hello").unwrap();
let mut hello_module = napi_module {
nm_version: 1,
nm_flags: 0,
nm_filename: null_mut(),
nm_register_func: Some(init),
nm_modname: name.as_ptr() as _,
nm_priv: 0 as *mut _,
reserved: [0 as *mut _; 4],
};
unsafe {
napi_module_register(&mut hello_module);
}
}
unsafe extern "C" fn init(env: napi_env, exports: napi_value) -> napi_value {
let desc = [
new_func_descriptor("add", add),
];
let count = desc.len();
napi_define_properties(env, exports, count, desc.as_ptr());
exports
}
鸿蒙应用需要针对特定架构进行编译。使用以下命令:
bash复制cargo build -Zbuild-std --release --target aarch64-unknown-linux-ohos
这个命令会生成libhello.so文件,位于target/aarch64-unknown-linux-ohos/release/目录下。
为了让ArkTS能够正确识别我们的Rust模块,需要创建类型定义文件:
typescript复制// entry/src/main/rust/types/libhello/index.d.ts
export const add: (a: number, b: number) => number;
同时创建包描述文件:
json复制// entry/src/main/rust/types/libhello/oh-package.json5
{
"name": "libhello.so",
"types": "./index.d.ts",
"version": "",
"description": "Please describe the basic information."
}
最后,在工程的oh-package.json5中添加依赖:
json复制{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"libhello.so": "file:./src/main/rust/types/libhello"
}
}
将编译好的libhello.so复制到entry/libs/arm64-v8a目录下。
现在,我们可以在ArkTS页面中调用Rust实现的加法函数了:
typescript复制import testNapi from 'libhello.so'
// 在某个事件处理函数中
onClick() {
hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
}
如果一切配置正确,你将在日志中看到输出:"Test NAPI 2 + 3 = 5"。
在实际项目中集成Rust模块时,我遇到过不少问题。这里分享几个常见问题的解决方法:
类型不匹配错误:确保Rust和ArkTS之间的类型转换正确。比如,ArkTS的number对应Rust的f64。
内存管理问题:虽然Rust有所有权系统,但与NAPI交互时仍需小心内存泄漏。特别注意CString等需要手动释放的资源。
性能优化:频繁的跨语言调用会有开销。对于性能敏感的场景,建议尽量减少跨语言调用次数,可以在Rust侧完成更多处理。
调试技巧:可以使用hilog在Rust侧输出调试信息:
rust复制unsafe {
let msg = CString::new("Debug message").unwrap();
hilog::hilog_print(hilog::LogLevel::Info, 0xD001100, "RustTag", msg.as_ptr());
}