1. 入坑rust

1.1 rust发展历程

  • 2006年,Mozilla 员工 “Graydon Hoare” 开发了Rust。
  • 2015年5月15日,Rust编程语言核心团队正式宣布发布Rust 1.0版本,之后连续4年,在Stack Overflow开发者「最受喜爱编程语言」评选中获得第一名。
  • 2019年7月4日,社交网络巨头Facebook联合其他100个行业巨头,对外宣布准备建设Libra(天秤座)项目。该项目的技术特点是:构建于区块链技术之上,并且基于Rust实现。
  • 2019年7月18日,微软安全响应中心(MSRC)发文宣称:我们需要更安全的系统编程语言。
  • 在suricata5.0上,rust已经成为一个必选组件,被用来编写应用层解析代码。

需要基于suricata开发项目的我,不得不入坑rust了。

1.2 rust安装

rustup 是rust官方的版本管理工具。应当作为安装 Rust 的首选。
在linux/mac下,运行一条命令就行了:

curl https://sh.rustup.rs -sSf | sh

但是由于众所周知的防火墙问题,我们需要配置一下中科大的源。

详细参考:https://zhuanlan.zhihu.com/p/26944087

2. Rust初印象

  • rust对标c/c++,是一个编译型系统级编程语言(可以用来编写操作系统的那种)。
  • Rust借用了C和C++的语法,它不允许空指针和悬挂指针,二者是C和C++中系统崩溃、内存泄露和不安全代码的根源。
  • Rust做到了内存安全而无需.NET和Java编程语言中实现自动垃圾收集器的开销,这是通过所有权/借用机制、生命周期、以及类型系统来达到的。
  • Rust 使用实现(implementation)、特征(trait)和结构化类型(structured type)而不是类(class)。这点,与基于继承的OO语言 C++, Java 有相当大的差异。

第一印象:

fn main() {println!("Hello, world!");
}

复杂一点的猜猜看游戏

use std::io;
use std::cmp::Ordering;
use rand::Rng;fn main() {println!("Guess the number!");let secret_number = rand::thread_rng().gen_range(1, 101);loop {println!("Please input your guess.");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("Failed to read line");let guess: u32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};println!("You guessed: {}", guess);match guess.cmp(&secret_number) {Ordering::Less => println!("Too small!"),Ordering::Greater => println!("Too big!"),Ordering::Equal => {println!("You win!");break;}}}
}

对于熟悉c/c++的我,读起来还是挺通顺的。

3. rust语言基础

3.1 变量和可变性

变量默认是不可改变的(immutable),当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。

fn main() {let x = 5;println!("The value of x is: {}", x);x = 6;println!("The value of x is: {}", x);
}

比如这样是不对的,rust编译就会出错。

我们可以在变量名之前加 mut 来使其可变。

fn main() {let mut x = 5;println!("The value of x is: {}", x);x = 6;println!("The value of x is: {}", x);
}

变量还有一个“隐藏“的玩法,这样我们就可以像python那样,”改变“一个变量的类型了。(注意不是真正改变了变量类型,而是隐藏了之前的变量)

let spaces = "   ";
let spaces = spaces.len();

3.2 数据类型

Rust是静态类型(staticallytyped)语言,也就是说在编译时就必须知道所有变量的类型。
比如,当我们想要将String转换为数字时,必须增加类型注解,

let guess = "42".parse().expect("Not a number!"); // 错误
let guess: u32 = "42".parse().expect("Not a number!"); // 正确

rust的原生数据类型(primitive types)包括:

// boolean type
let t = true;
let f: bool = false;// char type
let c = 'c';// numeric types
let x = 42;
let y: u32 = 123_456;
let z: f64 = 1.23e+2;
let zero = z.abs_sub(123.4);
let bin = 0b1111_0000;
let oct = 0o7320_1546;
let hex = 0xf23a_b049;// string types
let str = "Hello, world!";
let mut string = str.to_string();// arrays
let a = [0, 1, 2, 3, 4];// tuples
let tuple: (i32, &str) = (50, "hello");// raw pointers
let x = 5;
let raw = &x as *const i32;// functions
fn plus_one(x: i32) -> i32 {x + 1
}// structure
# struct User {#     username: String,
#     email: String,
#     sign_in_count: u64,
#     active: bool,
# }
#
let mut user1 = User {email: String::from("someone@example.com"),username: String::from("someusername123"),active: true,sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");

3.3 控制流

rust的控制流和C/C++很类似。

条件:if/else if/else

fn main() {let number = 6;if number % 4 == 0 {println!("number is divisible by 4");} else if number % 3 == 0 {println!("number is divisible by 3");} else if number % 2 == 0 {println!("number is divisible by 2");} else {println!("number is not divisible by 4, 3, or 2");}
}

循环:loop、while 和 for
loop

fn main() {let mut counter = 0;let result = loop {counter += 1;if counter == 10 {break counter * 2;}};assert_eq!(result, 20);
}

while

fn main() {let mut number = 3;while number != 0 {println!("{}!", number);number = number - 1;}println!("LIFTOFF!!!");
}

for

fn main() {let a = [10, 20, 30, 40, 50];for element in a.iter() {println!("the value is: {}", element);}
}

4. 认识所有权

所有权(系统)是 Rust 最独特的功能,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。
所有权对很多程序员来说都是一个新概念,需要一些时间来适应。好消息是随着你对 Rust 和所有权系统的规则越来越有经验,你就越能自然地编写出安全和高效的代码。持之以恒!

4.1 作用域

{                      // s 在这里无效, 它尚未声明let s = "hello";   // 从此处起,s 是有效的// 使用 s
}                      // 此作用域已结束,s 不再有效

4.2 内存分配/释放

我们需要寻找一个存储在堆上的数据来探索 Rust 是如何知道该在何时清理数据的。
Rust的字符串类型,String是被分配到堆上的,能够存储在编译时未知大小的文本.

let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // 将打印 `hello, world!`

我们都知道,在c/c++中,分配到堆上的内存使用malloc/new,使用完需要释放free/delete。
Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。

{let s = String::from("hello"); // 从此处起,s 是有效的// 使用 s
}                                  // 此作用域已结束,// s 不再有效

4.3 堆变量的移动

我们来看一个堆变量移动的例子:

let s1 = String::from("hello");
let s2 = s1;

我们将 s1 赋值给 s2,String 的数据被复制了,这意味着我们从栈上拷贝了它的指针、长度和容量。我们并没有复制指针指向的堆上数据

之前我们提到过当变量离开作用域后,Rust 自动调用 drop 函数并清理变量的堆内存。这就有了一个问题:当 s2 和 s1 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。

为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存,Rust 则认为 s1 不再有效,因此 Rust 不需要在 s1 离开作用域后清理任何东西。

let s1 = String::from("hello");
let s2 = s1; // 这段代码执行之后,s1将不再能使用
println!("{}, world!", s1); // 错误

如下图所示,这种方式叫做变量“移动”。不是深拷贝也不是浅拷贝。

4.4 所有权与函数

将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。

fn main() {let s = String::from("hello");  // s 进入作用域takes_ownership(s);             // s 的值移动到函数里 ...// ... 所以到这里不再有效let x = 5;                      // x 进入作用域makes_copy(x);                  // x 应该移动函数里,// 但 i32 是 Copy 的,所以在后面可继续使用 x} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,// 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

4.5 返回值与作用域

返回值也可以转移所有权

fn main() {let s1 = gives_ownership();         // gives_ownership 将返回值// 移给 s1let s2 = String::from("hello");     // s2 进入作用域let s3 = takes_and_gives_back(s2);  // s2 被移动到// takes_and_gives_back 中, // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,// 所以什么也不会发生。s1 移出作用域并被丢弃fn gives_ownership() -> String {             // gives_ownership 将返回值移动给// 调用它的函数let some_string = String::from("hello"); // some_string 进入作用域.some_string                              // 返回 some_string 并移出给调用的函数
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域a_string  // 返回 a_string 并移出给调用的函数
}

4.6 引用

rust中的引用,类似c++中的应用,用&表示。

我们看一个例子

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()
}

&s1 语法让我们创建一个 指向 值 s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域时其指向的值也不会被丢弃。

fn calculate_length(s: &String) -> usize { // s 是对 String 的引用s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,// 所以什么也不会发生

当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。

我们将获取引用作为函数参数称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。

如果我们尝试修改借用的变量呢?剧透:这行不通!

fn main() {let s = String::from("hello");change(&s);
}fn change(some_string: &String) {  // & 表示引用(不可改变)some_string.push_str(", world");  // 错误
}

我们通过一个小调整就能修复代码中的错误:

fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) { // &mut 就是可变引用啦some_string.push_str(", world"); // 正确
}

4.7 slice 部分引用

字符串 slice(string slice)是 String 中一部分值的引用,它看起来像这样:

let s = String::from("hello world");let hello = &s[0..5];
let world = &s[6..11];

5. 项目管理–cargo

cargo是rust的项目管理器,用过node.js的猿们,应该对node.js中的神器npm、grunt、gulp等工具印象深刻。作为新一代静态语言中的翘楚,rust官方参考了现有语言管理工具的优点,于是就产生了cargo。

言而总之,作为rust的代码组织管理工具,cargo提供了一系列的工具,从项目的建立、构建到测试、运行直至部署,为rust项目的管理提供尽可能完整的手段。

5.1 包(package)

当运行 cargo new 时是在创建一个包(package)

$ cargo new hello_world --bin$ cd hello_world
$ tree .
.
├── Cargo.toml
└── src└── main.rs1 directory, 2 files

项目编译

$ cargo build

项目运行

$ cargo runRunning `target/debug/hello_world`
Hello, world!

5.2 crate

Cargo 的约定是如果在代表包的 Cargo.toml 的同级目录下包含 src 目录且其中包含 main.rs 文件的话,Cargo 就知道这个包带有一个与包同名的二进制 crate,且 src/main.rs 就是 crate 根。

如果包(package)同时包含 src/main.rs 和 src/lib.rs,那么它带有两个 crate:一个库和一个二进制项目

5.3 模块(module)

随着工程的增大,把所有代码写在一个文件里面,是一件极其初等及愚蠢的作法。模块允许我们将代码组织起来,模块是几乎所有语言的基础设施,尽管叫法各有不同。

Rust 提供了一个关键字 mod,它可以在一个文件中定义一个模块,或者引用另外一个文件中的模块。

通常,我们会在单独的文件中写模块内容,然后使用 mod 关键字来加载那个文件作为我们的模块。

比如,我们在 src 下新建了文件 aaa.rs。现在目录结构是下面这样子:

foo
├── Cargo.toml
└── src└── aaa.rs└── main.rs

我们在 aaa.rs 中,写上:

pub fn print_aaa() {println!("{}", 25);
}

在 main.rs 中,写上:

mod aaa;use aaa::print_aaa;fn main () {print_aaa();
}

细心的朋友会发现,aaa.rs 中,没有使用 mod xxx {} 这样包裹起来,是因为 mod xxx; 相当于把 xxx.rs 文件用 mod xxx {} 包裹起来了。初学者往往会多加一层,请注意。

rust也支持层级的模块,这点和python很像。
详细参考:https://wiki.jikexueyuan.com/project/rust-primer/module/module.html

6. rust集合

就像C++的stl一样,Rust提供了一系列的基础且通用的容器类型。善用这些集合类型,可以让Rust编程更加方便轻松。

6.1 动态数组Vec

  • 和我们之前接触到的Array不同,Vec具有动态的添加和删除元素的能力,并且能够以O(1)的效率进行随机访问。
  • 同时,对其尾部进行push或者pop操作的效率也是平摊O(1)的。
  • 同时,Vec的所有内容项都是生成在堆空间上的,也就是说,你可以轻易的将Vec move出一个栈而不用担心内存拷贝影响执行效率——毕竟只是拷贝的栈上的指针。

来段示例代码:

let mut v = Vec::new();v.push(5);
v.push(6);
v.push(7);
v.push(8);for i in &v {println!("{}", i);
}

用过c++的朋友,是不是很熟悉

6.2 哈希 map

和动态数组Vec一样,哈希表(HashMap)也是Rust内置的集合类型之一,同属std::collections模块下。
它提供了一个平均复杂度为O(1)的查询方法,是实现快速搜索必备的类型之一。

来段代码:

use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);let team_name = String::from("Blue");
let score = scores.get(&team_name);

7. 错误处理

错误处理是保证程序健壮性的前提,在编程语言中错误处理的方式大致分为两种:抛出异常(exceptions)和作为值返回。

Rust 将错误作为值返回并且提供了原生的优雅的错误处理方案。

7.1 Result

熟练使用Result是编写 Rust 代码的关键,Rust 优雅的错误处理离不开值返回的错误形式,编写代码时提供给使用者详细的错误信息是值得推崇的。

enum Result<T, E> {Ok(T),Err(E),
}

T 和 E 是泛型类型参数;T 代表成功时返回的 Ok 成员中的数据的类型,而 E 代表失败时返回的 Err 成员中的错误的类型。
因为 Result 有这些泛型类型参数,我们可以将 Result 类型和标准库中为其定义的函数用于很多不同的场景,这些情况中需要返回的成功值和失败值可能会各不相同。

来一个打开文件出错的代码示例

use std::fs::File;fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file,Err(error) => {panic!("There was a problem opening the file: {:?}", error)},};
}

这里我们告诉 Rust 当结果是 Ok 时,返回 Ok 成员中的 file 值,然后将这个文件句柄赋值给变量 f。match 之后,我们可以利用这个文件句柄来进行读写。

match 的另一个分支处理从 File::open 得到 Err 值的情况。在这种情况下,我们选择调用 panic! 宏。如果当前目录没有一个叫做 hello.txt 的文件,当运行这段代码时会看到如下来自 panic! 宏的输出:

thread 'main' panicked at 'There was a problem opening the file: Error { repr:
Os { code: 2, message: "No such file or directory" } }', src/main.rs:9:12

来个更复杂的例子:

use std::fs::File;
use std::io::ErrorKind;fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file,Err(error) => match error.kind() {ErrorKind::NotFound => match File::create("hello.txt") {Ok(fc) => fc,Err(e) => panic!("Tried to create file but there was a problem: {:?}", e),},other_error => panic!("There was a problem opening the file: {:?}", other_error),},};
}

7.2 panic

Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。
panic! 宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。
Rust 类型系统的 Result 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 Result 来告诉代码调用者他需要处理潜在的成功或失败。

在适当的场景使用 panic! 和 Result 将会使你的代码在面对不可避免的错误时显得更加可靠。

8. 模式匹配

模式匹配,多出现在函数式编程语言之中,为其复杂的类型系统提供一个简单轻松的解构能力。
rust的模式匹配依靠match和pattern两个关键字。

8.1 match

先来段match代码

enum Direction {East,West,North,South,
}fn main() {let dire = Direction::South;match dire {Direction::East => println!("East"),Direction::North | Direction::South => {println!("South or North");},_ => println!("West"),};
}

有点类似c语言的switch对不对
rust的match,比c语言的switch要强大得多,比如match可以对现有的数据结构进行解构,轻易的可以拿出其中的数据部分来:

enum Action {Say(String),MoveTo(i32, i32),ChangeColorRGB(u16, u16, u16),
}fn main() {let action = Action::Say("Hello Rust".to_string());match action {Action::Say(s) => {println!("{}", s);},Action::MoveTo(x, y) => {println!("point from (0, 0) move to ({}, {})", x, y);},Action::ChangeColorRGB(r, g, _) => {println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",r, g,);}}
}

8.2 pattern

写rust代码,match和pattern更配哦
pattern对现有的数据结构进行解构,轻易的可以拿出其中的数据部分来

struct Point {x: i64,y: i64,
}
let point = Point { x: 0, y: 0 };
match point {Point { x, y } => println!("({},{})", x, y),
}

模式匹配中,当我想要匹配一个数字(字符)范围的时候,我们可以用…来表示:

let x = 1;match x {1 ... 10 => println!("一到十"),_ => println!("其它"),
}let c = 'w';match c {'a' ... 'z' => println!("小写字母"),'A' ... 'Z' => println!("大写字母"),_ => println!("其他字符"),
}

当我们只是单纯的想要匹配多种情况的时候,可以使用 | 来分隔多个匹配条件

let x = 1;match x {1 | 2 => println!("一或二"),_ => println!("其他"),
}

进一步学习Rust

欢迎加入我的知识星球,我会提供一些学习资料(书籍/视频)以及解答一些问题。
大家一起学习Rust

五分钟入门rust语言相关推荐

  1. STM32F103五分钟入门系列(十三)独立看门狗IWDG

    参考:STM32F103五分钟入门系列(十三)独立看门狗IWDG 作者:自信且爱笑' 发布时间:2021-07-31 19:50:28 网址:https://blog.csdn.net/Curnane ...

  2. STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置

    摘自:STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置 作者:自信且爱笑' 发布时间: 2021-05-01 12:08:32 网址:https://blog ...

  3. STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

    摘自:STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结 作者:自信且爱笑' 发布时间: 2021-04-28 21:17:40 网址:https://blog. ...

  4. Maven五分钟入门

    Maven 五分钟入门 ---本文翻译自Maven官网的Maven in 5 Minutes,稍有删改,所有版权归maven所有.本文只作学习交流之用. 安装 Maven 是一个java工具,因此,在 ...

  5. STM32F103五分钟入门系列(八)SysTick滴答定时器+SysTick中断实现跑马灯

    学习板:STM32F103ZET6 往期博客: STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结 STM32F103五分钟入门系列(二)GPIO的七大寄存器+G ...

  6. STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)

    学习板:STM32F103ZET6 往期博客: STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结 STM32F103五分钟入门系列(二)GPIO的七大寄存器+G ...

  7. C语言指针 五分钟入门!你要是不理解,我就当场把这个编译器.........

    话不多说,先上代码. #include <stdio.h>void test(int *p) {*p = 666666; }int main() {int* p1 = NULL;int p ...

  8. 教你五分钟入门使用html5 svg绘制图形

    跟着步骤学习,耐心点好么,不要只看图不看文字,只看图5天都学不会. 我在工作中用不到h5绘图,所以我是个小菜鸟.但我始终有一种迷之信念,任何已经存在的东西学一下就会了. 所以刚好,我顺便可以讲一下我的 ...

  9. Origami五分钟入门秘籍

    早在Facebook推出Facebook Home时,相信强大的原型工具Quartz Composer就进入了很多设计师的法眼,无须编写代码就可以输出细腻丰富的动画,这个太刁了,和大家一样,彼时我也信 ...

最新文章

  1. 低质量的勤奋,比懒惰更可怕
  2. python break -else 语句
  3. 基础知识:IDE集成开发环境(pycharm)、基本数据类型、用户的交互、运算符
  4. python多行语句用反斜杠_抗联部队规定新战士每年必须学会多少个生字
  5. 欧拉函数 - HDU1286
  6. dblink 同步数据_使用DBLINK同步TC数据库
  7. [转] 背完这444句英语,你的口语绝对不成问题
  8. win7 mysql 启动 问题
  9. 前端 - 实习一个月总结
  10. ffmpeg批量转换ts为mp4
  11. 可视化:架构师必看 京东咚咚架构演进
  12. 外星人入侵游戏python学习心得——创建第一个外星人在左上角
  13. 【学习笔记】传说中的马尔可夫决策过程(MDP)和贝尔曼方程(Bellman Equation)
  14. 主键和候选键有什么区别
  15. 如何给未来的自己写一封信(邮件) -- 方法
  16. 只有两个键的键盘,只会复制粘贴
  17. 校准不好TOF相机就废了?
  18. 如何看懂Minecraft报错的关键信息。
  19. 计算机教师结构化方式面试,市计算机:17名学生通过全国教师资格证结构化面试...
  20. java 15k,广州-Java高级-15k-22k(月薪)

热门文章

  1. 查询优化(TTFB过长)left join索引未生效
  2. C语言正交表测试用例,测试用例设计—正交试验法
  3. 从零开始学badusb(1)--初识badusb
  4. 有感~半导体技术之晶圆尺寸,450mm是否会来到
  5. python赴日本_关于赴日IT-老司机纯干货详解
  6. 电商网站Web自动化测试实战( 编写京东搜索脚本python+selenium框架)
  7. Speech模块管理语音输入功能,提供语音识别功能,可支持用户通过麦克风设备进行语音输入内容。通过plus.speech可获取语音输入管理对象
  8. 计算机使用hdmi需要设置方法,win10系统使用hdmi连接电视的操作方法
  9. 正在报名:2022年5G消息开发者马拉松
  10. 学系统集成项目管理工程师(中项)系列13b_人力资源管理(下)