今天主要會講的部分有
相信各位已經有自己熟知的程式語言,所以這裡不會鉅細彌遺的講過所有語法,如果是第一次學習程式語言,可以去官網上找更完整的介紹.
接下來的示範都會在昨天建立的專案底下實作,如果還沒創建好專案,可以先去看看昨天的文章.
創建完後的結構應該會長成這樣
編輯完後在終端輸入指令
cargo run
就會幫你編譯並且執行
需要安裝套件的話 就在終端輸入
cargo add
相關套件可以去 https://crates.io/ 查看
在 Rust 中,變數預設是不可變(immutable),必須顯式聲明為 mut 才能改值。
另外有 const 與 shadowing 的概念。
建立不可變變數。
fn main() {
let x = 5; // 不可變
println!("x = {}", x);
// x = 6; // ❌ 編譯錯誤:不可變
}
建立可變變數。
fn main() {
let mut y = 10; // 可變
println!("y = {}", y);
y = 20; // ✅ 可以改值
println!("y = {}", y);
}
常數必須在編譯時就決定,且型別必須標明。
const PI: f64 = 3.14159;
fn main() {
println!("PI = {}", PI);
}
同名變數重新宣告,不需要 mut,可以改型別。
fn main() {
let z = 5;
let z = z + 1; // 新的 z
let z = "Hello"; // 型別改成字串
println!("z = {}", z);
}
Rust 是靜態型別語言,但可推斷型別。
fn main() {
let a: i32 = -42; // 32位整數
let b: u64 = 100; // 無號64位整數
println!("a = {}, b = {}", a, b);
}
fn main() {
let pi: f64 = 3.14; // 預設 f64
let e: f32 = 2.71; // 單精度
println!("pi = {}, e = {}", pi, e);
}
fn main() {
let is_rust_fun: bool = true;
println!("Rust 好玩嗎?{}", is_rust_fun);
}
fn main() {
let letter: char = 'A';
let emoji: char = '🦀';
println!("字母: {}, Emoji: {}", letter, emoji);
}
Rust 函式使用 fn 宣告,必須明確參數型別,回傳值型別用 -> 表示。
fn main() {
greet("Alice");
let sum = add(3, 5);
println!("3 + 5 = {}", sum);
}
fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn add(x: i32, y: i32) -> i32 {
x + y // 省略分號表示回傳
}
fn main() {
let number = 10;
if number > 0 {
println!("正數");
} else if number < 0 {
println!("負數");
} else {
println!("零");
}
}
fn main() {
let mut count = 0;
loop {
count += 1;
if count == 3 {
break;
}
}
println!("計數結束: {}", count);
}
fn main() {
let mut n = 5;
while n > 0 {
println!("n = {}", n);
n -= 1;
}
}
fn main() {
for i in 1..=3 {
println!("i = {}", i);
}
}
以上是關於資料型態跟變數的部分,接著我們要進入相當重要的 - Ownership 的概念
在開始介紹前,官網上提到了 Stack 跟 Heap 兩種記憶體儲存方式的差別,這邊做一個總結
Stack 的操作非常簡單,新增資料只要把資料堆在最後(或最上面)一格就好,但如果要放資料到 Heap 的話,要考慮容量夠不夠大,不夠的話要再去要更多的空間,所以在效能上 Stack 會比 Heap 好.
此外,Stack 上放的資料必須是已知固定大小,像是整數、浮點數,布林值或 Array 之類的資料,如果像是 Vector 、String 這種可能變更大小的資料就會被放在 Heap.
這一個觀念在學習 Rust 的時候非常重要.
所謂的 Ownership 做的就是記憶體管理,如果過去學過 C 語言的可能不陌生,但如果是學習 python 為主的話,這部分可能需要一點時間習慣.
那開始之前,先說明一下為什麼需要 Ownership,最主要的原因就是為了 Concurrency,在多個執行緒下要保證不出狀況,在撰寫程式碼的時候就需要加入規則去管理這些記憶體的配置和釋放.
底下我們透過程式碼來介紹 Ownership ,現在有一個 function,用來獲取球員上場得分
fn main() {
let points = get_points();
println!("{:?}", points);
}
fn get_points() -> Vec<i32> {
let points = vec![23, 11, 10];
return points;
}
當在 main 裡呼叫 get_points 的時候,points 這個變數的所有權是屬於 main 這個 scope 的,所以即使 get_points 這個 scope 結束,他依然不會釋放.
但假設像底下的情況
fn main() {
let points = get_points();
let total_point = get_total_point(points);
println!("{:?}", points);
}
fn get_points() -> Vec<i32> {
let points = vec![23, 11, 10];
return points;
}
fn get_total_point(points: Vec<i32>) -> i32 {
let mut total = 0;
for point in points.iter() {
total += point;
}
return total;
}
這裡就會報錯 因為在 let total_point = get_total_point(points); 的時候 已經將所有權轉移給 get_total_point了,所以當get_total_point這個scope結束後,points就被釋放掉了
所以為了解決這個問題,我們必須這麼修改
fn main() {
let points = get_points();
let total_point = get_total_point(&mut points);
println!("{:?}", points);
}
fn get_points() -> Vec<i32> {
let points = vec![23, 11, 10];
return points;
}
fn get_total_point(points: &mut Vec<i32>) -> i32 {
let mut total = 0;
for point in points.iter() {
total += point;
}
return total;
}
在這裡,加入了 &mut ,這樣一來即使points的所有權就還會是在main這個scope裡.
希望上面的介紹能讓各位認識 Ownership,底下我出了一個範例,可以想一下過程中buffer是屬於哪個scope呢,可以先想完後,再對答案.
但開始前,因為底下程式碼有用到 move 的功能,這邊解釋一下
move 會強制閉包(closure)取得外層變數的所有權(ownership),而不是借用(reference)它。
這樣閉包內的變數會被移動(moved)到閉包裡,外層變數就不能再使用。
這邊一樣舉個例子
fn main() {
let data = vec![1, 2, 3];
let closure = || {
println!("{:?}", data); // 借用
};
closure();
println!("{:?}", data); // ✅ 還能用
}
fn main() {
let data = vec![1, 2, 3];
let closure = move || {
println!("{:?}", data); // 擁有權被移進來
};
closure();
// println!("{:?}", data); // ❌ 編譯錯誤,data 已被移走
}
這樣的功能在處理多個執行緒的時候,可以確保同一時間只有一個執行序能修改資料,非常重要.
fn log_len(s: &str) {
println!("log_len: {}", s.len());
}
fn take_and_give_back(s: String) -> String {
println!("take_and_give_back got: {}", s);
s
}
fn main() {
let mut title = String::from("Rust");
log_len(&title);
{
let mut buffer: Vec<String> = Vec::new();
buffer.push(title);
let first_ref: &str = &buffer[0];
log_len(first_ref);
let consume_and_return = move || {
let mut b = buffer;
let x = b.pop().expect("buffer had one element");
let x = take_and_give_back(x);
b.push(x);
b
};
let mut buffer = consume_and_return();
buffer.push(String::from("Hello from scope A"));
println!("buffer in scope A has {} items", buffer.len());
}
}
宣告點(內層 scope A 開始)
let mut buffer: Vec<String> = Vec::new();
closure 使用 move 捕獲
let consume_and_return = move || { /* 使用 buffer */ };
呼叫 closure 並接收回傳
let mut buffer = consume_and_return();
離開內層 scope A
今天就先講到這,因為不知道應該要切在哪一個部分,所以就一次講完,東西有點多哈哈.