学习Rust已经两周了,基本上是断断续续的在学,或者是在上下班坐公交时,或者是在ODC没事做时。现在已经学习了Rust程序设计语言的前5章,是时候做一个总结了。关于数据类型或者if else这种内容我就不在罗列了,我只说一说我感兴趣的部分。
- 变量和可变性(variable & mutability)
- 函数的隐式返回值
- 理解语句(statement)和表达式(expression)的区别
- break可以带变量
- 元组
- 所有权(ownership)
其中所有权我会重点说一下。
先看看Rust官网的slogan,slogan往往是可以有一个更加整体的理解。
我抽取下Key words,1. 每个人 2. 可靠且高效。“每个人”说明这门语言不会像面向machine的汇编或者C,应该具备现代语言的一些特性,泛型,反射,完善的函数库,好用的集合等等。可靠且高效,“可靠”就是说写出来的程序的漏洞会少,不会像C那样不检查下标越界,也不会产生一些常见的内存错误(doule free或者悬挂指针之类的)。当说到“高效”我想到了C++,大家都说C++的编译时间长且开发很慢(c++我没什么发言权,说的不对,诸君还请谅解),既然是高效,我想就不能仅仅是运行时间快了吧,开发时间也应该快。下面我们探讨一下我上面说的几点。
1. 变量和可变性
rust中变量默认是不可变的,也就是说下面的类似下面的赋值操作是非法的。
fn main() { let a = 100; a = 101; }
会出现这样的错误,“不能给一个不可变的变量赋值两次。”
如果你需要一个“会变”的变量,那你需要用 mut 关键字去显式(explicitly)的声明一个变量。这么设计的原因,为什么我们需要显示的去声明一个变量为可变的?我也不知道,我猜测跟所有权有关?
fn main() { let mut a = 100; a = 101; }
2. 函数的隐式返回值
如果函数不适用return语句进行返回的话,函数隐式的返回最后一个表达式。当然这种写法是可以选装的,你可以选择传统的return版的返回语句。
fn add_one(num: i32)->i32 { num+1 } fn main() { let mut a = 100; a = 101; println!("{}", add_one(a)); }
3. 理解语句(statement)和表达式(expression)的区别
rust对于语句和表达式的区分比较严格,语句就是执行一些操作但并不产生结果的指令,表达式计算并产生一些值。
let mut num = 100; //一个赋值语句 num = num + 1; //语句 num + 1 //表达式
函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),{}
,也是一个表达式。
let y = { let x = 3; x+1 };
4. break可以带变量
就下边那样,loop是rust中的循环,类似while(1)。
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter*2; } }; println!("The Result is {}", result); }
5. 元组
元组(tuple)类型是一个复合类型,它可以包含多种不同类型。这种类型的形式是 " (type1, type2, type3 ... ... ) "。
let tuple1 = (1, "hello", 3.14); let tuple2: (i32, string, f32) = (1, "hello", 3.14);
那么怎么去获取元组的元素呢。可以通过元组的解构(destructuring),通过解构元组我们可以把元组内的值赋给其他的变量。
let (x, y, z) = tuple1;
也可以使用点号(".")去直接访问元组内的元素
let x = tuple1.0; let y = tuple1.1; let z = tuple1.2;
6. 所有权(ownership)
所有权是rust一个特点之一,也是rust内存管理的核心。每个值(可以理解是内存的一小块)都有一个所有者,这个所有者可以是变量。同时程序超出所有者的作用域时,所有者所有的值也会通过调用drop()函数,从而被释放。(注意drop不是通过运行时来调用的,而是在编译期就被插入了drop函数),这种做法类似于C++中的RAII机制。值得注意的是,所有权是可以被转移的,并且所有者在转移了值的所有权之后,他就会被rust认为是无效的所有者,再次使用该变量(所有者)会产生错误。
fn foo(str_cp:String) { println!("{}", str_cp); } fn main() { let str1 = String::from("hello world"); foo(str1); println!("{}", str1); }
在上边的例子中,我们在堆上创建了一个字符串"hello world",该字符串的所有者是变量str1,值得注意的是,将变量作为函数的形参也会转移值的所有权,因此我们当7行的函数调用返回时,str1已经是一个无效的所有者,可怜的str1,白白丢了自己的地盘,但是他也不是什么都没有收获的,这种所有权的转移,也是有好处的。
这里我生搬硬套了一种情况,尝试说明这种所有权转移的机制是如何防止double free的情况产生的。下面是一段和上述代码段类似的代码段,可以看到这里对同一内存块进行了两次free。那么rust是如何防止这种情况产生的呢?实际上在rust中17行的情况根本就不会出现,因为str1在此时已经是无效的了,没有free它的可能性。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void foo(char *str) { printf("%s\n", str); free(str); } int main(void) { while (1) { char *str1 = (char *)malloc(512); strncpy(str1, "hello world", 512); foo(str1); free(str1); //double free printf("%s\n", str); } return 0; }
就这样,本文只是我对前几天的学习进行的一个简单的总结。很多重要的东西都没有覆盖,等我的rust用的更熟练,我会尝试出一个Rust教程。Peace。
参考
[1] https://kaisery.github.io/trpl-zh-cn/ch04-02-references-and-borrowing.html
[2] https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
原文地址:https://www.cnblogs.com/dennis-wong/p/12081543.html