Rust adalah bahasa sistem modern yang menjanjikan memory safety tanpa garbage collector. Rust memakai C ABI sebagai jembatan FFI resmi—setiap fungsi yang ditandai extern "C" dapat dipanggil dari C, dan Rust dapat memanggil library C lewat extern "C" block. Karena C ABI adalah kontrak bersama, kode Rust dan C dapat saling memanggil tanpa wrapper tambahan. Pada chapter ini kita akan melihat dua arah: Rust yang diekspor untuk dipanggil C, dan Rust yang memanggil library C. Sandbox tidak memiliki rustc terpasang, sehingga demo Rust bersifat konseptual mengikuti dokumentasi resmi; kita verifikasi mekanisme dengan library C yang mensimulasikan output Rust.
Rust yang Dipanggil dari C #
Rust compiler (rustc atau cargo build) dapat menghasilkan shared library .so lewat crate type cdylib. Attribute #[no_mangle] menyuruh compiler tidak melakukan name mangling, dan extern "C" menyatakan calling convention C. Kombinasi ini menghasilkan symbol yang dapat ditemukan oleh dlsym atau linker C biasa.
// File: src/lib.rs - library Rust yang diekspor ke C
#[no_mangle]
pub extern "C" fn rust_tambah(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn rust_versi() -> *const u8 {
// Rust string literal adalah UTF-8 dengan null terminator implisit
b"rust-lib 0.1.0\0".as_ptr()
}Untuk menghasilkan .so, tambahkan di Cargo.toml:
[lib]
name = "rust_demo"
crate-type = ["cdylib"]Build dengan cargo:
cargo build --release
# Hasil: target/release/librust_demo.soDi sisi C, kita mendeklarasikan fungsi dan link seperti library biasa. Karena sandbox tidak memiliki rustc, kita simulasikan output Rust dengan C murni untuk memverifikasi mekanisme link dan symbol.
/* File: rust_lib.c - simulasi output Rust yang diekspos ke C.
*
* Dalam Rust nyata, file ini setara dengan:
* #[no_mangle]
* pub extern "C" fn rust_tambah(a: i32, b: i32) -> i32 { a + b }
* Rust compiler menghasilkan symbol C ABI 'rust_tambah' (tanpa mangling)
* karena #[no_mangle] dan extern "C".
*/
#include <stdio.h>
int rust_tambah(int a, int b) {
return a + b;
}
const char *rust_versi(void) {
return "rust-lib 0.1.0";
}/* File: c_panggil_rust.c - C memanggil library Rust (simulasi) */
#include <stdio.h>
#include <stdlib.h>
/* Deklarasi fungsi dari library Rust */
extern int rust_tambah(int a, int b);
extern const char *rust_versi(void);
int main(void) {
int hasil = rust_tambah(20, 22);
printf("Rust versi: %s\n", rust_versi());
printf("rust_tambah(20, 22) = %d\n", hasil);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -fPIC -shared -o librust_demo.so rust_lib.c
gcc -std=c11 -Wall -Wextra -o c_panggil_rust c_panggil_rust.c -L. -lrust_demo -Wl,-rpath,.
./c_panggil_rustOutput yang Dihasilkan:
Rust versi: rust-lib 0.1.0
rust_tambah(20, 22) = 42Mari kita telaah baris-baris kode di atas:
#[no_mangle](Rust): Attribute yang menyuruh Rust compiler tidak melakukan name mangling. Rust secara default mangle nama fungsi dengan hash untuk mengodekan informasi generic dan lifetime. Tanpa#[no_mangle], symbol akan menjadi sesuatu seperti_ZN11rust_demo11rust_tambah17h1234567890abcdefEyang tidak dapat dipanggil dari C.extern "C"(Rust): Menentukan calling convention C (System V AMD64 pada Linux x86_64). Rust default memakai convention “Rust” yang berbeda dan tidak kompatibel dengan C.extern "C"sama dengan__attribute__((cdecl))di GCC.pub(Rust): Menandai fungsi sebagai public—dapat dipanggil dari luar module. Tanpapub, fungsi bersifat private dan tidak diekspor ke.so.*const u8return (Rust): Rust tidak memiliki tipechar *. Untuk mengembalikan string C, kembalikan*const u8yang merupakan pointer ke byte UTF-8. Penting: Rust string literal tidak otomatis null-terminated; kita harus menambahkan\0secara eksplisit (b"rust-lib 0.1.0\0").- Flag
-L. -lrust_demo -Wl,-rpath,.: Sama seperti link library C biasa. Linker mencarilibrust_demo.sodi.(lewat-L.), menyemat rpath., lalu resolve symbolrust_tambahdanrust_versi. nm -D librust_demo.so | grep rustmemperlihatkan dua symbol tanpa mangling:T rust_tambahdanT rust_versi. Inilah yang dicari oleh linker C dan olehdlsym. Output nyata dari Rust compiler identik secara struktur—yang berbeda hanya kode mesin yang dihasilkan.
C yang Dipanggil dari Rust #
Arah sebaliknya: Rust memanggil fungsi C. Rust menggunakan block extern "C" untuk mendeklarasikan fungsi eksternal yang ada di library C, lalu memanggilnya lewat unsafe block. Keyword unsafe wajib karena Rust compiler tidak dapat menjamin memory safety untuk call lintas FFI.
// File: src/main.rs - Rust memanggil fungsi C
use std::os::raw::{c_int, c_char, c_double};
extern "C" {
fn sqrt(x: c_double) -> c_double;
fn abs(x: c_int) -> c_int;
}
fn main() {
let x: f64 = 50.0;
let akar: f64 = unsafe { sqrt(x) };
println!("sqrt({}) = {}", x, akar);
let n: i32 = -17;
let mutlak: i32 = unsafe { abs(n) };
println!("|{}| = {}", n, mutlak);
}Compile dan jalankan (di lingkungan dengan rustc):
rustc src/main.rs -o panggil_c -lm
./panggil_cOutput yang Dihasilkan (konseptual, mengikuti dokumentasi Rust):
sqrt(50) = 7.0710678118654755
|-17| = 17Mari kita telaah baris-baris kode di atas:
- Baris 2 (
use std::os::raw::{c_int, c_char, c_double}): Rust menyediakan tipe alias yang menjamin ukuran sesuai C ABI.c_intselalu 32-bit signed sepertiintC;c_double64-bit IEEE 754 sepertidoubleC. Memakai tipe alias ini penting karena Rusti32tidak selalu sama dengan Cintdi semua platform. - Baris 4-7 (
extern "C" { fn sqrt(...); fn abs(...); }): Block deklarasi fungsi eksternal. Signature harus persis cocok dengan deklarasi C—bila tidak cocok (misalnya parameterc_intvsi32), Rust dapat crash dengan undefined behavior. Block ini tidak memanggil fungsi; ia hanya menyatakan bahwa fungsi tersebut ada di luar. - Baris 11 (
unsafe { sqrt(x) }): Call ke fungsi FFI wajib dibungkusunsafe. Rust compiler tidak dapat memverifikasi bahwa fungsi C tidak melakukan undefined behavior.unsafeblock menyatakan “programmer telah menjamin call ini aman secara manual”. - Flag
-lm: Rust compiler tidak otomatis linklibm. Karenasqrtada dilibm, kita nyatakan eksplisit seperti saat memakaigcc. - Output
7.0710678118654755: Lebih banyak digit daripada output C (7.07107) karena Rustprintln!dengan{}format double ke presisi penuh. Implementasisqrtyang dipanggil tetap sama—yang berbeda hanya formatting output.
Pola Opaque Pointer untuk Handle Rust #
Library Rust yang ingin dipakai dari C umumnya mengekspor opaque handle (sama seperti pola di chapter Opaque Pointers). Rust struct diberi #[repr(C)] supaya layout mengikuti aturan C, dan fungsi factory/destructor diekspos dengan #[no_mangle] extern "C".
// File: src/lib.rs - library Rust dengan opaque handle
use std::boxed::Box;
#[repr(C)]
pub struct Akun {
id: i32,
saldo: i64,
}
#[no_mangle]
pub extern "C" fn akun_buat(id: i32) -> *mut Akun {
Box::into_raw(Box::new(Akun { id, saldo: 0 }))
}
#[no_mangle]
pub extern "C" fn akun_hancurkan(ptr: *mut Akun) {
if ptr.is_null() { return; }
unsafe { let _ = Box::from_raw(ptr); }
}
#[no_mangle]
pub extern "C" fn akun_saldo(ptr: *const Akun) -> i64 {
if ptr.is_null() { return 0; }
unsafe { (*ptr).saldo }
}Pola penting yang diperhatikan:
#[repr(C)]: Menyuruh Rust menata struct mengikuti C ABI (field berurutan dengan alignment C). Tanpa ini, Rust bebas menata ulang field untuk efisiensi, yang dapat menyebabkan layout berbeda dari ekspektasi C.Box::into_raw(Box::new(...)): Mengkonversi RustBox<T>(heap-allocated, ownership Rust) ke raw pointer*mut T. Rust tidak lagi mengelola memori—pemanggil (C) yang bertanggung jawab membebaskan lewatakun_hancurkan.Box::from_raw(ptr): Mengkonversi raw pointer kembali keBox<T>. SaatBoxkeluar scope, Rust membebaskan memori. Inilah caraakun_hancurkanmengembalikan ownership ke Rust untuk di-dealloc.unsafe { (*ptr).saldo }: Dereference raw pointer wajib dalamunsafeblock. Rust tidak menjamin pointer valid—programmer harus yakin C tidak mengirim NULL atau pointer yang sudah dihancurkan.