deno系列第二篇,給deno做rust擴充套件
這篇文章主要接著《編譯deno,deno結構解析》 作的第二篇,由於deno目標是給提供像瀏覽器一樣的安全的環境,但是如果你需要在後端實現一些deno不方便實現的東西,你要如何做呢?那為什麼我們不能給deno做一個擴充套件呢?我們就以做一個計算斐波那契數列的方法做一個deno做rust擴充套件的例子。
第一步:定義訊息型別
上篇文章目錄解析說到,deno是通過中間層使得v8和rust互相呼叫,那麼v8是c++寫的,rust又是另一門語言,那需要通訊要怎麼怎麼做呢?deno使用很常規的類似RPC來呼叫,只不過去掉了r。使用過thrift和grpc的同學都知道如果要實現多語言通訊實際上是要互相定義型別,deno也不例外,只不過使用的是flatbuffers,這裡有興趣自行學習。
所以我們第一步定義型別:
- 在src/msg.fbs中增加GetFibo和GetFiboRes兩種型別,型別名字可以隨便取,程式碼如下
union Any { Start, ... GetFibo, GetFiboRes } table GetFibo { num: int32; } table GetFiboRes { result: int32; } 複製程式碼
什麼意思呢?你可以這樣認為GetFibo就是定義了我傳入的引數列表型別,GetFiboRes則是定義了返回值的型別。而我們要做計算斐波那契數列的方法,那麼引數只有一個數字,結果也只有一個數字,所以將我們都只要定義一個數字型別就好。
寫好後,我們可以編譯一下
./tools/build.py # 生成target/debug/gen/msg_generated.ts,這個我們後面要用到 複製程式碼
第二步:建立與rust進行通訊的方法和ts的方法定義
- 新建一個檔案js/get_fibo.ts,程式碼如下
import * as msg from "gen/msg_generated"; import * as flatbuffers from "./flatbuffers"; import { assert } from "./util"; import * as dispatch from "./dispatch"; function req( num: number, ): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] { const builder = flatbuffers.createBuilder(); msg.GetFibo.startGetFibo(builder); msg.GetFibo.addNum(builder, num); const inner = msg.GetFibo.endGetFibo(builder); return [builder, msg.Any.GetFibo, inner]; } function res(baseRes: null | msg.Base): number { assert(baseRes !== null); assert(msg.Any.GetFiboRes === baseRes!.innerType()); const res = new msg.GetFiboRes(); assert(baseRes!.inner(res) !== null); return res.result(); } export function getFiboSync(num: number): number { return res(dispatch.sendSync(...req(num))); } export async function getFibo(num: number): Promise<number> { return res(await dispatch.sendAsync(...req(num))); } 複製程式碼
作下說明:
- gen/msg_generated 就是我們之前生成的資料型別定義
- flatbuffers 用來產生協議資料的工具
- assert 檢測資料是否異常的工具
- dispatch 傳送資料通訊的方法
此外如果我們只需要寫js而不需要通訊rust的話,其實就也不需要引用這些庫了,直接在getFiboSync和getFibo寫方法就好了。這個檔案ts主要用途就是和rust互動用的,同時定義下要暴露的ts方法,req方法是組轉要傳送的資料結構,res則是處理接收回來的訊息,dispatch傳送資料。
注
:getFiboSync和getFibo 分別代表同步方法和非同步方法
增加rust方法
在src/ops.rs增加方法,這裡的方法也主要是接收和資料組裝,程式碼如下:
... let op_creator: OpCreator = match inner_type { msg::Any::Accept => op_accept, msg::Any::Chdir => op_chdir, ... msg::Any::GetFibo => op_get_fibo //增加我們的方法 _ => panic!(format!( "Unhandled message {}", msg::enum_name_any(inner_type) )), ... fn op_get_fibo( _state: &Arc<IsolateState>, base: &msg::Base<'_>, data: libdeno::deno_buf, ) -> Box<Op> { assert_eq!(data.len(), 0); let inner = base.inner_as_get_fibo().unwrap(); let cmd_id = base.cmd_id(); let num = inner.num(); blocking(base.sync(), move || -> OpResult { // 計算fibonacci數列 let sqrt5 = 5_f64.sqrt(); let c1 = (1.0_f64+sqrt5)/2.0_f64; let c2 = (1.0_f64-sqrt5)/2.0_f64; let result_f = (sqrt5/5.0_f64)*(c1.powi(num)-c2.powi(num)); let result = result_f as i32; let builder = &mut FlatBufferBuilder::new(); let inner = msg::GetFiboRes::create( builder, &msg::GetFiboResArgs { result, }, ); Ok(serialize_response( cmd_id, builder, msg::BaseArgs { inner: Some(inner.as_union_value()), inner_type: msg::Any::GetFiboRes, ..Default::default() }, )) }) } ... 複製程式碼
這裡稍微解釋一下rust的match在這裡的意思,你可以理解為一個增強版的switch,就是GetFibo的資料型別過來的話,就執行op_get_fibo方法,而op_get_fibo主要是在封裝FlatBufferBuilder資料,而真正有效計算斐波那契數列的程式碼其實就一點,當然如果功能程式碼量大則可以新建一個rust檔案來搞,如下:
let sqrt5 = 5_f64.sqrt(); let c1 = (1.0_f64+sqrt5)/2.0_f64; let c2 = (1.0_f64-sqrt5)/2.0_f64; let result_f = (sqrt5/5.0_f64)*(c1.powi(num)-c2.powi(num)); let result = result_f as i32; 複製程式碼
最後一步
其實到這裡鏈路就算徹底打通了,我們只差最後一步,把我們的方法暴露出來
- 修改js/deno.ts檔案,把get_fibo.ts的方法暴露出去即可
... export { getFiboSync, getFibo } from "./get_fibo"; ... 複製程式碼
編譯之後就搞定了
./tools/build.py 複製程式碼
測試程式碼如下:
import * as deno from "deno"; (async()=>{ console.log(deno.getFiboSync(10)); console.log(await deno.getFibo(11)); })(); 複製程式碼
其實在上一篇我也有講到,學習deno就是學習一個庫,相信看過測試程式碼就知道原因了。