共计 4912 个字符,预计需要花费 13 分钟才能阅读完成。
Gopher 转 Rust 辣眼睛语法排行榜
作者:中弈 – sealos 作者,sealer 发起人
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
gopher: 这是在写代码还是在发朋友圈?@运算符容许为一个字段绑定另外一个变量,这样就能在上面的代码中应用该变量了.
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 语言圣经 代码和介绍,十分好的学习材料,想零碎学习 rust 的同学可参考