RustでMATLAB C APIを呼び出してMATファイルを作る

唐突だがMATLABのMATファイルをRustで作りたくなった。
pythonだとscipy.io.savematとscipy.io.loadmatで保存と読み込みができる。
Rustだとどうなのだろうと思って調べてみたところ、こんな[クレート](
https://docs.rs/matfile/0.2.0/matfile/)を発見した。
良さそうと思って使ってみたが、どうもまだ読み込みしかできないようだ。

自作するのもめんどくさいので、[MATLAB C API](https://jp.mathworks.com/help/matlab/matlab-c-api-to-read-mat-file-data.html) を使えないかな〜と思って試行錯誤したメモ帳。


# Cで書いた関数をRustで呼ぶ

Rust はCの関数を[FFI](https://doc.rust-jp.rs/the-rust-programming-language-ja/1.9/book/ffi.html)で呼び出せる。

``` C:src/hello.c
#include <stdio.h>

int hello (void) {
	printf ("Hello World! [from C]\n");
	return 0;
}
```

Rustからは以下のようにして呼ぶ。

```Rust:src/main.rs

extern {
    fn hello ();
}

fn main() {
    unsafe { hello () };
    println! ("Hello World! [from Rust]");
}
```

cargo でCのソースも一緒にビルドするために、Cargo.toml を以下のように書く。

```toml:Cargo.toml
[package]
name = "ffi_c"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libc = "0.2"

[build-dependencies]
cc = "1.0"
```

また、Cargo.tomlと同じディレクトリにbuild.rsというファイルを作る。

```Rust:build.rs

extern crate cc;

fn main () {

    cc::Build::new()
        // .cpp(true)  // in the case of C++ 
        .file ("src/hello.c")
        .compile ("libfoo.a");
}
```

これで cargo build すると libfoo.a が作られ、本体のプログラムがコンパイル&リンクされる。


# Cで書いた引数・返り値ありの関数をRustで呼ぶ

引数や返り値を与えたい場合は以下のようにする。

```c:src/double.c
double double_input (double input) {
	return 2 * input;
}
```
```Rust:src/smain.rs
extern crate libc;

extern {
    fn hello ();
    fn double_input (input: libc::c_double) -> libc::c_double;
}

fn main() {
    unsafe { hello () };
    println! ("Hello World! [from Rust]");


    let input = 4.0;
    let output = unsafe { double_input (input) };
    println! ("{} * 2 = {}", input, output);
}
```
```Rust:build.rs
extern crate cc;

fn main () {
    cc::Build::new()
        // .cpp(true)  // in the case of C++ 
        .files (vec!["src/hello.c","src/double.c"])
        .compile ("libfoo.a");
}
```

[libcクレート](https://crates.io/crates/libc) でCの型をが定義されているので、これを用いる。

複数のCファイルを用いる場合は、 build.rs のfileをfiles に変えて引数をイテレータにすればいいらしい。




# MATファイルを作る

MATLAB C API のサンプルである matlabroot/extern/examples/eng_mat/matcreat.c を使ってMATファイルを作らせる。
matcreat.c をsrcフォルダにコピーして、main関数をmatcreat関数に名前を変えておく。

```Rust:src/main.rs

extern crate libc;

extern {
    fn matcreat () -> libc::c_int;
}

fn main() {
    unsafe { matcreat () };
}
```

matcreat.cはMATLABのライブラリを呼び出さないといけないので、それをbuild.rsに追記する。

```Rust:build.rs
extern crate cc;

fn main () {
    println!("cargo:rustc-link-search=native=matlabroot/bin/maci64");
    println!("cargo:rustc-link-lib=mx");
    println!("cargo:rustc-link-lib=mat");

    cc::Build::new()
        // .cpp(true)  // in the case of C++ 
        .file ("src/matcreat.c")
//        .object ("matlabroot/bin/maci64/libmx.dylib")
//        .object ("matlabroot/bin/maci64/libmat.dylib")
        .include ("matlabroot/extern/include")
        .compile ("libfoo.a");
}
```

私はMacを用いているのでmaci64ディレクトリに色々入っているが、LinuxやWindowsだと別の名前になる。

途中でコメントアウトされているobject() は静的ライブラリのパスを引数にとるらしく、動的ライブラリのパスを渡したらwarningを吐かれた。

これで無事matファイルがされる。



# 終わりに
できればRustから直でMATファイルを読み書きできるやつが欲しい。



# 参考サイト
https://scrapbox.io/yuta0801/Rust%E3%81%A7C%2FC++%E3%81%AE%E9%96%A2%E6%95%B0%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B
http://mmi.hatenablog.com/entry/2017/02/28/213656
https://qiita.com/kjunichi/items/31aef0cf3f4f7fe6dc32
https://teratail.com/questions/157950