C++幼女先輩

プログラミング成分多め

ios開発にRustを使う 環境構築編

iOSでは、Objective-CC++も書ける)、Swift しか使えません
が、C++の 異言語間インタフェース FFIを使えば、Objectibe-CやSwiftから C++を通して Rustを実行出来ます
そのための環境構築

とりあえず xcodeでのビルドを行う

Rustのインストール

Rust自体は パッケージマネージャから出来るが、その場合だと Rustのバージョン管理などが不便なので
もしインストールしている場合は 一旦削除し、Rustupをインストールする

環境構築は下記参照
Building and Deploying a Rust library on iOS

書いてる通り実行すれば出来るけど一応簡単に

xcode build toolのインストール

xcode-select --install

にて、xcodeのインストール

rustupのインストール

curl https://sh.rustup.rs -sSf | sh

rustupをインストールする

iosiosシミュレータ用のToolChainのインストール

rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios

iosだけでなく、iosシミュレータも入れておくと開発が楽

Cargoのインストール

cargo install cargo-lipo

パッケージマネージャ&ビルドツールのCargoは是非入れておくべきだ

環境変数

bash_profile 等に cargoのパスの設定が入るので、sourceして環境変数を読み込み直す

source ~/.bash_profile

サンプル

これまた、上記URLのとおりにすればよい

Rustファイル作成

lib.rs

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

#[no_mangle]
pub extern fn rust_greeting(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_greeting_free(s: *mut c_char) {
    unsafe {
        if s.is_null() { return }
        CString::from_raw(s)
    };
}

Rustの文法は今回は省略
rust_greeting関数は 引数に文字列を受け取り "Hello " を頭につけて返す
rust_greeting_freeは 作成した文字列を開放する

FFI用ヘッダ

greetings.h

#include <stdint.h>

const char* rust_greeting(const char* to);
void rust_greeting_free(char *);

上記のRust関数を呼び出すためのヘッダである

toml

Rustのビルドのしかたを教えなければならない

Cargo.toml

[package]
name = "greetings"
version = "0.1.1"
authors = ["fluffyemily <fluffyemily@mozilla.com>"]
description = "Example static library project built for iOS"
publish = false

[lib]
name = "greetings"
crate-type = ["staticlib", "cdylib"]

今回は、ライブラリを作成するので、create-typeは スタティックライブラリとダイナミックライブラリを指定した

rustのビルド

cargo lipo --release

lipoを使うと便利である
今はToolChainに iOS関連をいれているので、それらがビルドされる
また、Universalライブラリという、全てのToolChainのライブラリがまとまったものが作成されるので
それを指定すれば、エミュレータでも実機でも、32bitでも64bitでも 設定変更不要で便利だ

xcodeでプロジェクト作成

とりあえずSinglePageプロジェクトを作成し
先程の作成した Universalライブラリと、libresolvをリンクする
また、リンクパスに Universalの場所を指定する

Swift Bridgeファイル

RustからC++へのブリッジは 上記 ライブラリ作成でC++とアクセス可能だ
Objective-CC++と互換性があり そのまま実行できる
SwiftとC++を相互呼び出しするために Bridgeを作る

#ifndef Greetings_Bridging_Header_h
#define Greetings_Bridging_Header_h

#import "greetings.h"

#endif

この、greetings.hは 先程作った Rust関数のC言語ヘッダである
importで怒られると思うが、ヘッダのSearchPathを追加すれば良い

Swiftコード作成

RustGreetings.swift

class RustGreetings {
    func sayHello(to: String) -> String {
        let result = rust_greeting(to)
        let swift_result = String(cString: result!)
        rust_greeting_free(UnsafeMutablePointer(mutating: result))
        return swift_result
    }
}

RustのBridgeをした関数 rust_greeting、rust_greeting_free を呼び出すWrapクラス
多少面倒だが、こうやって呼び出すことが出来る

これを実行するために ViewControllerの viewDidLoadedに 上記を呼び出すコードを書く

let rustGreetings = RustGreetings()
print("\(rustGreetings.sayHello(to: "world"))")

実行

実行すると Hello World とコンソールに出るはずだ