在開發應用程式的時候,常會需要在程式加入圖片、聲音等等的素材。以開發Web應用程式來說,還會需要用到很多HTML模板、CSS、JavaScript、語言檔、資料庫設定檔等等的素材檔案。這些素材檔案常因為放不進程式執行檔中,而只好另外放在檔案系統的某個目錄下,由程式在執行階段的時候再去讀取。像這樣程式和素材分開的存放的作法雖然很常見,但其實會引發許多問題。例如,在設定程式執行環境的時候,工序會變得複雜,系統管理員必須要按照程式的說明文件,將指定的素材放置到指定的路徑之下。又或是把暴露在執行檔之外的素材,交付給客戶使用之後,難保他們不會將那些素材外流或是另作它用。講難聽一點就是有可能會被盜圖、盜設計排版、盜JS程式、盜資料庫,素材被他人盜用在其它的專案上。又或是一樣的程式,只不過把外部素材改一改、換一換之後,就被他人當成是自己做的應用程式來賣了。
那麼,Rust程式語言要如何把外部檔案素材給匯入至程式內呢?
include_bytes
Rust程式語言內建include_bytes!
巨集,可以傳入一個檔案路徑,編譯器在編譯程式的時候就會去讀取該檔案,並將該檔案內容直接轉成u8
陣列,以&'static [u8]
的型別回傳。
fn main() {
let file_data = include_bytes!("/path/to/file"); // &'static [u8]
// do something ...
}
include_str
Rust程式語言內建include_str!
巨集,類似include_bytes!
巨集,同樣可以傳入一個檔案路徑,編譯器也會在編譯程式的時候去讀取該檔案。不同的地方在於「include_str」巨集會將檔案以UTF-8編碼成Rust程式語言的字串,以&'static str
的型別回傳。
fn main() {
let file_text = include_str!("/path/to/file"); // &'static str
// do something ...
}
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程式專案的根目錄或根目錄的子目錄下。
fn main() {
let file_data = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", "path/to/file"));
// do something ...
}
如以上程式,就會去讀取Cargo程式專案的根目錄下的path/to/file
這個檔案。
include
Rust程式語言另外還有內建一個include!
巨集,這個巨集的使用方式跟include_bytes!
和include_str!
差不多,只不過它是直接把檔案內容作為程式碼使用。如果您的外部素材是一個符合Rust程式語言語法的陣列,或許可以使用這個include!
巨集。但是在大部分的情況下,應該要儘量少用它。
["Hello!", "Rust!"]
fn main() {
let array = include!("/path/to/file_array");
// do something ...
}
能解決編譯時間增加的套件──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
Cargo.toml
巨集的使用
lazy_static_include_bytes!
和lazy_static_include_str!
的用法差不多,以逗號分隔多個靜態變數名稱,並於每個靜態變數後面使用=>
語法來傳入要讀取的檔案路徑,路徑可以用字串定數或是「元祖(tuple)」來表示。每個靜態變數名稱前可以加上pub
關鍵字來進行可見度的修飾,pub
關鍵字前可以加上文件註解或是其它屬性。
例如:
use lazy_static_include::*;
lazy_static_include_str! {
/// doc
TEST => "data/test.txt",
/// doc
pub TEST2 => ("data", "test-2.txt"),
}
assert_eq!("This is just a test text.", TEST);
assert_eq!("Some text...", TEST2);
lazy_static_include_bytes!
和lazy_static_include_str!
巨集定義出來的靜態變數,其型別是lazy_static
的智慧型指標,如果要獲得&'static [u8]
或是&'static str
型別,可以使用*
(星號)進行「解參考」的動作。
use lazy_static_include::*;
lazy_static_include_bytes! {
/// doc
TEST => "data/test.txt",
}
let data: &'static [u8] = *TEST;
include_array
lazy_static_include_array
巨集的話,其實也是跟include
巨集一樣不太建議使用。第一個參數傳入靜態變數的名稱和陣列型別(以冒號:
分隔),第二個參數傳入要讀取的檔案路徑,路徑會相對於Cargo程式專案的根目錄。如果靜態變數是公開的(public),可以在靜態變數名稱前加上pub
關鍵字。
例如:
[123, 456, 789, 1_000, 500000000000u64]
["Hi", "Hello", "哈囉"]
use lazy_static_include::*;
lazy_static_include_array! {
/// doc
TEST: [u64; 5] => "data/u64_array.txt",
/// doc
pub TEST2: [&'static str; 3] => ("data", "string_array.txt")
}
assert_eq!(123, TEST[0]);
assert_eq!(456, TEST[1]);
assert_eq!(789, TEST[2]);
assert_eq!(1000, TEST[3]);
assert_eq!(500000000000u64, TEST[4]);
assert_eq!("Hi", TEST2[0]);
assert_eq!("Hello", TEST2[1]);
assert_eq!("哈囉", TEST2[2]);