Skip to content

Instantly share code, notes, and snippets.

@jiayun
Last active March 4, 2026 15:06
Show Gist options
  • Select an option

  • Save jiayun/00cabf3f20ea756f196c01ea3751471d to your computer and use it in GitHub Desktop.

Select an option

Save jiayun/00cabf3f20ea756f196c01ea3751471d to your computer and use it in GitHub Desktop.
Ch27 Development Practices — 完整範例
//! # 開發實務工具箱
//!
//! 本模組示範 Rust 文件註解(rustdoc)的最佳實務,
//! 包含函式文件、範例、以及 doc test。
/// 計算兩個整數的最大公因數(GCD)。
///
/// 使用歐幾里得演算法,時間複雜度為 O(log(min(a, b)))。
///
/// # Arguments
///
/// * `a` - 第一個非負整數
/// * `b` - 第二個非負整數
///
/// # Examples
///
/// ```
/// use ch27_development_practices::gcd;
///
/// assert_eq!(gcd(12, 8), 4);
/// assert_eq!(gcd(7, 0), 7);
/// assert_eq!(gcd(0, 5), 5);
/// assert_eq!(gcd(0, 0), 0);
/// ```
///
/// # Panics
///
/// 此函式不會 panic。
pub fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let temp = b;
b = a % b;
a = temp;
}
a
}
/// 語意版本號(SemVer)。
///
/// 遵循 [Semantic Versioning 2.0.0](https://semver.org/) 規範,
/// 格式為 `MAJOR.MINOR.PATCH`。
///
/// # Examples
///
/// ```
/// use ch27_development_practices::SemVer;
///
/// let v = SemVer::parse("1.42.3").unwrap();
/// assert_eq!(v.major, 1);
/// assert_eq!(v.minor, 42);
/// assert_eq!(v.patch, 3);
/// assert_eq!(v.to_string(), "1.42.3");
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SemVer {
/// 主版本號 — 不相容的 API 變更
pub major: u64,
/// 次版本號 — 向下相容的功能新增
pub minor: u64,
/// 修訂版本號 — 向下相容的錯誤修正
pub patch: u64,
}
impl SemVer {
/// 建立新的語意版本號。
///
/// # Examples
///
/// ```
/// use ch27_development_practices::SemVer;
///
/// let v = SemVer::new(2, 0, 1);
/// assert_eq!(v.to_string(), "2.0.1");
/// ```
pub fn new(major: u64, minor: u64, patch: u64) -> Self {
Self {
major,
minor,
patch,
}
}
/// 從字串解析語意版本號。
///
/// 格式必須為 `MAJOR.MINOR.PATCH`,每個部分都是非負整數。
///
/// # Examples
///
/// ```
/// use ch27_development_practices::SemVer;
///
/// assert_eq!(
/// SemVer::parse("1.0.0"),
/// Ok(SemVer::new(1, 0, 0))
/// );
///
/// assert!(SemVer::parse("not-a-version").is_err());
/// assert!(SemVer::parse("1.2").is_err());
/// ```
pub fn parse(s: &str) -> Result<Self, String> {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return Err(format!("expected 3 components, got {}", parts.len()));
}
let major = parts[0]
.parse::<u64>()
.map_err(|e| format!("invalid major: {e}"))?;
let minor = parts[1]
.parse::<u64>()
.map_err(|e| format!("invalid minor: {e}"))?;
let patch = parts[2]
.parse::<u64>()
.map_err(|e| format!("invalid patch: {e}"))?;
Ok(Self::new(major, minor, patch))
}
/// 判斷是否為正式發布版本(major >= 1)。
///
/// 根據 SemVer 規範,`0.x.y` 是初始開發階段,
/// 公開 API 不保證穩定。
///
/// # Examples
///
/// ```
/// use ch27_development_practices::SemVer;
///
/// assert!(!SemVer::new(0, 9, 0).is_stable());
/// assert!(SemVer::new(1, 0, 0).is_stable());
/// ```
pub fn is_stable(&self) -> bool {
self.major >= 1
}
/// 判斷此版本是否與另一個版本 API 相容。
///
/// 根據 SemVer:
/// - major 為 0 時,只有 minor 相同才相容
/// - major >= 1 時,major 相同即相容
///
/// # Examples
///
/// ```
/// use ch27_development_practices::SemVer;
///
/// let v1 = SemVer::new(1, 2, 0);
/// let v2 = SemVer::new(1, 5, 3);
/// assert!(v1.is_compatible_with(&v2));
///
/// let v3 = SemVer::new(2, 0, 0);
/// assert!(!v1.is_compatible_with(&v3));
///
/// // 0.x 系列:minor 不同就不相容
/// let pre1 = SemVer::new(0, 1, 0);
/// let pre2 = SemVer::new(0, 2, 0);
/// assert!(!pre1.is_compatible_with(&pre2));
/// ```
pub fn is_compatible_with(&self, other: &Self) -> bool {
if self.major == 0 && other.major == 0 {
self.minor == other.minor
} else {
self.major == other.major
}
}
}
impl std::fmt::Display for SemVer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl PartialOrd for SemVer {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SemVer {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.major
.cmp(&other.major)
.then(self.minor.cmp(&other.minor))
.then(self.patch.cmp(&other.patch))
}
}
/// 檢查字串是否為有效的 Rust 識別碼。
///
/// 有效的識別碼以字母或底線開頭,後接字母、數字或底線。
///
/// # Examples
///
/// ```
/// use ch27_development_practices::is_valid_identifier;
///
/// assert!(is_valid_identifier("hello_world"));
/// assert!(is_valid_identifier("_private"));
/// assert!(!is_valid_identifier("123abc"));
/// assert!(!is_valid_identifier(""));
/// ```
pub fn is_valid_identifier(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_alphabetic() || c == '_' => chars.all(|c| c.is_alphanumeric() || c == '_'),
_ => false,
}
}
/// 將 snake_case 轉換為 CamelCase。
///
/// # Examples
///
/// ```
/// use ch27_development_practices::to_camel_case;
///
/// assert_eq!(to_camel_case("hello_world"), "HelloWorld");
/// assert_eq!(to_camel_case("my_struct_name"), "MyStructName");
/// assert_eq!(to_camel_case("single"), "Single");
/// ```
pub fn to_camel_case(s: &str) -> String {
s.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(c) => {
let upper: String = c.to_uppercase().collect();
upper + &chars.collect::<String>()
}
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcd_basic() {
assert_eq!(gcd(12, 8), 4);
assert_eq!(gcd(54, 24), 6);
}
#[test]
fn test_gcd_with_zero() {
assert_eq!(gcd(0, 5), 5);
assert_eq!(gcd(7, 0), 7);
assert_eq!(gcd(0, 0), 0);
}
#[test]
fn test_gcd_coprime() {
assert_eq!(gcd(13, 7), 1);
}
#[test]
fn test_semver_parse() {
let v = SemVer::parse("1.42.3").unwrap();
assert_eq!(v, SemVer::new(1, 42, 3));
}
#[test]
fn test_semver_parse_invalid() {
assert!(SemVer::parse("1.2").is_err());
assert!(SemVer::parse("abc").is_err());
assert!(SemVer::parse("1.2.3.4").is_err());
}
#[test]
fn test_semver_ordering() {
let v1 = SemVer::new(1, 0, 0);
let v2 = SemVer::new(1, 1, 0);
let v3 = SemVer::new(2, 0, 0);
assert!(v1 < v2);
assert!(v2 < v3);
}
#[test]
fn test_semver_compatibility() {
let v1 = SemVer::new(1, 2, 0);
let v2 = SemVer::new(1, 9, 5);
assert!(v1.is_compatible_with(&v2));
let v3 = SemVer::new(2, 0, 0);
assert!(!v1.is_compatible_with(&v3));
}
#[test]
fn test_valid_identifier() {
assert!(is_valid_identifier("foo"));
assert!(is_valid_identifier("_bar"));
assert!(!is_valid_identifier("1bad"));
assert!(!is_valid_identifier(""));
}
#[test]
fn test_to_camel_case() {
assert_eq!(to_camel_case("hello_world"), "HelloWorld");
assert_eq!(to_camel_case("single"), "Single");
}
}
/// 示範 Rust 開發實務中的文件註解與工具鏈使用。
///
/// 本程式展示:
/// - rustdoc 文件註解格式(`///` 與 `//!`)
/// - doc test 內嵌於文件中
/// - clippy 友善的程式碼風格
/// - 模組化設計
use ch27_development_practices::{SemVer, gcd, is_valid_identifier, to_camel_case};
/// 展示格式化設定的效果。
///
/// rustfmt 會自動處理:
/// - 縮排(4 個空格)
/// - 行寬限制(預設 100)
/// - 尾隨逗號
fn demo_formatting() {
println!("=== rustfmt 格式化 ===");
// rustfmt 會自動對齊、排版這些結構
let config_options = vec![
("edition", "2024"),
("max_width", "100"),
("tab_spaces", "4"),
("use_field_init_shorthand", "true"),
];
for (key, value) in &config_options {
println!(" {key} = {value}");
}
}
/// 展示 clippy 友善的程式碼寫法。
///
/// 以下每段程式碼都遵循 clippy 建議的最佳實務。
fn demo_clippy_friendly() {
println!("\n=== clippy 友善寫法 ===");
// Good: 用 is_empty() 而非 len() == 0
let items: Vec<i32> = vec![];
if items.is_empty() {
println!(" 集合是空的(用 is_empty() 而非 len() == 0)");
}
// Good: 用 if let 處理單一模式
let maybe_value: Option<i32> = Some(42);
if let Some(v) = maybe_value {
println!(" 取得值:{v}(用 if let 而非 match 單一分支)");
}
// Good: 用 unwrap_or_default 而非 match None => default
fn get_nickname() -> Option<String> {
None
}
let fallback: String = get_nickname().unwrap_or_default();
println!(" 預設值:'{fallback}'(用 unwrap_or_default)");
// Good: 用 iter 方法鏈而非手動迴圈
let numbers = [1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().sum();
println!(" 總和:{sum}(用 .iter().sum() 而非手動迴圈)");
// Good: 用 to_string() 而非 format!("{}", x)
let n = 42;
let s = n.to_string();
println!(" 數字轉字串:{s}(用 .to_string() 而非 format!)");
// Good: 用 contains 而非手動搜尋
let text = "hello, world";
if text.contains("world") {
println!(" 找到子字串(用 .contains() 方法)");
}
}
/// 展示文件註解的功能。
fn demo_rustdoc() {
println!("\n=== rustdoc 文件註解 ===");
// 使用 lib.rs 中有完整文件的函式
let result = gcd(48, 18);
println!(" gcd(48, 18) = {result}");
let result = gcd(100, 75);
println!(" gcd(100, 75) = {result}");
// 識別碼驗證
println!(
" is_valid_identifier(\"hello\") = {}",
is_valid_identifier("hello")
);
println!(
" is_valid_identifier(\"123\") = {}",
is_valid_identifier("123")
);
// 命名轉換
println!(
" to_camel_case(\"my_struct\") = {}",
to_camel_case("my_struct")
);
}
/// 展示語意版本號操作。
fn demo_semver() {
println!("\n=== 語意版本號(SemVer) ===");
let versions = ["0.1.0", "1.0.0", "1.4.2", "2.0.0-beta"];
for v_str in &versions {
match SemVer::parse(v_str) {
Ok(v) => {
println!(
" {v_str} → major={}, minor={}, patch={}, stable={}",
v.major,
v.minor,
v.patch,
v.is_stable()
);
}
Err(e) => {
println!(" {v_str} → 解析失敗:{e}");
}
}
}
// 版本比較
let v1 = SemVer::new(1, 2, 0);
let v2 = SemVer::new(1, 5, 3);
let v3 = SemVer::new(2, 0, 0);
println!("\n 版本相容性:");
println!(" {v1} 與 {v2} 相容?{}", v1.is_compatible_with(&v2));
println!(" {v1} 與 {v3} 相容?{}", v1.is_compatible_with(&v3));
// 版本排序
let mut versions = vec![
SemVer::new(2, 1, 0),
SemVer::new(1, 0, 0),
SemVer::new(1, 9, 5),
SemVer::new(0, 1, 0),
];
versions.sort();
println!("\n 排序後:");
for v in &versions {
println!(" {v}");
}
}
/// 展示條件編譯。
fn demo_conditional_compilation() {
println!("\n=== 條件編譯 ===");
// cfg! 巨集在執行期回傳 bool
println!(" debug_assertions = {}", cfg!(debug_assertions));
println!(" target_os = {}", std::env::consts::OS);
println!(" target_arch = {}", std::env::consts::ARCH);
// #[cfg] 屬性在編譯期決定是否包含程式碼
#[cfg(debug_assertions)]
println!(" 這行只在 debug 模式下出現");
#[cfg(not(debug_assertions))]
println!(" 這行只在 release 模式下出現");
}
/// 展示測試模式。
fn demo_testing_patterns() {
println!("\n=== 測試模式 ===");
println!(" Rust 測試架構:");
println!(" - 單元測試:#[cfg(test)] mod tests,與程式碼同檔案");
println!(" - 整合測試:tests/ 目錄,測試公開 API");
println!(" - doc test:文件中的 ``` 區塊,同時是文件和測試");
println!(" - cargo test:一次執行所有測試");
println!();
println!(" 常用測試指令:");
println!(" cargo test # 執行所有測試");
println!(" cargo test -p <package> # 測試特定套件");
println!(" cargo test --doc # 只跑 doc test");
println!(" cargo test -- --nocapture # 顯示 println! 輸出");
println!(" cargo test test_name # 跑名稱匹配的測試");
}
fn main() {
println!("Ch27: Rust 開發實務\n");
demo_formatting();
demo_clippy_friendly();
demo_rustdoc();
demo_semver();
demo_conditional_compilation();
demo_testing_patterns();
println!("\n=== GitHub Actions CI 範例 ===");
println!(" 典型 Rust CI pipeline:");
println!(" 1. push / PR 觸發");
println!(" 2. cargo fmt -- --check (格式檢查)");
println!(" 3. cargo clippy -- -D warnings (lint 檢查)");
println!(" 4. cargo test (測試)");
println!(" 5. cargo build --release (建置)");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment