# 其他語言中的 Rust
> [rust-inside-other-languages.md](https://github.com/rust-lang/rust/blob/stable/src/doc/book/rust-inside-other-languages.md)
commit 024aa9a345e92aa1926517c4d9b16bd83e74c10d
> **注:** 1.7.0-stable 將此章節去掉了,因此內容可能不具有時效性,這里我們暫時保留。
作為我們的第三個項目,我們決定選擇一個可以展示Rust最強實力的功能:缺少實質的運行時。
當組織增長,他們越來越依賴大量的編程語言。不同的編程語言有不同的能力和弱點,而一個多語言棧讓你在某個特定的編程語言的優點起作用的時候能使用它,當它有缺陷時使用其他編程語言。
一個非常常見的問題是很多編程語言在程序的運行時性能是很差的。通常來講,使用一個更慢的,不過提供了更強大的程序員生產力的語言是一個值得的權衡的問題。為了幫助緩和這個問題,它們提供了一個用C編寫部分你的系統,然后接著用高級語言編寫的代碼調用C代碼。這叫做一個“外部函數接口”,通常簡寫為“FFI”。
Rust 在所有兩個方向支持 FFI:它可以簡單的調用C代碼,而且至關重要的,它也可以簡單的*在*C中被調用。與 Rust 缺乏垃圾回收和底層運行時要求相結合,這讓 Rust 成為一個當你需要嵌入其他語言中以提供一些額外的活力時的強大的候選。
這有一個全面[專注于FFI的章節](#)和詳情位于本書的其他位置。不過在這一章,我們會檢查這個特定的FFI用例,通過 3 個例子,分別在 Ruby,Python 和 JavaScript 中。
### 問題
有很多不同的項目我們可以選擇,不過我們將選擇一個 Rust 相比其他語言有明確優勢的例子:數值計算和線程。
很多語言,為了一致性,將數字放在堆上,而不是放在棧上。特別是在專注面向對象編程和使用垃圾回收的語言中,堆分配是默認行為。有時優化會棧分配特定的數字,不過與其依賴優化器做這個工作,我們可能想要確保我們總是使用原始類型而不是使用各種對象類型。
第二,很多語言有一個“全局解釋器鎖(GIL)”,它在很多情況下限制了并發。這在安全的名義下被使用,也有一定的積極影響,不過它限制了同時可以進行的工作的數量,這是一個很負面的影響。
為了強調這兩方面,我們將創建一個大量使用這兩方面的項目。因為這個例子關注的是將Rust嵌入到其他語言中,而不是問題自身,我們只使用一個玩具例子:
> 啟動10個線程。在每個線程中,從1數到500萬。在所有10個線程結束后,打印“done”。
我選擇 500 萬基于我特定的電腦。這里是一個例子的 Ruby 代碼:
~~~
threads = []
10.times do
threads << Thread.new do
count = 0
5_000_000.times do
count += 1
end
count
end
end
threads.each do |t|
puts "Thread finished with count=#{t.value}"
end
puts "done!"
~~~
嘗試運行這個例子,并選擇一個將運行幾秒鐘的數字。基于你電腦的硬件配置,你可能需要增大或減小這個數字。
在我的系統中,運行這個例子花費`2.156`秒。并且,如果我用一些進程監視工具,像`top`,我可以看到它只用了我的機器的一個核。這是 GIL 在起作用。
雖然這確實是一個虛構的程序,你可以想象許多問題與現實世界中的問題相似。為了我們的目標,啟動一些繁忙的線程來代表一些并行的,昂貴的計算。
### 一個Rust庫
讓我們用Rust重寫這個問題。首先,讓我們用Cargo創建一個新項目:
~~~
$ cargo new embed
$ cd embed
~~~
這個程序在Rust中很好寫:
~~~
use std::thread;
fn process() {
let handles: Vec<_> = (0..10).map(|_| {
thread::spawn(|| {
let mut x = 0;
for _ in 0..5_000_000 {
x += 1
}
x
})
}).collect();
for h in handles {
println!("Thread finished with count={}",
h.join().map_err(|_| "Could not join a thread!").unwrap());
}
}
~~~
一些代碼可能與前面的例子類似。我們啟動了 10個 線程,把它們收集到一個`handles`向量中。在每一個線程里,我們循環 500 萬次,并每次給`_x`加一。最后,我們同步每個線程。
然而現在,這是一個 Rust 庫,而且它并沒有暴露任何可以從C中調用的東西。如果現在我們嘗試在別的語言中鏈接這個庫,這并不能工作。我們只需做兩個小的改變來修復這個問題,第一個是修改我們代碼的開頭:
~~~
#[no_mangle]
pub extern fn process() {
~~~
我們必須增加一個新的屬性,`no_mangle`。當你創建了一個Rust庫,編譯器會在輸出中修改函數的名稱。這么做的原因超出了本教程的范圍,不過為了其他語言能夠知道如何調用這些函數,我們需要禁止這么做。這個屬性將它關閉。
另一個變化是`pub extern`。`pub`意味著這個函數應當從模塊外被調用,而`extern`說它應當能被C調用。這就是了!沒有更多的修改。
我們需要做的第二件事是修改我們的`Cargo.toml`的一個設定。在底部加上這些:
~~~
[lib]
name = "embed"
crate-type = ["dylib"]
~~~
這告訴Rust我們想要將我們的庫編譯為一個標準的動態庫。默認,Rust 編譯為一個“rlib”,一個 Rust 特定的格式。
現在讓我們構建這個項目:
~~~
$ cargo build --release
Compiling embed v0.1.0 (file:///home/steve/src/embed)
~~~
我們選擇了`cargo build --release`,它打開了優化進行構建。我們想讓它越快越好!你可以在`target/release`找到輸出的庫:
~~~
$ ls target/release/
build deps examples libembed.so native
~~~
那個`libembed.so`就是我們的“共享目標(動態)”庫。我們可以像任何用C寫的動態庫使用這個文件!另外,這也可能有`embed.dll` (Microsoft Windows) 或`libembed.dylib` (Mac OS X),基于不同的平臺。
現在我們構建了我們的 Rust 庫,讓我們在 Ruby 中使用它。
### Ruby
在我們的項目中打開一個`embed.rb`文件。并這么做:
~~~
require 'ffi'
module Hello
extend FFI::Library
ffi_lib 'target/release/libembed.so'
attach_function :process, [], :void
end
Hello.process
puts 'done!'
~~~
在我們可以運行之前,我們需要安裝`ffi`gem:
~~~
$ gem install ffi # this may need sudo
Fetching: ffi-1.9.8.gem (100%)
Building native extensions. This could take a while...
Successfully installed ffi-1.9.8
Parsing documentation for ffi-1.9.8
Installing ri documentation for ffi-1.9.8
Done installing documentation for ffi after 0 seconds
1 gem installed
~~~
最后,我們可以嘗試運行它:
~~~
$ ruby embed.rb
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
Thread finished with count=5000000
done!
done!
$
~~~
哇哦,這很快欸!在我系統中,它花費了`0.086`秒,而不是純 Ruby 所需的 2 秒。讓我們分析下我們的 Ruby 代碼:
~~~
require 'ffi'
~~~
首先我們需要`ffi`gem。這讓我們可以像 C 庫一樣使用 Rust 的接口。
~~~
module Hello
extend FFI::Library
ffi_lib 'target/release/libembed.so'
~~~
`Hello`模塊被用來從共享庫中附加原生函數。在其中,我們`extend`必要的`FFI::Library`模塊,接著調用`ffi_lib`加載我們的動態庫。我們僅僅傳遞我們庫存儲的路徑,它是我們之前見過的,是`target/release/libembed.so`。
~~~
attach_function :process, [], :void
~~~
`attach_function`方法由`ffi`gem提供。它用來把我們Rust中`process()`連接到Ruby中同名函數。因為`process()`沒有參數,第二個參數是一個空數組,并且因為它也沒有返回值,我傳遞`:void`作為最后的參數。
~~~
Hello.process
~~~
這是實際的Rust調用。我們的`module`和`attach_function`調用的組合設置了環境。它看起來像一個Ruby函數,不過它實際是Rust!
~~~
puts 'done!'
~~~
最后,作為我們每個項目的要求,我們打印`done!`。
這就是全部!就像我們看到的,連接兩個語言真是很簡單,并為我們帶來了很多性提升。
接下來,讓我們試試 Python!
### Python
在這個目錄中創建一個`embed.py`,并寫入這些:
~~~
from ctypes import cdll
lib = cdll.LoadLibrary("target/release/libembed.so")
lib.process()
print("done!")
~~~
甚至更簡單了!我們使用`ctypes`模塊的`cdll`。之后是一個快速的`LoadLibrary`,然后我可以調用`process()`。
在我的系統,這花費了`0.017`秒。非常快!
### Node.js
Node 并不是一個語言,不過目前它是服務端 JavaScript 居統治地位的實現。
為了在 Node 中進行 FFI,首先我們需要安裝這個庫:
~~~
$ npm install ffi
~~~
安裝之后,我們就可以使用它了:
~~~
var ffi = require('ffi');
var lib = ffi.Library('target/release/libembed', {
'process': ['void', []]
});
lib.process();
console.log("done!");
~~~
這看起來比 Python 的例子更像Ruby的例子。我們使用`ffi`模塊來獲取`ffi.Library()`,它加載我們的動態庫。我們需要標明函數的返回值和參數值,它們是返回“void”,和一個空數組表明沒有參數。這樣,我們就可以調用它并打印結果。
在我的系統上,這會花費`0.092`秒,很快。
### 結論
如你所見,基礎操作是*很*簡單的。當然,這里有很多我們可以做的。查看[FFI](#)章節以了解更多細節。
- 前言
- 貢獻者
- 1.介紹
- 2.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照