2015年10月10日 星期六

Rust 的 C 語言界面:struct 和 enum

對於打算使用新語言的人來說,能否容易地使用 C 語言大量的函式庫是選擇語言的重要指標。

使用 C 的函式庫,簡單分兩個層次:
  1. 呼叫 C 的函式,以及提供 callback
  2. 使用 C 的資料結構。
過往的經驗告訴我,麻煩在第二點。不同語言的資料結構採用不同的記憶體 layout,因此很難直接使用他種語言的資料結構。於是常要先撰寫一堆存取資料結構的小函式,再透過這些函式來使用資料結構。

這假日我好好研究了這個問題,驚奇發現 Rust 是僅次於 C++,我用過最容易和 C 界接的語言。以下以我最近處理的一個 struct 裡面有 struct,還有 enum,更有陣列的資料結構的簡化版,說明如何做出有同樣 layout 的 Rust 資料結構。

#define EC_MAX_STRING_LENGTH 64
#define EC_MAX_PORTS 4

typedef enum {
    EC_PORT_NOT_IMPLEMENTED, 
    EC_PORT_NOT_CONFIGURED, 
    EC_PORT_EBUS, 
    EC_PORT_MII 
} ec_slave_port_desc_t;

typedef struct {
    uint8_t link_up; 
    uint8_t loop_closed; 
    uint8_t signal_detected; 
} ec_slave_port_link_t;

typedef struct {
    uint16_t position; 
    uint32_t vendor_id; 
    uint32_t product_code; 
    struct {
        ec_slave_port_desc_t desc; 
        ec_slave_port_link_t link; 
        uint32_t receive_time; 
        uint16_t next_slave; 
    } ports[EC_MAX_PORTS]; 
    uint8_t error_flag; 
    char name[EC_MAX_STRING_LENGTH]; 
} ec_slave_info_t;

以下使用 Rust 做出在記憶體 layout 一模一樣的資料結構:

// 首先要使用 libc 中提供的 C 的型別。
// int  對應 c_int
// uint 對應 c_uint
// char 對應 u8
// int16_t, uint16_t 等直接對應 int16_t 及 uint16_t。

extern crate libc;
use libc::{c_uint, c_int, int16_t, uint8_t, uint16_t, uint32_t};

const EC_MAX_PORTS: usize = 4;
const EC_MAX_STRING_LENGTH: usize = 64;

// 在 enum 前加上 #[repr(C)],告知 Rust 採用 C 的 layout。

#[repr(C)]
pub enum ec_slave_port_desc_t {
    EC_PORT_NOT_IMPLEMENTED = 0,
    EC_PORT_NOT_CONFIGURED,
    EC_PORT_EBUS,
    EC_PORT_MII,
}

#[repr(C)]
pub struct ec_slave_port_link_t {
    link_up: uint8_t,
    loop_closed: uint8_t,
    signal_detected: uint8_t,
}

// 對於 C 結構中的陣列,就使用 Rust 的陣列取代。
#[repr(C)]
pub struct ec_slave_info_t {
    pub position: uint16_t,
    pub vendor_id: uint32_t,
    pub product_code: uint32_t,
    ports: [ec_slave_port_t; EC_MAX_PORTS],
    al_state: uint8_t,
    error_flag: uint8_t,
    name: [u8;EC_MAX_STRING_LENGTH],
}

我產生上列程式的組合語言列表,確定它和 C 語言的記憶體配置一模一樣。 比較麻煩的是給預設值初始化時,這部份以後有機會再說明。