Rust 移動端開發體驗
在過去的幾周裏,我根據 Xlog 和 Loagan 的設計思路,使用 Rust 寫了一個移動端的跨平臺日誌庫 EZLog。在我實現這個庫的過程中,查閱了大量的問答和博客。因爲這些開發者的分享,節省了我大量的時間。所以我把我的經歷也分享出來。
如果以下任何一個點你感興趣,不要划走。
-
當下 (2022)Rust 在移動端的開發體驗如何
-
沒有 Java/Kotlin 代碼,不使用 AndroidStudio 和 Gradle, 秒級編譯並打包 Apk 運行
-
Rust 編譯靜態庫打包到 XCFramework, 發佈 Cocoapods
起因
22 年初,我有一個遊戲需求,要在 android 上實現勻速的貝塞爾曲線路徑移動。依賴 kurbo 寫了一個生成貝塞爾曲線 LUT 的命令行工具,體驗很好。於是想嘗試一下,發揮 Rust 的優勢,在移動端寫一個對性能有要求的開源庫,第一個想到的就是日誌。
可行性調查
大型公司在移動端使用 Rust
-
Google 在 2021 年將 Rust 引入 Android Rust in the Android platform
-
Mozilla 使用 Rust 編寫跨平臺應用服務組件 Firefox Application Services
-
飛書客戶端非 UI 部分使用 Rust 跨平臺實現
個人開發者在移動端的嘗試 Rust 的案例
-
Rust & cross-platform mobile development
-
RustDesk 遠程桌面應用
-
深度探索:前端中的後端
-
Publish game on Android with Macroquad
-
Rust on iOS and Mac Catalyst: A Simple, Updated Guide
更多的案例收錄,可以參看這篇 Rust 移動開發與跨平臺模式探究。從以上的例子來看,大概率是可行的,還要對具體的需求進行驗證。
項目簡述
主要功能
-
使用 mmap 做文件映射
-
認證加密
-
zlib 壓縮
-
日誌回撈
-
日誌清理
-
命令行解析工具
可行性驗證
先查找主要功能是否有對應的 Rust 開源實現。
-
memmap2
-
aead
-
flate2
開源社區早已實現好了:) 複製開源庫中的示例,藉助 android-ndk-rs。在 android 真機運行,查看日誌輸出,符合預期。項目中使用android ndk rs
的例子,可以查看examples/android_preview
cargo apk run -p ezlog_android_preview
Compiling ezlog v0.1.2 (ezlog/ezlog-core)
Compiling ezlog_android_preview v0.1.0 (ezlog/examples/android_preview)
Finished dev [unoptimized + debuginfo] target(s) in 2.39s
'lib/arm64-v8a/libezlog_android_preview.so'...
Verifying alignment of ezlog/target/debug/apk/ezlog_android_preview.apk (4)...
49 AndroidManifest.xml (OK - compressed)
1020 lib/arm64-v8a/libezlog_android_preview.so (OK)
Verification succesful
Performing Incremental InstallServing...
All files should be loaded. Notifying the device.SuccessInstall command complete in 949 msStarting: Intent { act=android.intent.action.MAIN cmp=rust.ezlog_android_preview/android.app.NativeActivity }
項目結構
├── android
│ ├── app # android 示例工程│ └── lib-ezlog # EZLog android 庫├── examples # rust 示例├── ezlog-cli # 命令行工具├── ezlog-core # 核心庫├── ios
│ ├── EZLog # EZLog iOS 庫│ ├── demo # iOS 示例工程│ └── framework # EZLog XCFramework
開發中碰到的問題及解決
iOS
iOS 端的開發流程爲
-
Rust 編碼
-
通過 cbindgen 生成頭文件
-
編譯多平臺靜態庫
-
把靜態庫和頭文件打包成 XCFramework,並依賴
-
實現 Swift 綁定
-
測試,發佈
在對比了多種依賴靜態庫的方式之後,發現 XCFramework 對多平臺的支持,更適合這個項目。更多 XCFramework 的相關資料可以看這幾篇文章 distributing universal ios frameworks as xcframeworks using cocoapods, Static libraries into XCFramework,From Rust To Swift。在項目中的構建使用,可以參看ios/b_ios.sh
腳本。
Swift 與 C 的互相調用,很多概念需要了解。在被Unmanaged
, @escaping
,@convention
,UnsafePointer
,UnsafeBufferPointer
, UnsafeMutableRawPointer
折磨許久之後,終於可以在 Swift 中拿到 Rust 的回調了。
Cocoapods 支持 XCFramework,嘗試了 SPM,找不到符號的問題沒有解決。暫時放一放。在花費了以天計的時間成本之後,終於在 Cocoapods 成功發佈。
對比一下三個包管理工具的從註冊到發佈的時間成本,從簡單到繁瑣的排序是 Cargo < Cocoapods < Maven。
隨着蘋果在 XCode14 中廢棄了 bitcode,Rust 在 iOS/MacOS 中最大的痛點也就消失了。
android FFI
android 上 Rust 與 JNI 的互調和 C/C++ 的區別不大。同樣要考慮變量的生命週期,全局的 JavaVM 引用,類加載到 JVM,native 線程在 JVM 的 attach 和 detach,詳細的示例代碼可以查看 jni-rs。
得益於 Rust 的生命週期管理,一些內存清理操作 Rust 已經處理了,不需要我們再手動的處理。
impl<'a: 'b, 'b> Drop for JavaStr<'a, 'b> {
fn drop(&mut self) {
match self.env.release_string_utf_chars(self.obj, self.internal) {
Ok(()) => {}
Err(e) => warn!("error dropping java str: {}", e),
}
}
}
android 動態庫大小
因爲用戶對 RAM,流量的關心和 Android 版本向前兼容原因,android 開發中對包體積大小是敏感的。對於 Rust 編譯產物體積較大的問題,在查閱了 Minimizing Rust Binary Size 文章後,在 release 模式開啓優化。
我們將 XLog,Logan 都加入到 Demo 的依賴中,對比 release apk 中的 64 位動態庫大小,如圖所示
Rust 編譯的動態庫大小是最大的,不過也沒有太過於誇張,隨着手機硬件的進一步提升,應該不會是制約 Rust 在 android 中應用的原因。
崩潰?
不同的情況下,需要不同的方式
-
在開發,測試階段,由於編寫錯誤或者代碼混淆等問題,導致類,方法無法找到的情況,我們希望程序能直接崩潰,儘早的暴露問題。
-
產品上線後,因爲適配問題或者用戶操作導致 bug 出現,導致某個模塊的不可用。我們希望錯誤只限定在這個模塊,不影響其他的功能,並上報這個錯誤
Rust 的錯誤分爲可恢復和不可恢復的錯誤。Rust 初始化線程的 panic 會導致進程的退出。一些解決方法:
-
只在需要崩潰時使用 panic 宏
-
在 Clippy 中加入使用 unwrap 和 except 的警告
-
替換 [start..end] 爲 get(start..end)
-
FFI 中 catch_unwind
即使自己的代碼中沒有 panic 調用,依賴庫中也可能會調用。所以需要提供在生產環境中崩潰排查的能力。
崩潰排查
初始化時設置 panic hook。
#[cfg(not(feature = "backtrace"))]
fn hook_panic() {
std::panic::set_hook(Box::new(|p| {
event!(panic & format!("ezlog: \n {p:?}"));
}));
}
在崩潰時會回調拿到 PanicInfo
PanicInfo { payload: Any { .. }, message: Some(asdf), location: Location { file: "ezlog-core/src/lib.rs", line: 119, col: 5 },
PanicInfo 中有錯誤信息,panic 的文件路徑和代碼位置。這樣能粗略的排查 bug。如果想拿到具體的堆棧信息,我們還需要依賴 backtrace,這樣最後動態庫的大小會增加 80KB 左右
#[cfg(feature = "backtrace")]
fn hook_panic() {
std::panic::set_hook(Box::new(|p| {
let bt = Backtrace::new();
event!(panic & format!("ezlog: \n {p:?} \n{bt:?} \n"));
}));
}
PanicInfo { payload: Any { .. }, message: Some(asdf), location: Location { file: "ezlog-core/src/lib.rs", line: 119, col: 5 }, can_unwind: true } 0: backtrace::backtrace::trace_unsynchronized 1: backtrace::backtrace::trace 2: backtrace::capture::Backtrace::create 3: backtrace::capture::Backtrace::new 4: ezlog::init::{{closure}} 5: std::panicking::rust_panic_with_hook 6: std::panicking::begin_panic_handler::{{closure}} 7: std::sys_common::backtrace::__rust_end_short_backtrace 8: _rust_begin_unwind 9: core::panicking::panic_fmt 10: ezlog::init 11: _ezlog_init 12: _$s5EZLog18ezlogInitWithTraceyyF 13: _$s4demo7DemoAppVACycfC 14: _$s4demo7DemoAppV7SwiftUI0C0AadEPxycfCTW 15: <unknown> 16: _$s4demo7DemoAppV5$mainyyFZ 17: _main
PanicInfo { payload: Any { .. }, message: Some(asdf), location: Location { file: "ezlog-core/src/lib.rs", line: 119, col: 5 }, can_unwind: true }
0: <unknown>
1: <unknown>
2: <unknown>
3: <unknown>
4: <unknown>
5: <unknown>
6: <unknown>
7: Java_wtf_s1_ezlog_EZLog_init
8: art_quick_generic_jni_trampoline
9: art_quick_invoke_static_stub10: _ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE11: _ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE12: MterpInvokeStatic13: mterp_op_invoke_static14: _ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.335106805463763666415: _ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadERKNS_20CodeItemDataAccessorEPNS_11ShadowFrameEPNS_6JValueE16: _ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE17: MterpInvokeStatic18: mterp_op_invoke_static19: _ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.335106805463763666420: _ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadERKNS_20CodeItemDataAccessorEPNS_11ShadowFrameEPNS_6JValueE
2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Build fingerprint: 'google/flame/flame:12/SP2A.220305.012/8177914:user/release-keys'2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Revision: 'MP1.0'2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: ABI: 'arm64'2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Timestamp: 2022-07-07 15:25:50.559643355+08002022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Process uptime: 0s2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Cmdline: wtf.s1.ezlog.demo2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: pid: 14112, tid: 14112, name: f.s1.ezlog.demo >>> wtf.s1.ezlog.demo <<<2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: uid: 102872022-07-07 15:25:50.712 14141-14141/? A/DEBUG: signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x0 0000000000000000 x1 0000000000003720 x2 0000000000000006 x3 0000007fc9d511202022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x4 00000000ebad808a x5 00000000ebad808a x6 00000000ebad808a x7 00000000ebad808b2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x8 00000000000000f0 x9 f7d7529acf61ddf5 x10 0000000000000000 x11 ffffff80fffffbdf2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x12 0000000000000001 x13 0000007fc9d50fe8 x14 0000000000000000 x15 00000000000000082022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x16 0000007b425b6050 x17 0000007b42592db0 x18 0000007b523fa000 x19 00000000000037202022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x20 0000000000003720 x21 00000000ffffffff x22 0000000000000001 x23 00000000000000012022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x24 000000782576bad8 x25 00000078256bb708 x26 000000000000000b x27 0000007b515530002022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x28 0000007fc9d514a0 x29 0000007fc9d511a02022-07-07 15:25:50.712 14141-14141/? A/DEBUG: lr 0000007b42545aa0 sp 0000007fc9d51100 pc 0000007b42545acc pst 00000000000000002022-07-07 15:25:50.712 14141-14141/? A/DEBUG: backtrace:2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #00 pc 000000000004facc /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: cd7952cb40d1a2deca6420c2da7910be)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #01 pc 00000000000b3f1c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #02 pc 00000000000b22a8 /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #03 pc 00000000000b2164 /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #04 pc 00000000000b203c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #05 pc 00000000000b142c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #06 pc 00000000000b1e6c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #07 pc 00000000000c5ad0 /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #08 pc 000000000007550c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #09 pc 00000000000741ec /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so (Java_wtf_s1_ezlog_EZLog_init+28)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #10 pc 00000000002d4044 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+148) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #11 pc 00000000002ca9e8 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+568) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #12 pc 00000000002ee6b8 /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+320) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #13 pc 000000000040ade4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+820) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #14 pc 000000000076d4b8 /apex/com.android.art/lib64/libart.so (MterpInvokeStatic+3812) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #15 pc 00000000002c5014 /apex/com.android.art/lib64/libart.so (mterp_op_invoke_static+20) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #16 pc 00000000000790a2 [anon:dalvik-classes.dex extracted in memory from /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/base.apk] (wtf.s1.ezlog.EZLog.initWith+10)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #17 pc 000000000027d840 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.llvm.3351068054637636664)+644) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #18 pc 000000000035a9e4 /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*)+148) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #19 pc 000000000040b05c /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+1452) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #20 pc 000000000076d4b8 /apex/com.android.art/lib64/libart.so (MterpInvokeStatic+3812) (BuildId: 46df93bc978921840e5b428398c66a57)
可以發現,android 的堆棧輸出裏並沒有完整的 Rust 調用堆棧,我嘗試通過 add2line 的方法,但沒有成功。
使用 Rust 的體驗
- 學習曲線陡峭
我在讀完 Rust 官方文檔 後,又跟着 Rust 第一步 敲了一遍代碼。發現這只是個開始,在看了一遍 Rust Nomicon Rust Nomicon 中文和 Async Book 之後,就已經想放棄了。太多晦澀的內容了,比如:Unsafe
, PhantomData
,Send and Sync
, Pin
...
直接一邊寫項目一邊學吧。
在實際的項目中,開源社區提供的解決方案有時會更合適。比如:crossbeam_channel
, tokio
, once_cell
, thiserror
等等,在 crates.io 上看開源項目文檔也是學習的一部分。
- 文檔
Rust 大部分庫的文檔,對讀者很友好。詳盡的描述,完整的示例。和 Java 的 concurrent 包的文檔讀着一樣舒服。看這樣的文檔,好像就坐在作者的大腦皮層上看他寫代碼。反觀 iOS 和 Andriod 的一些文檔,字裏行間彷彿寫着 “你猜猜看這個怎麼用”。
- 單元測試
易用的單元測試,隨時添加#[test]
就可以寫一個測試用例。對比在 android 項目寫一個測試用例,我還要先去搜索,有哪些依賴是需要添加的。用 Rust 做開發,和系統 API 無關的業務邏輯,在桌面環境完成並測試,最後到對應的客戶端驗證。效率比起所有邏輯都在手機上驗證高多了。
- 錯誤處理
Rust 可恢復錯誤強制處理,當我第一次看到 Result 那巨長的方法列表,就像看到大閘蟹身上一圈一圈的繩子,要不一剪刀 (unwrap) 了事?我們不想在庫裏直接 panic,就只能看文檔了。避免圖片過長,用了 3 張圖顯示:
習慣了 try catch 的錯誤處理方式,剛開始處理 Result/Option 是懵的。通過文檔示例和 Clippy 提示,花一些時間就能掌握。當我開始熟悉這樣的錯誤處理方式,我不時會懷疑,之前是怎麼在 Java/Kotlin 中只用 try catch 就能寫完那些代碼的。。。
- 生命週期和所有權
Rust 編譯器的所有權 / 生命週期檢查,必須在編碼的時候就考慮對象的生命週期和內存的分配問題。對比 C/C++ 將編碼的痛苦前置了。對比有 GC 的語音,心智成本大幅提高。
- 編譯時間
debug 模式下
cargo clean && cargo build -p ezlogFinished dev [unoptimized + debuginfo] target(s) in 14.19s
使用緩存,更改一行代碼,再次編譯
cargo build -p ezlogFinished dev [unoptimized + debuginfo] target(s) in 1.46s
無緩存 release 模式下
target `aarch64-apple-ios`
Finished release [optimized] target(s) in 45.43s
target `aarch64-apple-ios-sim`Finished release [optimized] target(s) in 40.83s
target `x86_64-apple-ios`Finished release [optimized] target(s) in 39.26s
Building armeabi-v7a (armv7-linux-androideabi)
Finished release [optimized] target(s) in 47.64s
Building arm64-v8a (aarch64-linux-android)
Finished release [optimized] target(s) in 50.71s
在小型項目使用 Rust,基本沒有編譯摸魚時間。對於移動端開發來說,有過無編譯優化的中型項目的 Gradle 或者 XCode 構建體驗,這點編譯時間都不算事。如果想要加速 Android 端驗證的效率,那麼最好單獨新增 crate,用 android-ndk-rs 這樣的工具,動態獲取 target,因爲沒有 Java/Kotlin 代碼,跳過 gradle 的構建直接生成 APK 部署。
總結
學習成本
對於新手,Rust 文檔友好,社區活躍。還有官方支持的的包管理工具 Cargo,代碼檢查工具 Clippy。個人認爲上手難度要低於 C++。
開發效率
Rust 的學習曲線陡峭,編譯器對借用,生命週期的檢查。導致新手在初期開發效率低下。隨着對語言熟悉程度的提高,以及易用的測試框架,方便的跨平臺編譯,可以彌補前期的開發效率劣勢,加上 Rust 內存安全的特點,也降低了後期項目維護的難度。
適用
從 0 開始構建一個新的跨平臺 App,所有的非 UI 邏輯,都使用 Rust 實現,構建成單一的靜態 / 動態庫,提供 FFI 支持。Flutter/RN/Compose/SwiftUI 等等的框架做 UI 交互。在這種場景下使用 Rust 很合適。
但是如果想在現有的 App 中大量使用 Rust,那麼二進制依賴是一個問題,如果有多個業務 crate,打包成多個二進制文件。那麼各個 Rust 的產物可能會包含相同的依賴,比如 libc, syn, cfg-if, memchr 等等。最後的包大小會隨着業務的增多快速膨脹。
後續
項目需要繼續完善的地方
-
Unsafe 代碼審查
-
提供詳細事件監控回調
-
文檔完善
-
示例完善
-
性能測試
-
GUI 支持
除了實現了 android,iOS 的跨平臺,還有一些其他的使用場景。
-
兼容 Windows,滿足桌面端的需求。
-
實現 Dart 的 FFI,提供 Flutter SDK。
-
給其他的庫,遊戲框架提供 log 插件,比如:Bevy,Tauri。
Tips
- 添加 config,快速切換 target
在項目中添加 ./.cargo/config,並配置
[build]
target = "aarch64-linux-android"...
註釋掉其他的 target 後,在 vscode 中雙擊 shift 輸入 >> 找到 reload workspace,就可以切換到對應的 target 了。
- 使用 thiserror 庫來處理自定義的錯誤類型,可以省掉很多模版代碼。
參考文章及開源項目
-
mozilla multi platform arch design
-
Android Rust crates
-
Android Rust Introduction
-
Flutter Rust Bridge
FFI
-
rust ffi doc
-
how to call rust functions from c on linux h37
-
how does rust ffi pass parameters of type vec u8
-
How to return byte array from rust to c
android
-
Rust bindings to the JNI
-
JNI crate exapmles
-
Implementing JNI_OnLoad
-
Rust on Android
-
cargo ndk
-
Minimizing Rust Binary Size
-
Rust 中的 bin, lib, rlib, a, so 概念介紹
iOS
-
Create your own CocoaPods library
-
Building and Deploying a Rust library on iOS via Mozilla
-
Rust on iOS and Mac Catalyst
-
recipe swift rust callback
其他
-
Backtrace Capture on mobile in production enviroment
-
Cannot get backtrace on Android
-
Gimli doesn't support iOS
-
Transform backtrace to string during catch unwind
-
Android backtraces don't work since gimli is used as the symbolizer
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/B02nZn3cjvBhyirJSHjeMg