Gopher 轉 Rust 辣眼睛語法排行榜
| TOP 10 經常忘記寫的分號
fn add_with_extra(x: i32, y: i32) -> i32 {
let x = x + 1; // 語句
let y = y + 5; // 語句
x + y // 表達式
}
當你是從 golang 剛轉過來,你一定經常忘記寫分號, 對於 Rust 語言而言,區分語句和表達式的方式是非常重要,而且很多時候有了表達式會很方便, 比如不用再寫 return, 或者在匹配的時候使用。
語句執行一些操作無返回值,表達式會求值後返回一個值,所以分號‘;’就很重要了。
| TOP 9 感嘆號
fn main() {
println!("hello world");
}
這是什麼鬼,爲什麼 println 後面要加個感嘆號,是叫我別打印嘛?其實這是 go 裏面沒有的宏,宏可以幹很多函數無能爲力的事,在很多情況下也非常方便。比如元編程,可變參數,爲指定的類型實現某個特徵等,而且編譯之前就做好了展開。其本質是生成 (替換) 一些代碼,讓我們少寫代碼。
| TOP 8 &str String::from(" 傻傻分不清楚 ")
怎麼整個字符串這麼麻煩。。。
let s = "hello";
s 是被硬編碼進程序的,大小固定,類型爲 & str.
let s = String::from("hello");
s.push_str(",world!");
s 大小不可知道,分配在堆上,類型爲 String.
| TOP 7 引用借用
常規的引用是一個指針類型,指向了對象存儲的內存地址。借用:獲取變量的引用。
let x = 5;
let y = &x;
這裏 y 就是 x 的引用。引用的時候變量的所有權 (一夫一妻) 不會發生轉移,引用 =(出軌)。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
| TOP 6 Attribute
#[allow(dead_code)]
fn unused_function() {}
這眼睛真是辣了,怎麼還來個腳本語言的註釋?細細一看,哦,這叫 Attribute,能幹很多事,如:
-
條件編譯代碼
-
設置 crate 名稱、版本和類型(二進制文件或庫)
-
禁用 lint (警告)
-
啓用編譯器的特性(宏、全局導入(glob import)等)
-
鏈接到一個非 Rust 語言的庫
-
標記函數作爲單元測試
-
標記函數作爲基準測試的某個部分
等...
習慣之後發現,確實簡單很多,還能少寫好的代碼。比如:
#[derive(Debug)] // 加了就可以打印結構體debug信息了,不用自己去實現Display
struct Point {
x: i32,
y: i32,
}
println!("{:?}", p);
| TOP 5 Option Result 枚舉
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
剛開始寫的新手一定覺得自己是個垃圾,怎麼去取一個返回值都玩不明白,幹嘛整這麼複雜。其實這是個非常安全的設計,Tony Hoare, null 的發明者,曾經說過 我稱之爲我十億美元的錯誤:
當時,我在使用一個面嚮對象語言設計第一個綜合性的面向引用的類型系統。我的目標是通過編譯器的自動檢查來保證所有引用的使用都應該是絕對安全的。
不過在設計過程中,我未能抵抗住誘惑,引入了空引用的概念,因爲它非常容易實現。就是因爲這個決策,引發了無數錯誤、漏洞和系統崩潰,在之後的四十多年中造成了數十億美元的苦痛和傷害。
我們寫 golang 也經常因爲訪問了 nil 對象引發錯誤,而 rust 中拋棄了這一做法。自動走到空值的分支,習慣之後是非常安全和優雅的。
let age = Some(30);
if let Some(age) = age { // if let就不會取出空值,非常舒服
println!("age{}",age);
}
| TOP 4 變量綁定 @
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("Found an id in range: {}", id_variable)
},
}
id_variable @ 3..=7
| TOP 3 self Self super 自我?本我?超我?這是編程語言還是搞哲學
self 大部分人分分鐘理解,可是又冒出個 Self,其它語言過來的瞬間就慌了。。。
其實也很簡單,Self 表示結構體本身,self 代表對象本身:
pub struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
pub fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
pub fn width(&self) -> u32 {
return self.width;
}
}
fn main() {
let rect1 = Rectangle::new(30, 50);
println!("{}", rect1.width());
}
所以這裏 Self = Rectangle
super 只是爲了配合超我
,就是訪問父模塊,和上面沒啥太大關係
mod a {
pub fn foo() {}
}
mod b {
pub fn foo() {
super::a::foo(); // 父模塊
}
}
| TOP 2 泛型
fn bench_heap_sizes<I, H, B>(c: &mut Criterion, name: &str, init: I, new_test_heap: H)
where
I: Fn(Key, &[u32]),
H: Fn(Key, Vec<u32>) -> NewHeap,
B: Benchmark,
{...}
gopher 們是不是被上面代碼辣出了白內障?但是接觸過 c++ 的可能都還能接受,I,H,B 其實就是代表一個類型,where 裏面註明你不是啥類型都可以, 必須滿足一定特徵。
泛型確實在很多時候帶來了很多方便,少寫了很多代碼,編譯器會根據泛型爲我們生成很多代碼,Rust 在泛型性能這塊也做了很多優化,在運行時就知道具體類型了,不需要動態分發,這點比渣渣 c++ 好太多 (我黑 c++ 不怕被罵)
go 裏面覺得接口可以搞定這種需求,沒引入泛型,也挺簡單的,有它的編程哲學。
| TOP 1 生命週期聲明
任何 gopher 第一眼看到這個單引號的時候眼睛一定是被辣瞎的,然後一萬隻草泥馬。。。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
x、y 和返回值至少活得和'a 一樣久 (因爲返回值要麼是 x,要麼是 y),如果你不申明,對不起編譯器讓你哭。。。所以新手在寫的時候有種和編譯器有仇的感覺,然後編譯器像你媽一樣告訴你:“我這都是爲你好!”
你以爲這就結束了?還有靜態生命週期。。。
let s: &'static str = "逼死強迫症";
| 極度舒適 TOP 3
寫了這麼多辣眼睛語法 (其實似黑實誇),擔心被 rust 粉揍,來補充幾條我覺得極度舒適的點:
| TOP 3 枚舉與匹配
Rust 的枚舉和匹配非常強,應用非常廣泛,你可能會說咱也有 switch case 啊,然後在 rust 的 enum 和 match 面前就是個弟弟.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.")
}
Message::Move { x, y } => {
println!(
"Move in the x direction {} and in the y direction {}",
x,
y
);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
}
}
}
枚舉裏面可以支持不同的類型,元組結構體等,這很有用,比如在開發一個通信模塊,接收的數據類型會有好幾個種類,就可以非常方便優雅的解決問題,舉個例子:在 sealos 用 rust 寫的前端中就有類似代碼:
#[derive(Switch,Clone)]
pub enum AppRoute {
#[to = "/images/{name}"]
ImageDetail(String),
#[to = "/images"]
Images
}
路由匹配,有的路由帶參數,有的不帶,就可以通過枚舉實現。
| TOP 2 包管理
cargo 的包管理是很舒服的,gopher 們應該經常遇到編碼十分鐘,依賴解決一整天的情況,這在 rust 裏面,不存在的。而且 go 的包管理方式變來變去好多次, 該用啥工具,該不該 vendor 等等,不過隨着 golang 版本的升級這塊比早期改善很多了。
| TOP 1 錯誤處理
寫 go 的估計都被 if err != nil 折磨瘋了,三分之二代碼是 if err != nil, 下面來感受一下沒有對比就沒有傷害:
Golang:
func read_username_from_file() (string, error) {
f,err := os.OpenFile("hello.txt",os.O_CREATE|os.O_RDWR|os.O_APPEND, os.ModeAppend|os.ModePerm)
if err != nil {
return "", error
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return "",error
}
return string(content),nil
}
這裏我們把錯誤返回讓上層處理,兩次 if err != nil, 來看看 Rust:
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? 可以透明傳輸錯誤,而且可以鏈多調用,這樣代碼就會簡潔很多。Rust錯誤處理還不止這些,以上最具有代表性,希望go v2也能讓錯誤處理更方便一些。
| 總結
以上不權威排名有非常強烈的個人色彩,大家不必太認真,主要目的想圈出一些 go 轉 rust 同學需要注意的點,兩門語言都非常優秀,黑哪一個是不存在的,gopher 和 Rust 粉都輕噴~
編程語言都有各自的優勢,以下說一下我自己學習 Rust 的一點心得:
-
說 Rust 學習曲線陡,這其實非常不利於推廣,其實並沒有多難,特別對於 c/c++ 基礎的人來說,絕對不是事兒,心理上不要有任何壓力。
-
確實和我學 go python 會有點不一樣,go python 基本是瞄一眼直接上手寫項目了,Rust 我覺得還是有必要系統性學習一下。
-
動手!動手!動手!說三遍,書中例子你看懂了,再簡單你不一定能自己寫出來,能寫出來也不一定能編譯過去,所以動手非常重要。
-
總結,把一些難點東西總結出來,寫博客什麼的,這個過程會讓你重新思考,理解更深入。
| 資料
本文引用大量 rust 語言聖經 https://course.rs/ 代碼和介紹,非常好的學習材料,想系統學習 rust 的同學可參考
sealer 中使用 Rust 寫前端代碼:https://github.com/alibaba/sealer/tree/main/pkg/cloud/dashboard
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/kVEm35LQD3zrWR9qyny6BQ