2019年12月5日 星期四

用 Qt Graphics 做一個顯示座標的工具

故事是這樣的,平常小弟在公司處理的東西多半是一些 polygon, line 之類的資料,除錯的時候總不能看著 gdb 印出來的 x, y 座標 debug,所以公司同仁有自幹一套 debug 的工具幫忙把這些資料畫出來;不過呢…這套工具好像在記憶體模型那邊有點問題,資料量大的時候會變超慢;畫圖是直接 call xlib,每次放大縮小都要重畫所有物件,對記憶體的負擔又更嚴重。只要 polygon 數量突破幾萬個的時候,一次的 refresh 就會花上好幾秒。

前陣子手上暫時沒其他緊急的事情,乾脆就用 Qt Graphics 重寫一個,不準在留言問我為什麼不用 nodejs 寫,MaDer 公司的工作站就沒有 nodejs。
完工後自己試了一陣發現幾十萬個物件的時候放大縮小都超流暢,不愧是 Qt Graphics,雖然程式行數比我預期的多了些,但架構比本來的東西清楚很多。
其實過程中一直參考強者我同學 AZ 大大的 QCamber,覺得 AZ大大實在太過神猛狂強溫,5-6 年前就寫得出這麼複雜的 project,我一直覺得我寫 code 的時候的整體感很不夠,都是在單一 class 裡面塗塗抹抹,小地方會動可是大架構沒辦法在一開始就訂好,後續要修改的成本就非常高。 Anyway 總之它現有個樣子了,我覺得中間碰過一些實作的問題值得記錄一下,預計可能寫個三篇左右吧。

----

首先圖形顯示的部分,使用的是 Qt 的 graphics framework,可以用來繪製大量的 2D 物件,支援選取、縮放等等,我們這裡只是要顯示而已,也不用搞得這麼複雜。
Graphics framework 裡的三個基本元件就是:
  • QGraphicsScene:場景,可以把它想成一塊巨大的畫布,可以在上面自由放上各種 item,Scene 會幫你管理物件的顯示和更新,個人經驗 Scene 負擔到 10 萬個元件左右還很流暢,上到百萬個的時候就會有點頓了(又或者是我把所有 item 都放在 scene 裡面的關係)。
  • QGraphicsItem:物件,可以想像成畫素描的時候放的那些石膏,在一個場景上擺上東西,Qt 有提供基本的幾種物件:橢圓 QGraphicsEllipseItem、路徑 QGraphicsPathItem、多邊形 QGraphicsPolygonItem、矩形 QGraphicsRectItem跟文字 QGraphicsSimpleTextItem。
  • QGraphicsView:View 是唯一可以在 Qt 的 MainWindow 畫面上出現的物件,可以把 View 想成一台相機,場景 Scene 是不動的,相機從各種角度自由取景,並把取到的景顯示出來,如果取景的尺寸比畫面還要大,跟其他的物件一樣, View 能自動出現捲軸,也可以接收畫面上的滑鼠、鍵盤事件。
只用 Qt 原生的 QGraphicsScene, QGraphicsView, QGraphicsItem 只能組出最基本的顯示工具,變化量非常少,以下就示範一個最基本的設定:
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>

int main(int argc, char *argv[]) {
  QApplication app(argc, argv);
  QGraphicsScene *scene = new QGraphicsScene;
  scene->setSceneRect(0, 0, 400, 400);
  scene->addItem(new QGraphicsRectItem(50, 50, 150, 100));

  QGraphicsView *view = new QGraphicsView;
  view->setScene(scene);
  view->show();

  return app.exec();
編譯執行就可以看到這個畫面:

反正這個設計只是一開始建來試驗用的,看一下顯示的效果,很快下一篇就會被我們拆掉了。

2019年11月16日 星期六

把一顆樹寫出來是會有多難

故事是這樣子的,之前小弟發下豪語想用 Rust PEG 寫一個 C Parser,然後…就沒有然後了。好啦當然不是,不然就不會有這篇文了。
總之最近經過一陣猛烈的攪動之後,我的 parser 能處理的文法終於接近當年在學校修 compiler 的時候所要求的 B language 了,說來慚愧,當年寫 compiler 作業的時候 parser 只是裡面一個作業,要在 2-3 週裡面寫完的,結果現在搞半天寫不出個毛,果然上班跟上學還是不一樣,在學校可以全心全意投入寫 code ,週末的時候還可以熬個夜把作業寫出來;現在上班白天要改公司的 code ,晚上回家累個半死不想寫 code 只想開卡車(欸。

本篇講到的程式碼目前還沒推到遠端上,相關的程式碼可以參考:
AST 的資料結構:cast
型別的資料結構:ctype
既然現在可以處理比較複雜的文法了,再來要做什麼?想說就像作業的要求一樣,把我們處理好的 AST 用 graphviz 寫出去,是會有多難?

整個 dump graphviz 的進入點是一個函式,接收要倒出來的 AST 跟一個 out,out 的型別是 std::io::Write 的 dyn Write,這樣不管你是要寫到 stdout, stderr 還是寫到檔案都能傳進來,介面會是一樣的,函式的實作當然就是直接了當的把該印的東西都寫出去;另外實作一個 dump_node 幫我們把寫出一個 node 給獨立出來,id 會自動不斷累加,讓 node 的編號不會重複。
fn dump_graphviz(ast: CastTop, out: &mut dyn Write) {
    writeln!(out, "Digraph AST").unwrap();
    writeln!(out, "{{").unwrap();
    writeln!(out, "label = \"AST_Graph.gv\"").unwrap();
    writeln!(out, "node{} [label = \"PROGRAM_NODE\"]", 0).unwrap();
    ast.make_node(out, &mut 0);
    writeln!(out, "}}").unwrap();
}
fn dump_node(out: &mut dyn Write, id: &mut u32, label: &str) {
    *id += 1;
    writeln!(out, "node{} [label = \"{}\"]", id, label).unwrap();
}
另外我們要實作的是 make_node,這裡很自然的就是先宣告一個 trait,AST 裡面所有的物件都要實作這個 trait ,就都有 make_node 可以用了。
trait ToGraphviz {
  fn make_node(&self, out: &mut dyn Write, id: &mut u32);
}

impl ToGraphviz for CastTop {
  fn make_node(&self, out: &mut dyn Write, id: &mut u32) {
    match self {
      CastTop::FuncDeclList(v) => {
        let cur_id = *id;
        for decl in v {
          dump_node(out, id, "DECLARATION_NODE FUNCTION_DECL");
          decl.make_node(out, id);
        }
        if *id != cur_id { // new node
          writeln!(out, "node{} -> node{} [style = bold]", cur_id, cur_id + 1).unwrap();
        }
      },
    }
  }
}

impl ToGraphviz for FuncDecl {
  fn make_node(&self, out: &mut dyn Write, id: &mut u32) {
    let parent = *id;
    dump_node(out, id, &format!("IDENTIFIER_NODE {} NORMAL_ID", "int"));
    dump_node(out, id, &format!("IDENTIFIER_NODE {} NORMAL_ID", self.fun_name));
    dump_node(out, id, "PARAM_LIST_NODE");
    dump_node(out, id, "BLOCK_NODE");

    for i in parent..*id {
      writeln!(out, "node{} -> node{} [style = {}]",
          i, i+1, if i == parent {"bold"} else {"dashed"}).unwrap();
    }
  }
}
* :本來的作業要求連結第一個 child 的必須是實線,其他的用虛線,這裡沿用
這個實作的問題顯而易見,我們的輸出的實作跟資料綁死了,所以每個 node 裡面的實作都是大費周章,而且 code 很醜。
我們要更抽象化一點,其實輸出樹的邏輯是這樣子的:先寫 child 的 node,然後是自己,回傳自己的 id 給 parent,這樣上一層的人才能畫 edge 出來。
我們實作一個 dump_children 的函式,這個函式會用現在的 id 印出現在的 parent,然後把它跟所有傳進來的 children 畫線連起來:
fn dump_children(out: &mut dyn Write, id: &mut u32, label: &str, children: &[u32]) -> u32 {
  writeln!(out, "node{} [label = \"{}\"]", id, label).unwrap();
  let mut prev = *id;
  for child in children {
    writeln!(out, "node{} -> node{} [style = {}]", prev, child,
        if prev == *id { "bold" } else { "dashed" }).unwrap();
    prev = *child;
  }
  *id+=1;
  *id-1
}
因為 Rust 函式參數沒有預設值也沒有 overload,為了方便我們可以創一個 dump_nochild 的函式,這樣比較方便:
fn dump_nochild(out: &mut dyn Write, id: &mut u32, label: &str) -> u32 {
  dump_children(out, id, label, &[])
}
現在 make_node 的實作都可以用 dump_children 或 dump_nochild 實作,先對自己的 child 們呼叫 make_node,把回傳值(也就是 child 們印完的 root)收集起來再用 dump_children 印出去就行了:
impl ToGraphviz for CastTop {
  fn make_node(&self, out: &mut dyn Write, id: &mut u32) -> u32 {
    match self {
      CastTop::FuncDeclList(v) => {
        let children : Vec<_> = v.iter().map(|n| n.make_node(out, id)).collect();
        dump_children(out, id, "PROGRAM_NODE", &children);
      },
    }
    *id
  }
}

impl ToGraphviz for FuncDecl {
  fn make_node(&self, out: &mut dyn Write, id: &mut u32) -> u32 {
    let children = [
      dump_nochild(out, id, "IDENTIFIER_NODE int NORMAL_ID"),
      dump_nochild(out, id, &format!("IDENTIFIER_NODE {} NORMAL_ID", self.fun_name)),
      dump_nochild(out, id, "PARAM_LIST_NODE"),
      dump_nochild(out, id, "BLOCK_NODE")];
    dump_children(out, id, "DECLARATION_NODE FUNCTION_DECL", &children)
  }
}
這樣看起來就好多了,不過我們還能更進一步,仔細觀察上面的 dump_children 的話,就會發現我們還能用 fold 的方式改寫:
// print node, and link with all children
fn dump_children(out: &mut dyn Write, id: &mut u32, label: &str, children: &[u32]) -> u32 {
  *id+=1;
  writeln!(out, "node{} [label = \"{}\"]", id, label).unwrap();
  children.iter().fold(*id, |mut prev, child| {
      writeln!(out, "node{} -> node{} [style = {}]", prev, child,
          if prev == *id { "bold" } else { "dashed" }).unwrap();
      prev = *child;
      prev});
  *id
}
老實說,每次我費了這麼大的工夫,把一堆本來很黃很暴力的 code 改簡單,變成最後那樣的很純很 Functional 的 code,我都會在內心懷疑個 100 遍,費這麼大功夫是真的有比較快嗎?當然在維護上可能會好一點,但 Rust compiler 能保證抽象化真的是零成本的嗎?這可能是值得好好討論的議題。

每個函式都要帶著 out 跟 id 走,很不方便,用一個 struct 把它們裝起來:
struct DumpGraphviz {
  out: Box<dyn Write>,
  id: u32
}
dump_children 跟 dump_nochild 變成 DumpGraphviz 的實作,介面變成:
fn dump_children(&mut self, label: &str, children: &[u32]) -> u32
fn dump_nochild(&mut self, label: &str) -> u32
make_node 的介面則是:
fn make_node(&self, visit: &mut DumpGraphviz) -> u32
整體就變得清爽多了。
天底下沒有新鮮事,其實我就是在實作 visitor pattern,只是還沒把 visitor 整個抽出來讓不同的 visitor 可以在這上面實作。最後輸出的成品長這個樣子:

我有個小小的體悟,就是寫程式不要妄想一步登天,除非如強者我同學 AZ 大大那樣一眼就把超大程式的架構都畫出來,而且實作起來都不會亂掉。
我上一次的實作就是衝太快,翻著 C standard 想要一開始就照著 C standard 實作,然後文法寫得亂七八糟反而連簡單的文法都會大噴射無法處理;與其如此,不如先支援基本的功能,等 parser 跟文法處理都完善之後再慢慢把其他功能加上去。
我覺得用蓋房子比喻的話,寫大程式要像西敏寺那樣的大教堂一樣,先從一個功能完整的小教堂開始,然後把小部分拆掉蓋個更大更豪華的(有看過一個動畫片在演示這個過程的,只不過沒有公開版);如果一次就想蓋個超大的教堂,最後可能弄成一團廢墟,連禮拜的功能都沒有。

2019年11月4日 星期一

從 Coscup 小談 Rust

這篇其實有點拖稿,畢竟 COSCUP 都是幾個月前的事了;這次在 COSCUP 投稿了 Rust 議程軌,覺得可以來說說對 Rust 的一點感想。Rust 從問世、正式發佈到現在也差不多要 7 年,感覺近年來有愈來愈紅的趨勢,一種社群上面看一看發現大家都用過 Rust 的感覺。

今年的 COSCUP 專門開了一個 Rust 議程軌,而且感覺議程的內容正在提升,不再是一堆語言介紹,有更多的是在介紹用 Rust 實作的資料庫、web assembly 、類神經網路的應用,可以預見 Rust 正在走出推廣階段,前往實際應用的領域。
不過我們還是要回來問,Rust 在哪裡會有<十倍生產力>?也就是在哪裡可以把東西做得比其他語言十倍好,像是要推人工智慧大家就會推 Python;要寫高效能的網路可能會用 golang,有哪個領域是非用 Rust 不可的嗎?現在有些風聲是區塊鏈的合約和交易語言,但我對這塊應用的大小有點存疑。

Rust 天生尷尬在它的定位上,它的目標是一個安全高效的系統程式語言,它也的確有潛力做到這點,但整體看來 Rust 可能是幾大系統程式語言裡數一數二複雜的,可能只輸給 C++,配上最新加上去的 Async 可能差不多就比肩了(欸。
確實 Rust 從源頭來看,受到大量函數式語言和語法的啟發,語法上看得出核心來自一個優異的語言團隊並吸收了各類語言的優點;編譯時進行的所有權確認和以 mod 為編譯單位,雖然讓 Rust 編譯慢得像烏龜,卻也大量消除程式在執行時出錯的機會,或者因為設計師<忘記>而導致的問題。
Rust 不可能是一款早期的語言,它浪費太多運算資源在編譯檢查,在 C 語言發跡的年代不會浪費資源去做那些檢查,換來的就是 Rust 編譯器數一數二的 GY 程度,這個不行那個也不行,搞得寫 code 的人跟編譯器都很累……。

我認為 Rust 要走的會是一條很艱難的道路,Rust 內建的複雜性天生就拒絕了一些簡單的應用,用 Rust 寫起來太過繁瑣了,動態語言能搞定的網路服務開發速度是第一,程式設計師上手的速度還有開發的速度來看,沒理由不用動態語言;而一些偏底層的應用,特別是對從 C/C++ 來的人來說,Rust 根本就不可理喻,明明我用 C 系列一下就可以搞定的,誰跟你在那邊 4 種 String 還有一堆 Option 要處理?一眼看穿的程式實在用不上 Rust,有人覺得 Rust 可以在嵌入式系統上挑戰 C,我看再過 100 年都不太可能。
Rust 的優勢,要來到所謂的大型系統程式才會出現,透過編譯器的強制,把一些難以檢測到的記憶體問題給挑出來,當然用 C++20 的一些特性可以做到一樣的效果,但沒有編譯的強制只靠設計師所受的教育,在大型系統下畢竟不是一個妥當的做法,畢竟設計師也是人,不可能不犯錯,或者偷懶或者忘記,一不小心就引入 C++ 的舊語法 -- 那些為了向後相容絕對不會移除的部分。

但問題就在於:大型系統幾不太可能整個重寫,更別提底層所依賴的都是經過千錘百鍊的 C/C++ 函式庫,像 Mozilla 那樣決定把瀏覽器核心整個抽換掉真的是神經勇敢,市面上的大公司哪幾家做過一樣的事?
可以預期 Rust 幾年之內,都會是用滲透的方式慢慢進到各大公司的系統當中,也許是一個新實作的子系統或是重寫某些小部分,用 FFI binding 的方式和既有的系統銜接,但要成為主流我看還要努力一段時間才行。

其實我是覺得語言比語言氣死人,不過 Rust 對 go 一直是一個大家很有興趣的話題(雖然說兩個根本是完全不同的東西),我個人滿推薦 LoWeiHang 翻譯的這篇文章

2019年9月8日 星期日

從 C 呼叫 Lua 函式

故事是這樣子的,小弟在公司裡面,主要是負責維護一個沒人在用的產品,遠觀來說這個產品滿複雜的,內建兩種不同的演算法實作,為的是要應對不同的狀況,有些狀況用第一種演算法比較快,有些用第二種。
那故事是這樣子的,我們的程式裡面有一個函式會在每筆資料結束之後,用上筆資料的結果來判斷下一次要選哪個演算法,問題是這個函式目前是直接寫在整個引擎的 C code 裡面,於是如果想要改變一下判斷的標準……sorry 重新 build,雖然公司弄了套分散編譯可以編很快但還是要幾分鐘。
上星期自己試了一下,成功把 Lua 編到公司的 code 裡面,就能把判斷邏輯寫在 Lua script 裡面,要改判斷標準只要改 Lua 就可以了;我一般聽到會這樣用的是遊戲公司,因為遊戲一樣涉及大量的邏輯判斷,例如血要扣多少之類的,而遊戲又會大量的去變動這些參數,使得彈性變得非常重要,總不能要改參數就把整個遊戲引擎全部重建構一次……說是這麼說我也不曾證實哪家公司真的這麼做就是,如果我的讀者真的是這樣搞的麻煩留個言讓我知道一下。

總之自己試過之後其實非常簡單,難怪大家都說 Lua 最厲害的就是嵌入到 C 程式當 Sup 角 ,我主要是參考這個網頁(看來是舊金山大學 CS 的課程頁面);下載 Lua 就從官網下載即可,哪一版應該是無所謂,或者是 Linux 的話安裝開發套件也 OK。
不愧是以輕量著稱的腳本語言,連 Makefile 看起來都是手寫的,直接下 Make 讓它編譯完成,雖然說我在這步被環境變數 TARGET_VAR 卡了很久,makefile 不知道為什麼自己把它加到編譯參數裡了。
再來做下面幾件事就能呼叫 Lua 函式了:

1. 引入 Lua 的標頭檔:

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
然後在編譯的時候記得給一下 lua 標頭檔的位置,以及在連結的時候 -llua。

2. 生成 Lua state:

Lua 的 state 包含核心的函式,後面所有的函式都會需要這個 state;在 open 和 close 中間就能呼叫 Lua 函式了:
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// write lua_call ... code here
lua_close(L);

3. 載入檔案,呼叫函式:

使用 luaL_dofile 打開 lua 檔案,等於是呼叫 lua 直譯器執行這個檔案,如果有直接執行的東西這時候就會有效果,像是 print("hello world") ,我們這裡只是定義變數 "add" 對應到相加的函式。
要呼叫 lua 函式,我們以函式 add 為例:
-- add.lua add function
add = function(a, b)
  return 42
end
那麼在 C 裡面就是:
luaL_dofile(L, "add.lua");
lua_getglobal(L, "add");
lua_pushnumber(L, a);
lua_pushnumber(L, b);
lua_call(L, 2, 1);  // 2 parameter, 1 return value
int sum = (int)lua_tointeger(L, -1);
lua_pop(L, 1)
簡單來說就是先用 lua_getglobal 拿到存在 global 裡面的函式 add(在 luaL_dofile 的時候建立的);把參數推到堆疊上;執行 lua_call,指定兩個參數跟一個回傳值,取出回傳值,把回傳值彈出堆疊。
如果函式有多個回傳值的話,會依序放在堆疊的 -1, -2 … 上;lua_pop 也要彈出更多值;Lua 的有一系列的函式來把東西推到堆疊上/彈出堆疊,簡單的應用,通常就是 lua_pushinteger/lua_tointeger, lua_pushnumber/lua_tonumber 來推/彈整數跟浮點數到堆疊上,詳情請參考文件

這裡只記錄最基礎的應用,實際上應該還有更複雜的應用,例如我要做決策的參數如果很多的時候該怎麼辦?或者其實我想要直接推一個 struct 進到 lua 是可以的嗎?
這兩個問題我目前都沒有答案,還有待研究,目前只知道 luajit 這個看起來好像停擺的專案有提供類似的東西,可以準備好 C 的 struct 來吃。
如果有大大們有答案的話麻煩教一下小弟,小弟現在很需要。

2019年9月4日 星期三

Rust 裡面那些 String 們

故事是這樣子的,最近把小弟自幹的編譯器加上 rust 的 llvm wrapper llvm-sys,經過一陣猛烈的攪動之後,自幹的編譯器終於可以 dump LLVM IR 了,雖然只會輸出一個空殼子…但有第一步總是好的。
不過小弟在綁定的時候遇到一個大問題,也就是 Rust 裡面的 String,到底怎麼會有這麼多種,因為寫的時候一直沒搞清楚,然後就會被編譯器噴上一臉的錯誤,覺得痛苦,於是決定來打篇整理文。
簡單來說,Rust 的 std 有四種 String,每個 String 都有動態記憶體模式跟沒有 size 資訊(不是 Sized)的靜態模式,他們是:
std::string::String <-> std::str
std::ffi:OsString <-> std::ffi::OsStr
std::path::PathBuf <-> std::path::Path
std::ffi::CString <-> std::ffi::CStr
還有一個比較少用,只能表示 ascii 128 字元組成的字串的 std::ascii::asciiExt,這裡就不介紹了。

一般的程式語言在數字型態通常都很固定,Rust 就很明確的分為 i8, i16, i32, i64 …,就偏偏字串是個大坑,因為從 ASCII 到 unicode,字串實在有太多分岐,儘管有 unicode 也不是到處適用。Rust 從設計上一開始就直接採用 utf-8 作為設計標準,原生的 String/str 就是 utf 8 字串。
可是呢,並不是所有作業系統都玩 utf8 這套,因此 Rust 有另一個使用 wtf8 的 OsString,wtf8 跟 utf8 的差異在於 wtf8 算是<格式比較差>的 utf8,會出現一些 utf8 不允許的位元組,偏偏規格沒有要求一定要完美格式,造成 windows 或 javascript 有時會出現這種格式不良的 wtf8 字串,因此 OsString ,跟專門用來表示路徑的 PathBuf 就是使用 wtf8。
有關 wtf8 請參考:https://simonsapin.github.io/wtf-8/

上面的字串都是在型態中記錄字串長度,結尾不會有 \0 字元,CString 則是最傳統的 null-terminated 字串,在呼叫 C 函式的時候,一定要用 CString 傳遞才行。
順帶一提,一般寫在 code 裡面的 let hello = "hello world" 的型態是 &'static str:生命週期為 static 的靜態字串。

知道了以上幾個區別之後,就來看看要怎麼使用它們:
String 最簡單,裡面一定要是 utf8,產生就是從 static str 產生,或者是 new 之後慢慢 push 進去:
let hello : String = String::from("hello");
let mut world : String = String::new();
world.push_str("world");
world.push('!');
OsString 是類似的,但只能從 String 轉過來(注意 String 的所有權會轉給 OsString),或者一樣 new 之後 push String 進去:
use std::ffi::{OsString, OsStr};
let oshello : OsString = OsString::from(hello);
let mut world : OsString = OsString::new();
world.push("world!");
PathBuf 其實就想成 OsString 就好,兩者也可以互相用 from 轉換:
use std::path::{PathBuf, Path};
let p1 : PathBuf = PathBuf::from(oshello)
let mut p2 = PathBuf::new();
p2.push("/dev");
上面說了,OsString 跟 PathBuf 用的是 wtf8,是 utf8 的超集,因此一般只能單向從 String 到 OsString,反向是不行的,呼叫 OsString::into_string() 得到的是 Result<String, OsString>,也就是有可能會轉失敗;或者就是用 into_lossy_string 把編碼不完整的地方變成 U+FFFD,utf8 的 replacement character。
PathBuf 則是沒有 into_string 可以用,只能先轉換成 OsString 再轉過去,我也不知道為什麼 core team 要這樣設計。

剩下的就是函式了,很有趣的是 String, OsString, PathBuf 都是動態容器,操作內容都要轉換到 str, OsStr, Path 上面去:
str 有操作字串用的 split_whitespace, starts_with 等等
OsStr 沒有任何特殊的函式XD。
Path 有很多對路徑的操作:is_absolute, parent, with_extension 等等,很多函式操作後都會得到 Path 或是 OsStr 讓你做接下來的操作。

CString 比較棘手一點,它要在 new 的時候代入 Vec<u8> (或者有實作 Into<Vec<u8>> 的型態)來建立 CString,new 會自動在後面加上 \0 ,因此這個 Vec 裡面不應該有 \0。
其實我覺得把 CString 想得 Vec<u8> 的另一種型態就好了,它本身也提供 into_bytes, as_bytes 等函式轉換成 Vec<u8> 的型態。
如果要從 String 跟 OsString 轉換過來的話,String 要用 as_bytes() 轉成 Vec<u8>,OsString 因為 unix 跟 windows 會有不同的 OsString 實作,不一定都能轉成 Vec<u8>,在 unix 要引入 std::os::unix::ffi::OsStrExt 就可以將 OsString 用 as_bytes() 轉成 Vec<u8>;Windows 則建議轉成 String 再轉成 bytes ,請參考這個網址

用上了 CString,最重要的就是要交給外部的 C 函式去用,要用 as_ptr() 取出字串部分的 pointer,得到的就是 * u8 了,有必要的話再加上 as *const i8 轉型一下。
例如我要呼叫這個函式:
LLVMPrintModuleToFile (LLVMModuleRef M, const char *Filename, char **ErrorMessage)
這個函式,我的檔案名稱是一個 OsString:
use std::ffi::{CString, CStr};
use std::os::unix::ffi::OsStrExt;
llvm::core::LLVMPrintModuleToFile(
           self.module,
            path.as_bytes().as_ptr() as *const i8,
            ptr::null_mut());
看了這麼多,簡單整理一下大概是這樣:

老實說每次只要在 Rust 裡面弄到 Path 都會弄到懷疑人生……
Related Posts Plugin for WordPress, Blogger...