2.1 變數、型別與函式#
一個反直覺的預設:不可變#
Rust 最讓初學者意外的第一件事:變數預設不可變(immutable)。要能改,必須明確宣告 mut:
fn main() {
let x = 5;
// x = 6; // ← 編譯錯誤!x 不可變
let mut y = 5;
y = 6; // ✓ 有 mut 才能改
const MAX: u32 = 100; // 常數:編譯期決定,型別必須寫明
}
工程意義:預設不可變,讓「這個值會不會被改」在程式碼上一眼可見。 需要可變性時,mut 是一個明確的承諾,review 時會被特別檢視。
基本型別#
fn main() {
// 整數:i8/i16/i32/i64/i128/isize 與 u 開頭的無號版(u8/u32...)
let a: i32 = -42; // 預設整數型別是 i32
let b: u8 = 255;
// 浮點數
let f: f64 = 3.14; // 預設是 f64
// 布林與字元
let t: bool = true;
let c: char = '中'; // char 是 4 bytes,支援 Unicode
// 字串:兩種,初學者必分
let s1: &str = "hello"; // 字串切片(字面值就是 &str)
let s2: String = String::from("hello"); // 有所有權的字串(可增長)
}
常見困惑:
&str與String的區別——String擁有資料(可修改、可增長),&str是借用的一段文字(唯讀、不可增長)。函式參數若只是要讀文字,慣用&str。
型別推導與轉換#
Rust 有強大的型別推導——大多數時候不用寫型別,只在必要時標註:
let x = 5; // 編譯器推導為 i32
let y: f64 = 3.0; // 需要明確指定時才寫
// Rust 不做隱式轉換,必須明確:
let n = 5;
let m: f64 = n as f64; // 明確轉型
// let p: f64 = n; // ← 編譯錯誤!i32 不會自動轉 f64
沒有隱式轉換是刻意的:型別之間的轉換必須明確寫出來,避免「悄悄縮小精度」之類的隱藏錯誤。
函式#
fn main() {
let r = add(3, 4);
println!("3 + 4 = {}", r);
let s = greeting("小美");
println!("{}", s);
}
// 參數必須標型別;回傳型別用 -> 標註
fn add(a: i32, b: i32) -> i32 {
a + b // ← 最後一行「沒有分號」就是回傳值
}
fn greeting(name: &str) -> String {
format!("你好,{}", name) // format! 產生 String
}
初學者最容易踩的坑:最後一行「有沒有分號」決定是不是回傳值。
fn add(a: i32, b: i32) -> i32 {
a + b // ✓ 回傳 a+b
}
fn add2(a: i32, b: i32) -> i32 {
a + b; // ← 編譯錯誤!分號讓它變成「敘述」,沒有回傳值
}
控制流#
fn main() {
let n = 7;
// if 是「運算式」,可以賦值(條件不用括號)
let tag = if n % 2 == 0 { "偶數" } else { "奇數" };
// 迴圈三種
for i in 0..5 { // 0,1,2,3,4(range,最常用)
print!("{} ", i);
}
for i in 0..=5 { // 含結尾:0..5
print!("{} ", i);
}
let mut count = 0;
while count < 3 {
count += 1;
}
loop { // 無限迴圈,用 break 帶回傳值
count += 1;
if count > 10 { break count; }
}
}
本章小結#
- 變數預設不可變,
mut是明確的承諾 - 基本型別:
i32/u8/f64/bool/char;String擁有資料,&str是借用 - 沒有隱式型別轉換,轉型要明確
as - 函式:參數標型別、
->標回傳;最後一行沒有分號就是回傳值 if和迴圈都是運算式,可以賦值
想一想#
- 「預設不可變」對程式可讀性與 review 有什麼幫助?
String和&str的差別是什麼?函式參數該用哪個?a + b和a + b;的差別是什麼?這個設計的代價與好處各是什麼?