402's Blog

Rust on iOS

本文为 Medium 文章 Rust on iOS 的翻译版本,供喜欢这门技术的开发者阅读使用,请不要用于任何商业用途。原文理解并不难,所以我建议你可以先尝试阅读一下英文原版。

你可能听说过 Rust,它是一门为内存安全和速度而设计的系统级编程语言。由 Mozilla 打造,旨在提供下一代高性能跨平台软件的能力。如果你还没有听过这门语言,我建议先看一下(入门教程)great learning meterial,但请记住,你可能要花一点时间才会对这门语言感兴趣和欣赏它,因此我建议可以多尝试一些而不只是写一个「Hello World」。 如果你是一名 iOS 开发者你可能会问 怎样做 为什么 在 iOS 开发中使用 Rust。这篇文章将尽可能解释怎样做。至于为什么,对于我们 Visly 来说最重要的原因是它能够在 Android 和 iOS 平台间以一种高性能和安全的方式共享代码,又比 C++ 容易使用得多。 (我们也为 Android 写了类似的文章

准备开始

在我们开始之前我们需要确保我们已经安装了 Rust 工具链。我们将假定你已经安装了 iOS 工具链,如果没有,你需要下载 Xcode 并且根据其他的 iOS 入门教程配置好 iOS 环境。可以通过运行如下命令:xcode-select --install你可以确保环境已经安装。本文均假设你运行的是 macOS,因为这是编译 iOS 的必要条件。下一步将在我们的操作系统上安装 Rust。Rustup 让这一些只需要一行代码这么简单。curl https://sh.rustup.rs -sSf | sh你可以通过 rustc --version 命令验证 Rust 是否正确安装并且位于你的 PATH 路径下。一旦 Rust 在你的操作系统下安装成功我们需要确保 Rust 知道如何构建才能支持 iOS 架构。Rust 可以编译支持所有架构,但这不是默认行为。运行如下命令添加合适的架构。rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios在安装 rustup 和 rustc 的同时我们也安装了 cargo 在你的系统上。cargo 是 rust 包管理器,很像 Cocoapods、homebrew、或者 npm,它允许我们提价依赖和全局二进制包到我们的系统上。我们使用 cargo 安装 cargo-lipo —— 一个编译 universal iOS 二进制包的工具,也需要安装 cbindgen —— 一个从 rust 生成 C 头文件的工具。这可以帮助我们管理在编译和链接 iOS 库时的所有复杂逻辑。cargo install cargo-lipo cargo install cbindgen到此,如果你可以成功运行 cargo lipo --help cbindgen --help,就可以确定环境安装成功。

Hello World

我们使用 Rust 编译一个小的 hello world 应用!我们先来创建一个 rust 库,然后继续创建我们的 Xcode 工程。    mkdir rust-ios-example      cd rust-ios-example  cargo new rust --lib cd rust上述代码将创建一个由 cargo 管理的基础 rust 库,我们后续将在 Xcode 工程中使用它。--lib 标记告诉 cargo 我们想要创建一个库(Library),而不是一个可执行二进制文件,在我们新创建的工程文件夹中我们可以找到 Cargo.toml 这个文件,它的作用类似 Podfile 文件定义了你的库以及依赖的 metadata 信息。你还会找到 src 文件夹包含了 rust 源码。这个目录只包含 lib.rs 文件,文件中只包含一个简单的测试方法。删掉文件中的内容,替换成如下代码:     use std::os::raw::{c_char}; use std::ffi::{CString, CStr};  #[no_mangle] pub extern fn rust_hello(to: *const c_char) -> *mut c_char {     let c_str = unsafe { CStr::from_ptr(to) };     let recipient = match c_str.to_str() {         Err(_) => "there",         Ok(string) => string,     };     CString::new("Hello ".to_owned() + recipient).unwrap().into_raw() }  #[no_mangle] pub extern fn rust_hello_free(s: *mut c_char) {     unsafe {         if s.is_null() { return }         CString::from_raw(s)     }; }如上有两个基本方法,其中之一通过给定字符串构造一个新的字符串,另一个释放了给定的那个字符串。因此 rust 和 C 或 C++ 一样,我们在 Swift 和 Rust 间传递对象时,必须手动管理内存。后续我们将使用一些我们在 Visly 中的开发范式来简化这个操作。还有一些值得注意的事情,由于我们和 Swift 交互,我们需要使用 C 调用转换,这意味着我们需要告诉 rust 不要去 mangle【译者注,参见 C++  的 name mangling】任何方法名(#[no_mangle]),我们还需要转换 rust 字符串和 C 字符串。由于 Kotlin 和 Rust 间的转换胶水代码可以非常的小,所以在一个更大的应用中,这不是一个问题。在我们开始尝试在 Swift 工程中使用这些代码之前,我们需要生成 C 头文件以便 Swift 可以理解这些代码。通过之前安装的 cbindgen 来实现。cbindgen src/lib.rs -l c > rust.h最后我们需要编译 rust 库。打开 Cargo.toml 文件添加如下代码到文件的末尾。[lib] name = "rust" crate-type = ["staticlib", "cdylib"]上述代码告诉 cargo-lipo 生成何种类型的二进制包以及如何命名这个包。现在我们可以运行 cargo lipo --release。如果一切顺利的话你应该可以在 target/universal/release/ 目录下找到 librust.a 文件。

Xcode

到了开始一个新的 Xcode 工程,并在模拟器中测试它的时候了。先去 Xcode标准工程配置中,我们将使用 Swift ,当然你也可以使用 Objective-C。我们将工程命名为 hello-rust 保存项目到 rust-ios-example 根目录中。打开 ViewController.swift 替换其内容为如下代码。 import UIKit  class ViewController: UIViewController {     override func viewDidLoad() {         super.viewDidLoad()                  let result = rust_hello("world")         let swift_result = String(cString: result!)         rust_hello_free(UnsafeMutablePointer(mutating: result))         print(swift_result)     } }如果这个时候尝试编译应用,会在启动时报错:无法找到 rust_hello 和 rust_hello_free。因为我们没有包含原生库到工程中。使用如下代码引入 rust 库到 Xcode 工程中。     cd ~/rust-ios-example # or wherever you created this project  mkdir hello-rust/libs mkdir hello-rust/include  cp rust/rust.h hello-rust/include cp rust/target/universal/release/librust.a hello-rust/libs现在打开 Xcode 「build setting」 面板,添加上述库为 linked library,设置头文件为 「bridging header」,设置正确的 「library search path」 和 「header search path」。

我们可以再次编译和运行我们的应用,我们将看到「Hello World」在日志中输出。请记住这里我们还没有自动化的处理编译 Rust 代码,以及将二进制结果放到正确的地址,所以每次重新编译 rust code 后你需要重新引用头文件和二进制包。

Automating the process

自动化拷贝二进制包这个过程可以由下面这个 bash 脚本轻松的办到。    #!/bin/sh  cd rust  cargo lipo --release cbindgen src/lib.rs -l c > rust.h  cp rust.h ../hello-rust/include cp target/universal/release/librust.a ../hello-rust/libs保存上述代码到 rust-ios-example/install.sh 并在任何一次更新了 rust 代码后运行它,编译和安装它到你的 Xcode 工程中。如果你想 get fancy 你可以在将它设置为 Xcode 的编译步骤,每次编译 Xcode 工程时它都会被执行。

下一步

当上述代码工作时,并不是很容易掌握。在更大的工程中我们希望压缩那些用于 Swift 和 Rust 之间通信的丑陋二进制数字。在接下来的 post 中我将讲一下我们在构建 Visly 时用到的开发范式来简化这个工作。 在 Github 上可以找到本教程的示例代码,如果你计划在 iOS 开发中使用 Rust,这将是一个很好的起点。如果你有任何问题可以给我发推

评论