【Rust】螃蟹一呀爪八個,兩頭尖尖,這麼大的個…
前言
雖然聽說Rust的語法很難,但是筆者為什麼會想要學呢?主要是看到了這個APP - BongoCat,是一個滿有趣的鍵盤互動功能,主要是利用Tauri - Rust + Web去改寫這個用C#寫的Bongo-Cat-Mver,讓它能跑在windows / linux / macOS上,事不宜遲,我們馬上行動…
作業環境
項目 | 版本 |
---|---|
macOS | Sequoia 15.5 |
Visual Studio Code | 1.100.2 |
Rust | 1.87 |
介紹
- Rust是Mozilla主導開發的通用、編譯型程式語言。設計準則為「安全、並行、實用」,支援函數式、並行式、程序式以及物件導向的程式設計風格。
- 也可以說,它是一個現代化的C++語言,有滿多的程式碼都用有Rust改寫,像是Windodws 11的核心 / Android的元件 / macOS的新酷音輸入法…等,當然也有一般人把C++的項目改成Rust,無非是為了穩定性更高…
- 但…不得不說,Rust的圖示真的是比不上Go的地鼠啊,就是…很工業風,好在它的吉祥物 - 煮熟的螃蟹有扳回一成,而且還有3D版本的喲…
安裝
brew install rust
rustc --version
cargo --version
Hello, World!
- 學程式語言的第一課,好像約定俗成都一定要來個Hello, World!,那我們也來寫一個吧,就叫做hello.rs…
- 可以用指令執行,或者在VSCode左上有個Run的按鍵,用它也可以執行…
/// hello.rs
fn main() {
println!("Hello, world!");
}
rustc hello.rs
./hello
建立新專案
cd ~/Desktop
cargo new rust_first
code rust_first
- 簡單介紹一下專案結構,src放程式檔,Cargo.toml是專案設定檔,就跟node.js的package.json很像…
- TOML(Tom’s Obvious, Minimal Language)格式,是一個小規模、易於使用的語意化的設定檔格式,其實滿像Windows的ini設定檔格式
- 可以用cargo run來執行專案,其它指令就不細說了…
cargo run --bin rust_first
cargo new
cargo build
cargo check
cargo clean
cargo doc --open
cargo tree
做一個文字檔存取功能
- 接下來我們要做一個GMT顏色的JSON檔,之後做成Swift套件來使用…
- 所謂GMT(Generic Mapping Tools)使用的顏色名稱主要來自於 X11 顏色規範,這是一個在 Unix/Linux 系統中廣泛使用的顏色命名系統…
- 就是將在這個網頁的顏色色碼相關的內容,先存成file/Color.txt,轉成CSV / JSON檔,以利於後面的使用…
255
250
250
snow
248
248
255
ghostwhite
.
.
.
- 程式碼如下,內容就不細說了,語法還滿像C++的,就看看註解吧…
use std::fs::File; // 可以一個一個單獨use
use std::io::{BufRead, BufReader, Write}; // 也可以很多個use
fn main() -> std::io::Result<()> {
table_to_csv("file/Color.txt", "file/Color.csv")?;
Ok(())
}
fn table_to_csv(txt_file: &str, csv_file: &str) -> std::io::Result<()> {
let file = File::open(txt_file)?; // 開啟檔案位置 (有錯就return Err()) => let 常數
let reader = BufReader::new(file); // 建立讀取器 (一行一行讀)
let mut output = File::create(csv_file)?; // 建立要存的檔案路徑 (要存在哪裡?) => let mut 變數
let mut current_line = Vec::new(); // 把讀到的內容轉成 [255,250,250,snow]
// 把讀到的內容轉成 [255,250,250,snow] 的過程 => 一行一行存到CSV中
// (空的不要 / 是色碼數字就記起來 / 記下最後的顏色名稱)
for line in reader.lines() {
let line = line?.trim().to_string();
if line.is_empty() { continue; }
if line.parse::<u8>().is_ok() { current_line.push(line); continue; }
if current_line.is_empty() { continue; }
current_line.push(line);
let color_values = current_line.join(",");
writeln!(output, "{}", color_values)?;
current_line.clear();
}
Ok(())
}
- 比較要說明的是「Result的語法糖:?」,不過還真的是滿好用的…
- 其實跟Swift的Result是一樣的,成功和失敗二選一…
// 就是 let file = File::open(txt_file)?
fn _result_demo_(txt_file: &str) -> std::io::Result<()> {
let result = File::open(txt_file);
match result {
Err(error) => {
print!("{}" , error);
return Err(error)
}
Ok(file) => {
let size = file.metadata()?.len();
println!("檔案位址: {:p}, 檔案大小: {} bytes" , &file, size);
}
}
// if result.is_err() {
// println!("無法開啟檔案:{}", txt_file);
// return Err(result.unwrap_err());
// }
// let new_file = result.unwrap();
Ok(())
}
CSV to JSON
- 接下來我們要把轉出來的CSV,再把它轉成JSON…
- 雖然CSV容易編譯,但是JSON還是比較容易在網路上傳遞,而且可以知道有沒有輸入到重複的名稱…
- 我們把CSV以逗點分開,取得RGB數值與名稱,再使用serde / serde_json這個JSON套件,來包裝RGB數值,最後就存起來…
- 記得在Cargo.toml要加上要使用的套件設定…
- 也可以利用cargo tree指令來查看該專案裝了什麼套件…
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
cargo tree
- 部分程式碼如下,其實語法也算是滿直覺的…
fn main() -> std::io::Result<()> {
table_to_csv("file/Color.txt", "file/Color.csv")?;
csv_to_json("file/Color.csv","file/Color.json")?;
Ok(())
}
fn table_to_csv(txt_file: &str, csv_file: &str) -> std::io::Result<()> {}
fn csv_to_json(csv_file: &str, json_file: &str) -> std::io::Result<()> {
let file = File::open(csv_file)?;
let reader = BufReader::new(file);
let mut color_map = serde_json::Map::new(); // 儲存{<名稱>:<GRB顏色>}
let mut output_file: File = File::create(json_file)?;
for line in reader.lines() {
let line = line?;
let parts: Vec<&str> = line.split(',').collect(); // 255,250,250,snow => [255,250,250,snow]
if parts.len() != 4 { continue; }
let (red, green, blue, name) = match (parts.get(0), parts.get(1), parts.get(2), parts.get(3)) {
(Some(r), Some(g), Some(b), Some(n)) => (r, g, b, n),
_ => continue
};
// 儲存RGB數值,不是0~255就轉成0
let rgb = json!({
"red": red.parse::<u8>().unwrap_or(0),
"green": green.parse::<u8>().unwrap_or(0),
"blue": blue.parse::<u8>().unwrap_or(0)
});
color_map.insert(name.to_string(), rgb);
}
write!(output_file, "{}", serde_json::to_string_pretty(&color_map)?)?; // 寫入 JSON 檔案
println!("JSON 檔案已建立:{}", json_file);
Ok(())
}
fn _optional_demo_(array: Vec<&str>) -> std::io::Result<&str> {
let red = match array.get(0) {
Some(red) => red,
None => return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"陣列是空的"
))
};
return Ok(red);
}
JSON to HexString
- 最後我們來將RGB色碼,轉成16位元的色碼…
- 為什麼要這麼做呢?因為在JSON Editor Online這個網路IDE上,它可以顯示16位元色碼的顏色,非常的方便、直覺啊…
- 部分程式碼如下…
fn main() -> std::io::Result<()> {
table_to_csv("file/Color.txt", "file/Color.csv")?;
csv_to_json("file/Color.csv","file/Color.json")?;
json_to_hex("file/Color.json", "file/HexColor.json")?;
Ok(())
}
fn table_to_csv(txt_file: &str, csv_file: &str) -> std::io::Result<()> {}
fn csv_to_json(csv_file: &str, json_file: &str) -> std::io::Result<()> {}
fn json_to_hex(input_json: &str, output_json: &str) -> std::io::Result<()> {
let json_str = std::fs::read_to_string(input_json)?;
let colors: Value = serde_json::from_str(&json_str)?;
let mut hex_colors = serde_json::Map::new();
let object = match colors.as_object() {
Some(obj) => obj,
None => return Ok(())
};
for (name, color) in object {
let red = color["red"].as_u64().unwrap_or(0) as u8;
let green = color["green"].as_u64().unwrap_or(0) as u8;
let blue = color["blue"].as_u64().unwrap_or(0) as u8;
let hex_string = format!("#{:02X}{:02X}{:02X}", red, green, blue);
hex_colors.insert(name.to_string(), json!(hex_string));
}
// 寫入新的 JSON 檔案
let mut output_file = File::create(output_json)?;
write!(output_file, "{}", serde_json::to_string_pretty(&hex_colors)?)?;
println!("已轉換為 16 進位色碼格式:{}", output_json);
Ok(())
}
- 恭喜老爺夫人,賀成交…🫶
製作常用工具集
- 身為WW系列的作者,常做工具集也是件很正常的事情嘛…
- 而且Rust有impl功能,很像Swift的Extension,可以一個區塊一個區塊的去分類,非常的方便啊…
- 在要轉成mod的資料夾之下,加一個mod.rs的檔案,這個資料夾就變成「模組」了,跟Python的init.py滿像的…
- 然後再mod.rs中,加入要成為mod的檔名…
pub mod file;
pub mod string;
- 我們做一個文字小工具,使用struct,加入一個RGB色碼轉16進位色碼的功能…
- 其中有靜態方法是像是一般的fn一樣,用::呼叫的…
- 而實例方法的參數會多出一個&self,用.呼叫,這也跟Python滿像的…
pub struct StringManager;
// MARK: - static
impl StringManager {
/// RGB色碼轉16進位色碼 => (255, 248, 240) -> #F0F8FF
/// ## 參數
/// - `red` - 紅色色碼 (0~255)
/// - `green` - 綠色色碼 (0~255)
/// - `blue` - 藍色色碼 (0~255)
/// ## 回傳
/// - `String` - 16進位色碼
pub fn hex_string(red: u8, green: u8, blue: u8) -> String {
format!("#{:02X}{:02X}{:02X}", red, green, blue)
}
}
// MARK: - class
impl StringManager {
/// RGB色碼轉16進位色碼 => (255, 248, 240) -> #F0F8FF
/// ## 參數
/// - `&self` - 實例方法
/// - `red` - 紅色色碼 (0~255)
/// - `green` - 綠色色碼 (0~255)
/// - `blue` - 藍色色碼 (0~255)
/// ## 回傳
/// - `String` - 16進位色碼
pub fn _hex_string(&self, red: u8, green: u8, blue: u8) -> String {
return format!("#{:02X}{:02X}{:02X}", red, green, blue);
}
}
- 大家可以試試看,製作自己的工具集…
mod utility;
use utility::string::StringManager;
fn main() {
let manager = StringManager;
let static_hex = StringManager::hex_string(0, 0, 0);
let hex_string = manager._hex_string(255, 240, 235);
print!("{} {}", static_hex, hex_string);
}
同場加映 - Extension
- 有時想想,如果Rust能像Swift一般,在一般的類別加上fn就好了啊…
- 當然有~~~,就是使用trait…
- 做法上跟使用struct差不多,也是要加入mod.rs,只是換成trait關鍵字…
- 其中where跟Swift的很像,就是要符合後面的Protocol,果然晚出生的語言就是有這個好處,語法上很現代化…
- 我們這邊要為&str加上更多的功能,所以要加入FromStr這個trait…
- 話不多說,我們就加上「是不是數字?」跟「轉成數字」的功能…
use std::str::FromStr;
use std::fmt::Debug;
pub trait StringExtension {
fn is_number<T>(&self) -> bool where T: FromStr + Debug + PartialOrd + Copy;
fn _number<T>(&self) -> Option<T> where T: FromStr + Debug + PartialOrd + Copy;
}
impl<S> StringExtension for S where S: AsRef<str> {
/// 判斷文字是否能轉成數字?
/// ## 回傳
/// - `bool` - 是否能轉成數字
fn is_number<T>(&self) -> bool where T: FromStr + Debug + PartialOrd + Copy {
let this = self.as_ref();
if this.is_empty() { return false; }
match this.parse::<T>() {
Ok(_) => true,
Err(_) => false
}
}
/// 文字 => 數字
/// ## 回傳
/// - `Option<T>` - 數字
fn _number<T>(&self) -> Option<T> where T: FromStr + Debug + PartialOrd + Copy {
let this = self.as_ref();
if this.is_empty() { return None; }
match this.parse::<T>() {
Ok(num) => Some(num),
Err(_) => None
}
}
}
- 再整理一下程式碼,完工…
use std::fs::File; // 可以一個一個單獨use
use std::io::{BufRead, BufReader, Write}; // 也可以很多個use
use serde_json::{json, Value}; // 引用外部套件
mod utility;
use utility::string::StringManager;
mod extension;
use extension::string::StringExtension;
fn main() -> std::io::Result<()> {
table_to_csv("file/Color.txt", "file/Color.csv")?;
csv_to_json("file/Color.csv","file/Color.json")?;
json_to_hex("file/Color.json", "file/HexColor.json")?;
Ok(())
}
/// # 將txt => csv
/// ## 參數
/// - `txt_file` - txt檔案路徑
/// - `csv_file` - csv檔案路徑
/// ## 回傳
/// - `std::io::Result<()>` - 檔案內容或錯誤
fn table_to_csv(txt_file: &str, csv_file: &str) -> std::io::Result<()> {
let file = File::open(txt_file)?; // 開啟檔案位置 (有錯就return error) => let 常數
let reader = BufReader::new(file); // 建立讀取器 (一行一行讀)
let mut output = File::create(csv_file)?; // 建立要存的檔案路徑 (要存在哪裡?) => let mut 變數
let mut current_line = Vec::new(); // 把讀到的內容轉成 [255,250,250,snow]
// 把讀到的內容轉成 [255,250,250,snow] 的過程 => 一行一行存到CSV中
// (空的不要 / 是色碼數字就記起來 / 記下最後的顏色名稱)
for line in reader.lines() {
let line = line?.trim().to_string();
if line.is_empty() { continue; }
if line._is_number::<u8>() { current_line.push(line); continue; }
if current_line.is_empty() { continue; }
current_line.push(line);
let color_values = current_line.join(",");
writeln!(output, "{}", color_values)?;
current_line.clear();
}
Ok(())
}
/// # 將csv => json
/// ## 參數
/// - `csv_file` - csv檔案路徑
/// - `json_file` - json檔案路徑
/// ## 回傳
/// - `std::io::Result<()>` - 檔案內容或錯誤
fn csv_to_json(csv_file: &str, json_file: &str) -> std::io::Result<()> {
let file = File::open(csv_file)?;
let reader = BufReader::new(file);
let mut color_map = serde_json::Map::new(); // 儲存{<名稱>:<GRB顏色>}
let mut output_file: File = File::create(json_file)?;
for line in reader.lines() {
let line = line?;
let parts: Vec<&str> = line.split(',').collect(); // 255,250,250,snow => [255,250,250,snow]
if parts.len() != 4 { continue; }
let (red, green, blue, name) = match (parts.get(0), parts.get(1), parts.get(2), parts.get(3)) {
(Some(r), Some(g), Some(b), Some(n)) => (r, g, b, n),
_ => continue
};
// 儲存RGB數值,不是0~255就轉成0
let rgb = json!({
"red": red._number::<u8>().unwrap_or(0),
"green": green._number::<u8>().unwrap_or(0),
"blue": blue._number::<u8>().unwrap_or(0)
});
color_map.insert(name.to_string(), rgb);
}
write!(output_file, "{}", serde_json::to_string_pretty(&color_map)?)?; // 寫入 JSON 檔案
println!("JSON 檔案已建立:{}", json_file);
Ok(())
}
/// # 將json重組成有hex顏色的json
/// ## 參數
/// - `input_json` - json檔案路徑
/// - `output_json` - json檔案路徑
/// ## 回傳
/// - `std::io::Result<()>` - 檔案內容或錯誤
fn json_to_hex(input_json: &str, output_json: &str) -> std::io::Result<()> {
let json_str = std::fs::read_to_string(input_json)?;
let colors: Value = serde_json::from_str(&json_str)?;
let mut hex_colors = serde_json::Map::new();
let object = match colors.as_object() {
Some(obj) => obj,
None => return Ok(())
};
for (name, color) in object {
let red = color["red"].as_u64().unwrap_or(0) as u8;
let green = color["green"].as_u64().unwrap_or(0) as u8;
let blue = color["blue"].as_u64().unwrap_or(0) as u8;
let hex_string = StringManager::hex_string(red, green, blue);
hex_colors.insert(name.to_string(), json!(hex_string));
}
// 寫入新的 JSON 檔案
let mut output_file = File::create(output_json)?;
write!(output_file, "{}", serde_json::to_string_pretty(&hex_colors)?)?;
println!("已轉換為 16 進位色碼格式:{}", output_json);
Ok(())
}