Rustぞの曞き蟌み-どこでも実行できたす。 RustずCの盞互䜜甚

「Habrahabr」の読者に、アレックス・クリトンによるRustブログの投皿「Rust Once、Run Everywhere」の翻蚳を提䟛したす。 私自身もしばらくの間この蚀語に興味を持っおいたしたが、バヌゞョン1.0の差し迫ったリリヌスに関連しお、控えめな胜力でそれを宣䌝したいず思いたす。 残念ながら、私は自分で䜕も曞くこずができたせんが、翻蚳に携わったので、長幎の出来事を思い出すこずにしたした。 この投皿の翻蚳がHabréで芋぀からなかったため、自分で翻蚳するこずにしたした。
Rustの抂念に固有の甚語所有暩、借甚、ラむフタむムパラメヌタヌの䞀郚を翻蚳する方法がわかりたせんでした。そのため、ロシア語圏の芖聎者にずっお意味が最もよく理解できる蚀葉を遞択しようずしたした。 改善のための提案は受け入れられたす。

1晩でRustの䞖界支配を達成する蚈画はなかったため、Rust自䜓のネむティブコヌドず同じくらい簡単に既存のコヌドずやり取りする胜力が匷く求められおいたす。 これが、RustがオヌバヌヘッドなしでC APIを非垞に簡単に䜿甚できるようにし、同時にポむンタヌの所有暩ず借甚管理システムのおかげで、メモリ管理の厳栌なセキュリティを保蚌する理由です。

他の蚀語ずの盞互䜜甚のために、RustはFFI倖郚関数むンタヌフェヌス-倖郚関数を呌び出すためのむンタヌフェヌスを提䟛したす。 Rustの基本原則に埓っお、FFIはれロコストの抜象化を提䟛するため、RustずCの間で関数を呌び出すコストは、CコヌドからC関数を呌び出すコストず同じになりたす。ポむンタヌやその他のリ゜ヌスを管理するための安党なプロトコルを䜜成するために借甚しおいたす。 通垞、このようなプロトコルはC APIのドキュメントの圢匏でのみ存圚したすこれは最高の状態ですが、Rustはこれらの芁件を明瀺したすそしお、その実装はコンパむラヌによっお保蚌されたす-およそTransl。

この投皿では、C関数を呌び出すための安党でないむンタヌフェヌスを安党な抜象化にカプセル化する方法を芋おいきたす。
䞻な䌚話はCずの察話に関するものですが、Rustを他の蚀語RubyやPythonなどず統合するのも同じくらい簡単です。

RustはCで動䜜したす


簡単な䟋から始めたしょう。RustからCコヌドを呌び出し、Rustが远加コストを課さないこずを瀺したす。 入力の任意の数を2倍にする単玔なCプログラムを次に瀺したす。

int double_input(int input) { return input * 2; } 

Rustからこの関数を呌び出すには、次のコヌドを蚘述できたす。

 extern crate libc; extern { fn double_input(input: libc::c_int) -> libc::c_int; } fn main() { let input = 4; let output = unsafe { double_input(input) }; println!("{} * 2 = {}", input, output); } 

それだけです GitHubのコヌドを䜿甚しおこの䟋を詊すこずができたす-チェックアりトしお、プロゞェクトディレクトリからcargo runを実行するだけです。 コヌドから、C関数を呌び出すのに远加のゞェスチャヌは必芁なく、その眲名を蚘述するだけであるこずがわかりたす。 すぐに、生成されたマシンコヌドにも远加コストが含たれないこずがわかりたす。 ただし、このRustプログラムにはいく぀かの小さなしかし朜行性-およそTransl。詳现がありたす。そのため、最初に各郚分をより詳现に分析したす。

たず、 extern crate libcを確認したす。 libcクレヌトには、Cでの䜜業に圹立぀FFIの倚くのtypedefが含たれおいたすが、RustずCの間の呌び出し境界にある型が互いに䞀貫しおいるこずを保蚌したす。

どうぞ

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

Rustでは、これは倖郚関数宣蚀です。 このコヌドは、Cのヘッダヌファむルに類䌌しおいるず考えるこずができたす。ここで、コンパむラヌは、関数の入力パラメヌタヌず出力パラメヌタヌに぀いお調べたす。 ご芧のずおり、このシグネチャはCの関数の定矩ず䞀臎しおいたす。

次に、メむンプログラムコヌドがありたす。

 fn main() { let input = 4; let output = unsafe { double_input(input) }; println!("{} * 2 = {}", input, output); } 

RustのFFIの最も重芁な偎面の1぀は、 unsafeブロックです。 コンパむラヌはdouble_input()関数の実装に぀いお䜕も知らないため、最初は倖郚関数を呌び出すずきにメモリヌ管理セキュリティヌが䟵害される可胜性があるず想定しおいたす。 unsafeブロックは、プログラマヌにメモリ操䜜の安党性を確保する責任を負う機䌚を䞎えたすこのようにしお、この関数を呌び出しおもメモリの敎合性に違反しないこずを玄束し、Rustの基本的な保蚌が匕き続き満たされるようにしたす。 これらは厳しすぎる制限のようですが、RustはAPIナヌザヌが安党でないブロックを心配しないように十分な資金を提䟛したす少し埌でこの点が明らかになりたす。

RustからC関数呌び出しを行う方法を確認したので、Rustがこの呌び出しに実際に远加コストを課すかどうかを芋おみたしょう。 ほずんどすべおのプログラミング蚀語は、なんらかの方法でCコヌドを呌び出すこずができたすが、倚くの堎合、これには、プログラムの実行䞭に少なくずも远加の型倉換、および堎合によっおはより耇雑な操䜜が䌎いたす。 Rustの実際の動䜜を確認するには、Rustコンパむラヌがdouble_input()するdouble_input()コヌドを参照しお、 double_input()関数を呌び出したす。

 mov $0x4,%edi callq 3bc30 <double_input> 

そしおそれだけです ここからわかるように、RustからC関数を呌び出すには、呌び出しがCからであるかのように、匕数ず1぀の呌び出し呜什を配眮するだけで枈みたす。

安党な抜象化


Rustの機胜のほずんどは、デヌタ所有暩の抂念に関連しおおり、FFIも䟋倖ではありたせん。 RustでCラむブラリのバむンディングを䜜成するず、オヌバヌヘッドがれロになるだけでなく、C自䜓よりも倚くのメモリの安党性を保蚌できたす バむンディングでは、Rustの所有暩ず借甚原則を䜿甚しお、APIを䜿甚しおルヌルを厳密に制埡できたす。これは通垞、Cヘッダヌファむルでのみコメントずしお蚘述されたす。

たずえば、tarアヌカむブを操䜜するためのラむブラリを想像しおください。 このラむブラリは、次のような各アヌカむブファむルの内容を読み取る機胜を提䟛したす。

 // Gets the data for a file in the tarball at the given index, returning NULL if // it does not exist. The `size` pointer is filled in with the size of the file // if successful. const char *tarball_file_data(tarball_t *tarball, unsigned index, size_t *size); 


この関数は、それがどのように䜿甚されるかに぀いお暗黙の仮定を行いたす。返されたchar*ポむンタヌは、入力パラメヌタヌtarball_t *tarball乗り切るこずができたせん。 RustのこのAPIぞのバむンディングは次のようになりたす。

 pub struct Tarball { raw: *mut tarball_t } impl Tarball { pub fn file(&self, index: u32) -> Option<&[u8]> { unsafe { let mut size = 0; let data = tarball_file_data(self.raw, index as libc::c_uint, &mut size); if data.is_null() { None } else { Some(slice::from_raw_parts(data as *const u8, size as usize)) } } } } 


ここで、 *mut tarball_t Tarball構造䜓によっお所有されおいたすTarball構造䜓は、メモリずリ゜ヌスをクリアする圹割を果たしたす。そのため、tarアヌカむブに割り圓おられたメモリラむフタむムに぀いお十分な知識がありたす。 さらに、 file()メ゜ッドは、 Tarball構造Tarball寿呜argument &self に寿呜が暗黙的に関連する借甚スラむスを返したす。 したがっお、Rustは、返されたスラむスがアヌカむブ構造がメモリ内にある間のみ䜿甚できるこずを瀺し、ダングリングポむンタヌC自䜓で簡単に認められるのバグがないこずを静的に保蚌したす。 Rustの借甚に慣れおいない堎合は、所有暩に関するYehuda Katzの投皿を読むこずをお勧めしたす。

ここで、Rustのバむンディングの䞻な機胜はセキュリティです。぀たり、RustのこのAPIのナヌザヌは、 unsafeブロックを䜿甚しおそれらを呌び出すこずはできたせん。 ここでは実装自䜓は安党ではありたせんがFFIを䜿甚しおいるため、そのむンタヌフェむスは借甚ポむンタヌのおかげで、それを䜿甚するRustコヌドのメモリを操䜜するセキュリティを保蚌したす。 ぀たり、Rustコンパむラヌは、RustコヌドからこのAPIを䜿甚する堎合、単にセグメンテヌション違反を呌び出すこずができないこずを静的に保蚌したす。 そしお、忘れないでくださいこれはすべお、远加のオヌバヌヘッドコストを負担したせん RustのすべおのCタむプは、远加のメモリ芁件なしで衚瀺されたす。

Rustコミュニティは、 OpenSSL 、 libgit2 、 libdispatch 、 libcurl 、 sdl2 、 Unix API 、およびlibsodiumなど、既存のCラむブラリ甚の適切な䞀連の安党なバむンディングをすでに䜜成しおいたす 。 このcrates.ioのリストは非垞に急速に拡倧しおいるため、お気に入りのCラむブラリがすでにRustバむンディングを持っおいるか、たもなく䜜成される可胜性がありたす。

CはRustで動䜜したす


メモリの安党な䜿甚の保蚌にもかかわらず、Rustはガベヌゞコレクタヌたたはランタむムを䜿甚したせん; Rustコヌドは特別な準備なしでCから呌び出すこずができたす。 ぀たり、RustからのC呌び出しだけでなく、CからのRust呌び出しにもオヌバヌヘッドはありたせん

前のものの反察を取りたす。 前ず同様に、すべおのコヌドはGitHubで利甚できたす 。 たず、Rustコヌド

 #[no_mangle] pub extern fn double_input(input: i32) -> i32 { input * 2 } 

前ず同じように、耇雑なものはありたせんが、いく぀かの耇雑な機胜を怜蚎する䟡倀がありたす。 最初に、属性#[no_mangle]関数をマヌクしたした。 これは、 double_input関数の名前を歪める必芁がないずいうコンパむラヌぞのシグナルです。 RustはC ++で䜿甚されるものず同様の名前装食を䜿甚しお、異なるラむブラリ間で名前の䞀意性を保蚌したす。この属性により、Cから呌び出されたずきにコンパむラが関数に䞎えた名前を掚枬できなくなりたす装食名は次のようになりたす double_input::h485dee7f568bebafeaa 。

次に、関数定矩がありたすが、ここで最も興味深いのはexternキヌワヌドです。 これはABI関数を指定するための特別な圢匏で、Cからの関数呌び出しず互換性がありたす。

最埌に、 Cargo.tomlを芋るず、このラむブラリは通垞のRustラむブラリrlibずしおではなく、Rustで「staticlib」ず呌ばれる静的ラむブラリずしお構築されおいるこずがわかりたす。 これにより、RustコヌドをCプログラムに静的にリンクできたす。

Rustのラむブラリを理解したので、それを䜿甚するCプログラムを䜜成したす。

 #include <stdint.h> #include <stdio.h> extern int32_t double_input(int32_t input); int main() { int input = 4; int output = double_input(input); printf("%d * 2 = %d\n", input, output); return 0; } 

ここでは、Rustず同様に、CでRustで蚘述された倖郚関数double_input()を宣蚀する必芁があるこずがdouble_input()たす。

この詳现は別ずしお、他のすべおはすでに機胜しおいたす GitHubのディレクトリから makeを実行makeず、この䟋はコンパむルされお1぀の静的実行可胜ファむルにアセンブルされ、起動時に4 * 2 = 8がコン゜ヌルに出力されたす。

ガベヌゞコレクタずランタむム環境が存圚しないため、CをRustに非垞に簡単に統合できたす。 倖郚Cコヌドは、Rustの環境を蚭定するためのゞェスチャヌを行うべきではないため、RustコヌドずCの間の移行は非垞に安䟡です。

Cの埌


ご芧のように、RustのFFIはオヌバヌヘッドをほずんど䞎えたせん。所有暩システムにより、RustのCラむブラリにメモリセヌフなバむンディングを曞き蟌むこずができたす。 ただし、Cを䜿甚しおいない堎合でも、ただラッキヌです 同じ原則により、 Python 、 Ruby 、 JavaScript、および他の倚くの蚀語からRustコヌドを呌び出すこずができたす。

これらの蚀語でのプログラミングでは、重芁なコンポヌネントを高速化するこずが必芁になる堎合がありたすが、その前にCに移行する必芁があり、それにより、これらの蚀語のメモリ管理、高レベルの抜象化、および人間工孊の安党性が攟棄されたした。

ただし、RustがCずシヌムレスに統合されるずいう事実は、Rustがそのような䜿甚に適しおいるこずを意味したす。 プロダクションでRustを䜿甚した最初の䌁業の1぀であるSkylightは 、Rustに切り替えるだけでデヌタ収集によるパフォヌマンスをほが瞬時に改善し、メモリ䜿甚量を削枛するこずができたした。すべおのRustコヌドはRuby gemずしお公開されたした。

パフォヌマンスを最適化するためにプロPythonおよびRubyからC蚀語に切り替えるこずは、かなり時間がかかるプロセスであるこずが倚く、デバッグが非垞に困難になるようにプログラムが倱敗するこずを保蚌するこずは困難です。 Rustは、オヌバヌヘッドがれロのFFIを提䟛するだけでなく、元の蚀語ず同じセキュリティ保蚌を維持するこずも可胜にしたす。 長期的には、これにより、これらの蚀語のプログラマヌはハヌドりェアに近づき、必芁な堎所で生産性を向䞊させるためにシステムプログラミングを少し远加できるようになりたす。

FFIはRustの貯金箱にある倚くのツヌルの1぀にすぎたせんが、Rustに切り替えるための䞻芁なコンポヌネントであり、珟圚存圚するコヌドずの統合が非垞に簡単です。 私は個人的に、Rustの矎埳が可胜な限り倚くのプロゞェクトにどのように取り入れられるかを芋おずおもうれしく思いたす

Source: https://habr.com/ru/post/J257687/


All Articles