1. 安裝 rustup
rustup 是 Rust 的安裝和版本管理工具
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
該命令會安裝 rusup 和最新的穩定版本的 Rust;包括:
-
rustc
Rust 編譯器,用于將 Rust 代碼編譯成可執行文件或庫。 -
cargo
Rust 的包管理器和構建工具,用于管理項目依賴、編譯項目、運行測試等。 -
rustfmt
代碼格式化工具,用于自動格式化 Rust 代碼以符合官方風格指南。 -
clippy
靜態分析工具,用于捕捉常見錯誤和改進代碼質量。 -
其他工具,如
rustdoc
用于生成文檔等。
成功后控制臺會輸出:Rust is installed now. Great!
macOS 系統上需要安裝:xcode-select --install
cargo 在開發中較為常用,算是打交道最多的工具之一
2. 標準庫 Rust Standard Library
標準庫是 Rust 編程語言的官方庫,提供了一系列預先編寫好的類型和函數,用來處理常見的任務,如:
-
基本數據類型(比如
i32
,u64
,f32
等)。 -
集合類型(如
Vec<T>
,HashMap<K, V>
等)。 -
輸入/輸出(I/O)操作,包括文件操作和網絡編程。
-
線程和并發編程工具。
-
其他有用的工具,如字符串處理、日期和時間操作等。
渠道
通常情況下安裝 rustup 的時候,標準庫就已經安裝到本地;但是 rust 有幾種發布渠道,用于提供不同穩定程度的 Rust 版本,Rust 的三個主要發布渠道是:
-
Stable(穩定版):這是大多數用戶推薦使用的版本。它每六周發布一次,提供最新的功能和改進,但只包括那些經過充分測試和認為穩定的特性。
-
Beta(測試版):這個版本比 Stable 新,但可能包含一些即將納入下一個 Stable 版本的特性和改進。它主要用于測試即將發布的功能,以確保它們在正式成為穩定版之前沒有問題。
-
Nightly(每夜構建版):這是最前沿的版本,包括了所有最新開發的特性。這些特性可能未完全穩定或待評估,因此這個版本主要用于實驗和評估最新的語言改進。Nightly 版本,顧名思義,每夜更新一次,包括最新的代碼提交。
安裝
-
列出已安裝的版本
rustup toolchain list
-
安裝新的版本
rustup toolchain install beta
或者
rustup toolchain install nightly
切換版本
切換全局(即默認)Rust 版本,使用rustup default
命令:
rustup default stable
rustup default beta
rustup default nightly
這些命令會將你的系統默認 Rust 版本切換為相應的版本。
為特定項目切換版本
如果你只想為特定的項目切換 Rust 版本,而不影響全局設置,可以在項目目錄內使用以下命令設置目錄級別的默認版本:
rustup override set stable
rustup override set beta
rustup override set nightly
補裝標準庫源碼
rustup component add rust-src
每一個 toolchain 都有自己的源碼
建議安裝 stable 和 nightly 的源碼,因為只有 nightly 版本支持編譯鴻蒙系統
如果不安裝后續鴻蒙 OS 下編譯會報錯,根據提示安裝也行
為特定目標平臺編譯代碼
在 stable 下,rust 支持 android 平臺的編譯,通過 rustup target list |grep android
可以查看支持的所有平臺架構
% rustup target list |grep android 24-03-19 - 15:46:34
aarch64-linux-android (installed)
arm-linux-androideabi (installed)
armv7-linux-androideabi (installed)
i686-linux-android (installed)
thumbv7neon-linux-androideabi (installed)
x86_64-linux-android (installed)
如果已安裝,后面會有 (installed) 標識;建議一次性都安裝上:
rustup target add aarch64-linux-android arm-linux-androideabi armv7-linux-androideabi i686-linux-android thumbv7neon-linux-androideabi x86_64-linux-android
鴻蒙 OS 下需要切換到 nightly,通過 rustup target list |grep ohos
可以查看支持的所有平臺架構:
% rustup target list |grep ohos
aarch64-unknown-linux-ohos (installed)
armv7-unknown-linux-ohos (installed)
x86_64-unknown-linux-ohos (installed)
同樣,建議一次性都安裝上
3. 創建 Rust Library 工程
使用命令行創建:
cargo new demo --lib
或者使用 IDE,推薦使用 Jetbrains 的 RustRover
此時目錄結構如下:
demo
├── Cargo.toml
└── src└── lib.rs
Cargo.toml 的配置
[lib]
[lib]
crate-type = ["cdylib"]
crate-type
屬性用于指定編譯目標類型。這些類型決定了編譯器會如何編譯你的代碼。以下是一些常見的crate-type
值及其區別:
1. bin
-
描述:一個可執行的二進制文件。
-
使用場景:當你想要創建一個可以直接運行的程序時,使用此類型。大多數應用程序都是以
bin
類型編譯的。
2. lib
-
描述:一個庫文件,可以被其他 Rust 包作為依賴使用。
-
使用場景:如果你正在開發一個提供函數、類型或特性給其他包使用的庫,應選擇此類型。
3. rlib
-
描述:Rust 編譯的庫文件,包含元數據和符號,供后續的 Rust 編譯階段使用。
-
使用場景:當你想要編譯一個 Rust 庫供其他 Rust 項目使用,并期望進行鏈接和代碼生成優化時。
4. dylib
-
描述:一個動態鏈接庫(DLL),可以在運行時被 Rust 或其他語言的應用程序動態鏈接。
-
使用場景:當你想要創建一個可以被多個程序共享的庫,或者當你需要和其他使用動態鏈接的語言互操作時。
5. cdylib
-
描述:一個為 C 語言接口定制的動態鏈接庫。它移除了 Rust 特有的元數據,只保留了可以從 C 或其他語言調用的符號。
-
使用場景:當你開發一個 Rust 庫,希望能夠被 C 或其他語言作為動態鏈接庫使用時。這是創建跨語言共享庫的常見方式。
6. staticlib
-
描述:靜態庫(
.a
文件),可以被 C 語言或其他語言的應用程序在編譯時靜態鏈接。 -
使用場景:如果你想要創建一個可以被其他語言靜態鏈接的庫,或希望你的 Rust 代碼被編譯進一個單獨的二進制文件,而不依賴于 Rust 的運行時或其他動態庫。
7. proc-macro
-
描述:一個過程宏庫,用于創建自定義
#[derive]
宏或其他類型的宏。 -
使用場景:當你想要創建新的宏來擴展 Rust 語法,比如自定義派生屬性或宏指令時。
[dependencies] 和 [features]
由于需要區分 android 和 ohos 兩個平臺的特定庫,所以有一些依賴庫需要配置為可選的,然后使用 cargo
構建的時候添加 --features
參數來分別進行交叉編譯
對于 android 平臺,需要引入 jni 庫,來和 java/kotlin 互相調用
rust
和node
互相調用可以使用node-bindgen
,但遺憾的是,node-bindgen
并不兼容鴻蒙系統;不過已經有人基于node-bindgen
兼容了 ohos:https://crates.io/crates/ohos-node-bindgen對于
ohos
平臺,需要引入ohos-node-bindgen
庫,來和node
通信;由于ohos-node-bindgen
依賴socket2
,然而socket2
在ohos
下有bug
,所以這里需要使用https://github.com/stuartZhang/socket2.git
來替換
ohos-node-bindgen
內部依賴的socket2
版本
最終配置如下
[features]
default = ["android"]
android = ["dep:jni", "dep:android_logger"]
ohos = ["dep:ohos-node-bindgen", "dep:socket2"][dependencies]
jni = { version = "0.19.0", optional = true }
android_logger = { version = "0.13.3", optional = true }
ohos-node-bindgen = { version = "6.0.3", optional = true }
socket2 = { version = "0.4.10", optional = true }
dashmap = "5.5.3"
threadpool = "1.8.1"
log = "0.4.21"[patch.crates-io]
socket2 = { version = "0.4.10", git = "https://github.com/stuartZhang/socket2.git", branch = "v0.4.x" }
也就是說:
-
dashmap
、threadpool
和log
是所有平臺下都參與編譯的庫 -
android
單獨編譯:jni
和android_logger
-
ohos
單獨編譯:ohos-node-bindgen
和socket2
-
另外,
features
的默認值為android
編寫代碼 - lib.rs
由于存在不同的 features,所以對于 android:
#[cfg(feature = "android")]
#[no_mangle]
pub extern "system" fn Java_com_haier_uhome_uplus_hook_monitor_app_NativeLib_hello(env: JNIEnv,_class: JClass,
) -> jstring {// 將 Rust 字符串轉換為 JNI 字符串let result = env.new_string("Hello from Rust!").expect("Couldn't create Java string!");// 返回結果result.into_inner()
}
#[cfg(feature = "android")]
:與上述 features
對應
#[no_mangle]
則是禁用駝峰警告
對于 ohos:
#[cfg(feature = "ohos")]
#[ohos_node_bindgen]
pub extern "C" fn add(l: i32, r: i32) -> i32 {l + r
}
#[cfg(feature = "ohos")]
:與上述 features
對應
#[ohos_node_bindgen]
則是標識 add 函數可以被 node 端調用
node-bindgen 的大致原理如下:
1. FFI(外部函數接口)
Node.js 的原生模塊基于 C++ 和 Node.js 的 N-API(原生 API),N-API 提供了一套與 V8 引擎解耦的接口,使原生模塊在 Node.js 版本升級時保持兼容。
node-bindgen
底層利用 Rust 的外部函數接口(FFI)能力,通過這些接口與 Node.js 通信。Rust 的 FFI 功能允許其調用 C 語言 API。因此,
node-bindgen
實際上是通過 Rust 的 FFI 調用 Node.js 的 N-API 來創建和管理 JavaScript 值,以及執行與 JavaScript 環境的交互。2. 宏和屬性
node-bindgen
提供了一系列宏(例如#[node_bindgen]
),這些宏在編譯時自動生成將 Rust 函數暴露為 Node.js 可調用函數的膠水代碼。這個過程包括自動生成用于參數轉換和返回值處理的代碼,使 Rust 函數能夠直接接收來自 JavaScript 的參數并返回可以直接在 JavaScript 中使用的結果。3. 內存管理
Rust 和 JavaScript 之間的內存管理是
node-bindgen
的關鍵部分。Rust 有自己的內存管理規則,主要基于所有權和生命周期,而 JavaScript 的內存則由垃圾收集器自動管理。node-bindgen
必須確保在這兩種內存管理模型之間正確地橋接,包括處理 Rust 中的數據所有權轉移和確保 JavaScript 對象在需要時保持存活。4. 異步操作
Node.js 廣泛使用異步操作,而 Rust 也有強大的異步支持。
node-bindgen
支持將 Rust 的異步操作暴露給 Node.js。這通過將 Rust 的Future
轉換為 Node.js 的Promise
來實現。node-bindgen
會自動處理這種轉換,允許開發者以 Promise 的形式在 JavaScript 中接收 Rust 異步操作的結果。5. 類型轉換
node-bindgen
自動處理 Rust 類型和 JavaScript 類型之間的轉換。對于簡單類型(如數字和字符串),這通常是直接的。但對于復雜類型(如結構體或枚舉),node-bindgen
生成的代碼會負責序列化和反序列化操作,確保兩種語言之間可以無縫交換復雜數據結構。總結
node-bindgen
利用 Rust 的 FFI 能力、宏系統、強類型系統和異步特性,提供了一種高效、類型安全的方式來將 Rust 代碼與 Node.js 集成。它自動處理大部分繁瑣的膠水代碼編寫工作,使得 Rust 和 Node.js 之間的交互變得更加簡單直接。這樣的設計允許開發者專注于實現應用邏輯,而無需深入底層的語言綁定細節。理解頗為淺陋,如有任何問題可私 1239604859@qq.com 討論
編譯 - android 平臺的產物
官方提供了 cargo-ndk
工具,它簡化了為 Android 使用 Rust 編寫原生代碼庫(.so 文件)的過程。
-
下載安卓 NDK,并配置到環境變量
export ANDROID_HOME=$HOME/ssd/Android/sdk
PATH="$ANDROID_HOME/ndk-bundle:$PATH"
export PATH
-
安裝 cargo-ndk
cargo install cargo-ndk
-
使用
cargo-ndk
構建你的項目
cargo ndk -t armv7-linux-androideabi -t aarch64-linux-android -o ../../MonitorTestClient/app/src/main/jniLibs build --release
-
參數解釋
-
-t
或--target
:指定目標架構 -
-o
或--output
:指定輸出目錄,這里的目錄會用于存放編譯生成的.so
文件 -
build
:是cargo
的子命令,用于編譯項目,會傳遞它以及任何附加參數給cargo build
-
編譯 - ohos 平臺的產物
官方沒有為鴻蒙系統提供類似cargo-ndk
的工具,需要手動配置編譯參數
1. 首先切換到 nightly 渠道
rustup override set nightly
2. 配置環境變量:
# huawei
export OHOS_HOME=$HOME/ssd/huawei/sdk
export OHOS_API_V=9
export OHOS_CORE_V=3.1.0
export OH_NDK_ROOT=$OHOS_HOME/openharmony/$OHOS_API_V/native
PATH="$OHOS_HOME/hmscore/$OHOS_CORE_V/toolchains:$PATH"
export PATH
單獨配置 OHOS_API_V
的好處是如果華為更新了 Native SDK
,可以更方便的動態切換
3. 創建 ohos 對應 target 的可執行腳本,例如和config.toml
中 [target.aarch64-unknown-linux-ohos] 對應
-
創建位置:
~/.cargo
-
aarch64-unknown-linux-ohos-clang.sh
#!/bin/sh
. $HOME/.bash_profile
exec $OH_NDK_ROOT/llvm/bin/clang \-target aarch64-linux-ohos \--sysroot=$OH_NDK_ROOT/sysroot \-D__MUSL__ \"$@"
-
armv7-unknown-linux-ohos-clang.sh
#!/bin/sh
. $HOME/.bash_profile
exec $OH_NDK_ROOT/llvm/bin/clang \-target arm-linux-ohos \--sysroot=$OH_NDK_ROOT/sysroot \-D__MUSL__ \-march=armv7-a \-mfloat-abi=softfp \-mtune=generic-armv7-a \-mthumb \"$@"
-
x86_64-unknown-linux-ohos-clang.sh
#!/bin/sh
. $HOME/.bash_profile
exec $OH_NDK_ROOT/llvm/bin/clang \-target x86_64-linux-ohos \--sysroot=$OH_NDK_ROOT/sysroot \-D__MUSL__ \"$@"
-
. $HOME/.bash_profile
根據實際情況進行修改,只要能拿到$OH_NDK_ROOT
即可
4. 通用配置:config.toml
-
創建位置:
~/.cargo
# 鴻蒙編譯工具鏈-目前只能手動配置:
[target.aarch64-unknown-linux-ohos]
linker = ".cargo/aarch64-unknown-linux-ohos-clang.sh"[target.armv7-unknown-linux-ohos]
linker = ".cargo/armv7-unknown-linux-ohos-clang.sh"[target.x86_64-unknown-linux-ohos]
linker = ".cargo/x86_64-unknown-linux-ohos-clang.sh"# 會概率性地失敗于exit code: 0xc0000005, STATUS_ACCESS_VIOLATION錯誤 - https://rustcc.cn/article?id=568d35d6-b782-49e9-b9b1-5d870d28f927
[profile.dev.package.compiler_builtins]
opt-level = 2[alias]
ohos-build = ["build", "-Zbuild-std", "--target=aarch64-unknown-linux-ohos", "--target=armv7-unknown-linux-ohos", "--target=x86_64-unknown-linux-ohos"]
-
[alias]
作用是使得:????
cargo ohos-build --release
等價于cargo build -Zbuild-std --target=aarch64-unknown-linux-ohos --target=armv7-unknown-linux-ohos --target=x86_64-unknown-linux-ohos --release
-
對于我們演示的 demo 工程,最終編譯命令行如下
cargo ohos-build --release --features ohos
4. Android 工程測試 rust 產物
把動態庫拷貝到 app 模塊中
src
├── androidTest
├── main
│?? ├── jniLibs
│?? │?? ├── arm64-v8a
│?? │?? │?? └── libdemo.so
│?? │?? └── armeabi-v7a
│?? │?? └── libdemo.so
創建對應包名的單例
object NativeLib {init {System.loadLibrary("demo")}external fun hello(): Stringexternal fun mapTest()
}
在 MainActivity 中調用
val nstr = NativeLib.hello()
Log.d(TAG, "onCreate: $nstr")
2024-03-19 19:32:32.207 11941-11941 MainActivity D onCreate: Hello from Rust!
5. ohos 工程測試 rust 產物
把動態庫拷貝到 entry 模塊中
entry
├── build-profile.json5
├── hvigorfile.ts
├── libs
│?? ├── arm64-v8a
│?? │?? └── libdemo.so
│?? └── armeabi-v7a
│?? └── libdemo.so
在 Index.ets 中
import hello from "libdemo.so"@Entry
@Component
struct Index {@State message: string = 'Hello World';build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)Button("計 算").fontSize(50).fontWeight(FontWeight.Bold).onClick(() => {let sum = hello.add(3, 5);this.message = "3 + 5 = " + sum.toString();})}.width('100%')}.height('100%')}
}
運行結果:
? ?? ?
6. 團隊介紹
「三翼鳥數字化技術平臺-智家APP平臺」通過持續迭代演進移動端一站式接入平臺為三翼鳥APP、智家APP等多個APP提供基礎運行框架、系統通用能力API、日志、網絡訪問、頁面路由、動態化框架、UI組件庫等移動端開發通用基礎設施;通過Z·ONE平臺為三翼鳥子領域提供項目管理和技術實踐支撐能力,完成從代碼托管、CI/CD系統、業務發布、線上實時監控等Devops與工程效能基礎設施搭建。