在開發應用程式的時候,常會需要在程式加入圖片、聲音等等的素材。以開發Web應用程式來說,還會需要用到很多HTML模板、CSS、JavaScript、語言檔、資料庫設定檔等等的素材檔案。這些素材檔案常因為放不進程式執行檔中,而只好另外放在檔案系統的某個目錄下,由程式在執行階段的時候再去讀取。像這樣程式和素材分開的存放的作法雖然很常見,但其實會引發許多問題。例如,在設定程式執行環境的時候,工序會變得複雜,系統管理員必須要按照程式的說明文件,將指定的素材放置到指定的路徑之下。又或是把暴露在執行檔之外的素材,交付給客戶使用之後,難保他們不會將那些素材外流或是另作它用。講難聽一點就是有可能會被盜圖、盜設計排版、盜JS程式、盜資料庫,素材被他人盜用在其它的專案上。又或是一樣的程式,只不過把外部素材改一改、換一換之後,就被他人當成是自己做的應用程式來賣了。



那麼,Rust程式語言要如何把外部檔案素材給匯入至程式內呢?

include_bytes

Rust程式語言內建「include_bytes」巨集,可以傳入一個檔案路徑,編譯器在編譯程式的時候就會去讀取該檔案,並將該檔案內容直接轉成u8陣列,以「&'static [u8]」的型別回傳。

include_str

Rust程式語言內建「include_str」巨集,類似「include_bytes」巨集,同樣可以傳入一個檔案路徑,編譯器也會在編譯程式的時候去讀取該檔案。不同的地方在於「include_str」巨集會將檔案以UTF-8編碼成Rust程式語言的字串,以「&'static str」的型別回傳。

include_bytes和include_str的相對路徑

「include_bytes」和「include_str」巨集所輸入的路徑可以使用絕對路徑或是相對路徑。相對路徑是相對於使用「include_bytes」或「include_str」巨集的「.rs」原始碼檔案的路徑,舉例來說,如果原始碼檔案路徑為「/path/to/file.rs」,則「include_bytes!("123.jpg")」所使用到的檔案為「/path/to/123.jpg」;「include_bytes!("../123.jpg")」所使用到的檔案為「/path/123.jpg」。

若是覺得路徑相對於「.rs」原始碼檔案的路徑在使用上會不太方便的話,也可以配合「concat」和「env」巨集,將外部素材的路徑指定到Cargo程式專案的根目錄或根目錄的子目錄下。

如以上程式,就會去讀取Cargo程式專案的根目錄下的「path/to/file」這個檔案。

include

Rust程式語言另外還有內建一個「include」巨集,這個巨集的使用方式跟「include_bytes」和「include_str」差不多,只不過它是直接把檔案內容作為程式碼使用。如果您的外部素材是一個符合Rust程式語言語法的陣列,或許可以使用這個「include」巨集。但是在大部分的情況下,應該要儘量少用它。

能解決編譯時間增加的套件──Lazy Static Include

將外部素材與程式一同編譯,除了會使讓編譯出來的檔案體積增大外,還會大大增加編譯所需時間,使得開發效率下降。為了有效解決這個問題,筆者開發了一個「Lazy Static Include」套件,以「lazy_static_include_bytes」、「lazy_static_include_str」和「lazy_static_include_array」巨集來取代內建的「include_bytes」、「include_str」和「include」巨集。

顧名思義,這個套件利用Lazy Load的機制,在程式執行階段使用到外部素材檔案的時候,才會去讀取檔案。但是當我們使用「release」模式來編譯Cargo程式專案的時候,這個套件就會把外部素材檔案和Rust程式編譯在一起。換句話說,就算我們正式發行程式的時候外部素材檔案和Rust程式是編譯在一起的,但是在開發階段(非「release」模式時),由於是採用Lazy Load來讀取檔案,因此不會影響到編譯時間,開發效率自然也就不會下降啦!

Crates.io

https://crates.io/crates/lazy-static-include

Cargo.toml

lazy-static-include = "*"

巨集的使用

「lazy_static_include_bytes」和「lazy_static_include_str」的用法差不多,第一個參數傳入要宣告的靜態變數的名稱,第二個參數傳入要讀取的檔案路徑,路徑會相對於Cargo程式專案的根目錄。如果靜態變數是公開的(public),可以在靜態變數名稱前加上「pub」關鍵字。

例如:

如果要一次讀取多個檔案,組成靜態的Vec結構實體的話,可以在第二個參數後面加入更多的檔案路徑參數。如下:

如果只有一個檔案路徑,卻也想要變成Vec結構實體的話,可以使用「lazy_static_include_bytes_vec」或是「lazy_static_include_str_vec」巨集。如下:

「lazy_static_include_bytes」、「lazy_static_include_str」和「lazy_static_include_bytes_vec」、「lazy_static_include_str_vec」巨集定義出來的靜態變數,其型別都是「lazy_static」的智慧型指標,如果要獲得「&'static [u8]」、「&'static str」型別或是Vec型別等,必須使用「*」(星號)進行「解參考」的動作,例如:

為了避免搶走智慧型指標對Vec結構實體的擁有權,「解參考」後還需要再進行「參考」,來取得Vec參考型別的值。

include_array

「lazy_static_include_array」巨集的話,其實也是跟「include」巨集一樣不太建議使用。第一個參數傳入靜態變數的名稱和陣列型別(以冒號「:」分隔),第二個參數傳入要讀取的檔案路徑,路徑會相對於Cargo程式專案的根目錄。如果靜態變數是公開的(public),可以在靜態變數名稱前加上「pub」關鍵字。

例如:

如果要一次讀取多個檔案,組成靜態的巢狀Vec結構實體的話,可以在第二個參數後面加入更多的檔案路徑參數。如下:

如果只有一個檔案路徑,卻也想要變成巢狀Vec結構實體的話,可以使用「lazy_static_include_array_vec」巨集。如下: