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.

rust
// 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:

toml
[lib]
name = "rust_demo"
crate-type = ["cdylib"]

Build dengan cargo:

bash
cargo build --release
# Hasil: target/release/librust_demo.so

Di 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.

c
/* 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";
}
c
/* 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:

bash
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_rust

Output yang Dihasilkan:

text
Rust versi: rust-lib 0.1.0
rust_tambah(20, 22) = 42

Mari kita telaah baris-baris kode di atas:

  1. #[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_tambah17h1234567890abcdefE yang tidak dapat dipanggil dari C.
  2. 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.
  3. pub (Rust): Menandai fungsi sebagai public—dapat dipanggil dari luar module. Tanpa pub, fungsi bersifat private dan tidak diekspor ke .so.
  4. *const u8 return (Rust): Rust tidak memiliki tipe char *. Untuk mengembalikan string C, kembalikan *const u8 yang merupakan pointer ke byte UTF-8. Penting: Rust string literal tidak otomatis null-terminated; kita harus menambahkan \0 secara eksplisit (b"rust-lib 0.1.0\0").
  5. Flag -L. -lrust_demo -Wl,-rpath,.: Sama seperti link library C biasa. Linker mencari librust_demo.so di . (lewat -L.), menyemat rpath ., lalu resolve symbol rust_tambah dan rust_versi.
  6. nm -D librust_demo.so | grep rust memperlihatkan dua symbol tanpa mangling: T rust_tambah dan T rust_versi. Inilah yang dicari oleh linker C dan oleh dlsym. 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.

rust
// 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):

bash
rustc src/main.rs -o panggil_c -lm
./panggil_c

Output yang Dihasilkan (konseptual, mengikuti dokumentasi Rust):

text
sqrt(50) = 7.0710678118654755
|-17| = 17

Mari kita telaah baris-baris kode di atas:

  1. Baris 2 (use std::os::raw::{c_int, c_char, c_double}): Rust menyediakan tipe alias yang menjamin ukuran sesuai C ABI. c_int selalu 32-bit signed seperti int C; c_double 64-bit IEEE 754 seperti double C. Memakai tipe alias ini penting karena Rust i32 tidak selalu sama dengan C int di semua platform.
  2. Baris 4-7 (extern "C" { fn sqrt(...); fn abs(...); }): Block deklarasi fungsi eksternal. Signature harus persis cocok dengan deklarasi C—bila tidak cocok (misalnya parameter c_int vs i32), Rust dapat crash dengan undefined behavior. Block ini tidak memanggil fungsi; ia hanya menyatakan bahwa fungsi tersebut ada di luar.
  3. Baris 11 (unsafe { sqrt(x) }): Call ke fungsi FFI wajib dibungkus unsafe. Rust compiler tidak dapat memverifikasi bahwa fungsi C tidak melakukan undefined behavior. unsafe block menyatakan “programmer telah menjamin call ini aman secara manual”.
  4. Flag -lm: Rust compiler tidak otomatis link libm. Karena sqrt ada di libm, kita nyatakan eksplisit seperti saat memakai gcc.
  5. Output 7.0710678118654755: Lebih banyak digit daripada output C (7.07107) karena Rust println! dengan {} format double ke presisi penuh. Implementasi sqrt yang 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".

rust
// 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:

  1. #[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.
  2. Box::into_raw(Box::new(...)): Mengkonversi Rust Box<T> (heap-allocated, ownership Rust) ke raw pointer *mut T. Rust tidak lagi mengelola memori—pemanggil (C) yang bertanggung jawab membebaskan lewat akun_hancurkan.
  3. Box::from_raw(ptr): Mengkonversi raw pointer kembali ke Box<T>. Saat Box keluar scope, Rust membebaskan memori. Inilah cara akun_hancurkan mengembalikan ownership ke Rust untuk di-dealloc.
  4. unsafe { (*ptr).saldo }: Dereference raw pointer wajib dalam unsafe block. Rust tidak menjamin pointer valid—programmer harus yakin C tidak mengirim NULL atau pointer yang sudah dihancurkan.