github地址:https://github.com/bradyjoestar/rustnotes(欢迎star!)
pdf下载链接:https://github.com/bradyjoestar/rustnotes/blob/master/Rust%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.pdf
参考:
https://rustcc.gitbooks.io/rustprimer/content/ 《RustPrimer》
https://kaisery.github.io/trpl-zh-cn/ 《Rust程序设计语言-简体中文版》
2.1 前置知识
2.1.1 表达式和语句
其它语言中:
表达式是用来表达某含义的。可以包括定义某值,或判断某物,最终会有一个“值”的体现,“Anything that has a value"。 比如说 var a=b就是表达式,是把b的值赋给a,或者 if (a == b)其中if()内的也是表达式。 而表达式语句就是程序识别的一条执行表达式的语句。 例如 var a=b; 这条是赋值语句,这里微小的差别就是加上了分号;作为语句结束符。 其实表达式简单的可以理解成某语言的语法,而由这些语法构成的一条执行语句则是表达式语句。最直观的根据是否有分号来判断。
但是在rust语言中有点不同,
“let a = 5”是表达式语句,即使没有分号。
let y = (let a = 4);会返回下面的错误:
variable declaration using let
is a statement。
2.1.2 rust doc
rust提供了从注释到可浏览网页文档的生成方法,通过cargo doc的方式。
注释主要有三种:行注释,模块注释,文档注释。
Rust 也有特定的用于文档的注释类型,通常被称为 文档注释(documentation comments),他们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 使用 这个 crate,而不是它是如何被 实现 的。
文档注释使用三斜杠 /// 而不是两斜杆并支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。
模块注释使用 //! ,行注释使用 //
模块注释和文档注释用起来非常舒服的,远比/* */舒服,IDEA对它支持很好,提供自动换行。
2.2 条件表达式
在rust中的条件表达式没有switch语句。
2.2.1 if表达式
相对于 C 系语言,Rust 的 if 表达式的显著特点是:
1.判断条件不用小括号括起来。
2.它是表达式,而不是语句。
即使花括号里面有再多的表达式语句,它最后仍然需要返回一个值,是表达式。下面的写法是正确的:
let x = 5;
let y = if x == 5 {
println!("hello test”)
10
} else {
15
}; // y: i32
需要说明的是if中条件判断必须是bool类型,不能写出if 5 这种判断条件。
2.2.2 match语句
Rust 中没有类似于 C 的 switch 关键字,但它有用于模式匹配的 match,能实现同样的功能,并且强大太多。
match 的使用非常简单,举例如下:
let x = 5;
match x {
1 => {
println!("one")
},
2 => println!("two"),
3 => println!("three"),
4 => println!("four"),
5 => println!("five"),
_ => println!("something else"),
}
2.2.3 if let表达式
let y = 5;
if let y = 5 {
println!("{}", y); // 这里输出为:5
}
if let实际上是一个 match 的简化用法。设计这个特性的目的是,在条件判断的时候,直接做一次模式匹配,方便代码书写,使代码更紧凑。
2.3 循环表达式
2.3.1 for循环
Rust的for循环实际上和C语言的循环语句是不同的。这是为什么呢?因为,for循环不过是Rust编译器提供的语法糖!在rust中,for 语句用于遍历一个迭代器。
Rust 迭代器返回一系列的元素,每个元素是循环中的一次重复。然后它的值与 var 绑定,它在循环体中有效。每当循环体执行完后,我们从迭代器中取出下一个值,然后我们再重复一遍。当迭代器中不再有值时,for 循环结束。
for x in 0..10 {
println!("{}", x); // x: i32
}
没有c中的这种用法:
// C 语言的 for 循环例子
for (x = 0; x < 10; x++) {
printf( "%d\n", x );
}
设计目的:
1.简化边界条件的确定,减少出错;
2.减少运行时边界检查,提高性能。
当你需要记录你已经循环了多少次了的时候,你可以使用 .enumerate() 函数。比如:
for (i,j) in (5..10).enumerate() {
println!("i = {} and j = {}", i, j);
}
2.3.2 while循环
Rust 提供了 while 语句,条件表达式为真时,执行语句体。当你不确定应该循环多少次时可选择 while。
let mut x = 5; // mut x: i32
let mut done = false; // mut done: bool
while !done {
x += x - 3;
println!("{}", x);
if x % 5 == 0 {
done = true;
}
}
while条件判断的类型必须是bool类型。
2.3.3 loop
loop专用于无限循环,好处是对编译和运行进行了优化,跳过了表达式检查。
2.3.4 break和continue
Rust 也提供了 break 和 continue 两个关键字用来控制循环的流程。
1.break 用来跳出当前层的循环;
2.continue 用来执行当前层的下一次迭代
break和continue可以大大简化代码,不论在哪一门语言中。
for x in 0..10 {
if x % 2 == 0 { continue; }
println!("{}", x);
}
和
let mut x = 5;
loop {
x += x - 3;
println!("{}", x);
if x % 5 == 0 { break; }
}
2.3.5 label
你也许会遇到这样的情形,当你有嵌套的循环而希望指定你的哪一个 break 或 continue 该起作用。就像大多数语言,默认 break 或 continue 将会作用于当前层的循环。当你想要一个 break 或 continue 作用于一个外层循环,你可以使用标签来指定你的 break 或 continue 语句作用的循环。
如下代码只会在 x 和 y 都为奇数时打印他们:
'outer: for x in 0..10 {
'inner: for y in 0..10 {
if x % 2 == 0 { continue 'outer; } // continues the loop over x
if y % 2 == 0 { continue 'inner; } // continues the loop over y
println!("x: {}, y: {}", x, y);
}
}
2.4 Rust类型系统
2.4.1 可变性
rust 在声明变量时,在变量前面加入 mut 关键字,变量就会成为可变绑定的变量。
通过可变绑定可以直接通过标识符修改内存中的变量的值。
在绑定后仍然可以重新修改绑定类型。
例子:
fn main() {
let mut a: f64 = 1.0;
let b = 2.0f32;
//改变 a 的绑定
a = 2.0;
println!("{:?}", a);
//重新绑定为不可变
let a = a;
//不能赋值
//a = 3.0;
//类型不匹配
//assert_eq!(a, b);
}
2.4.2 原生类型
在所有rust的类型中,比较复杂的是字符串类型。当然不仅仅在rust中,包括golang等其它语言中,字符串类型和字符类型都是值得推敲的地方。
在本部分内容中,单独拿出字符类型和字符串类型放到最后进行讨论。
字符类型和字符串类型最好和其它语言对比讨论,例如go。
2.4.2.1 bool类型
Rust自带了bool
类型,其可能值为true
或者false
let is_she_love_me = false;
let mut is_he_love_me: bool = true;
2.4.2.2数字类型
和其他类C系的语言不一样,Rust用一种符号+位数的方式来表示其基本的数字类型。
可用的符号有 i、f、u
可用的位数,都是2的n次幂,分别为8、16、32、64及size。
因为浮点类型最少只能用32位来表示,因此只能有f32和f64来表示。
2.4.2.3 自适应类型
isize和usize取决于你的操作系统的位数。简单粗暴一点比如64位电脑上就是64位,32位电脑上就是32位。
但是需要注意的是,不能因为电脑是64位的,而强行将它等同于64,也就是说isize != i64,任何情况下你都需要强制转换。
减少使用isize和usize,因为它会降低代码可移植性。
2.4.2.4 数组 array
Rust的数组是被表示为[T;N]。其中N表示数组大小,并且这个大小一定是个编译时就能获得的整数值,T表示泛型类型,即任意类型。我们可以这么来声明和使用一个数组:
let a = [8, 9, 10];
let b: [u8;3] = [8, 6, 5];
print!("{}", a[0]);
和Golang一样,Rust的数组中的N(大小)也是类型的一部分,即[u8; 3] != [u8; 4]。
Rust大小是固定的。
2.4.2.5 slice
Slice从直观上讲,是对一个Array的切片,通过Slice,你能获取到一个Array的部分或者全部的访问权限。和Array不同,Slice是可以动态的,但是呢,其范围是不能超过Array的大小,这点和Golang是不一样的。Golang slice可以超出Array的大小是存在一些问题的。
一个Slice的表达式可以为如下: &[T] 或者 &mut [T]。
这里&符号是一个难点,我们不妨放开这个符号,简单的把它看成是Slice的规定。另外,同样的,Slice也是可以通过下标的方式访问其元素,下标也是从0开始。 可以这么声明并使用一个Slice:
let arr = [1, 2, 3, 4, 5, 6];
let slice_complete = &arr[..]; // 获取全部元素
let slice_middle = &arr[1..4]; // 获取中间元素,最后取得的Slice为 [2, 3, 4] 。切片遵循左闭右开原则。
let slice_right = &arr[1..]; // 最后获得的元素为[2, 3, 4, 5, 6],长度为5。
let slice_left = &arr[..3]; // 最后获得的元素为[1, 2, 3],长度为3。
2.4.2.6 动态Vec
在Rust里,Vec被表示为 Vec<T>, 其中T是一个泛型。
let mut v1: Vec<i32> = vec![1, 2, 3]; // 通过vec!宏来声明
let v2 = vec![0; 10]; // 声明一个初始长度为10的值全为0的动态数组
println!("{}", v1[0]); // 通过下标来访问数组元素
for i in &v1 {
print!("{}", i); // &Vec<i32> 可以通过 Deref 转换成 &[i32]
}
println!("");
for i in &mut v1 {
*i = *i+1;
print!("{}", i); // 可变访问
}
2.4.2.7 函数类型
相似于golang,在rust中函数也是一种类型,例子如下:
fn foo(x: i32) -> i32 { x+1 }
let x: fn(i32) -> i32 = foo;
assert_eq!(11, x(10));
2.4.2.8 枚举类型
struct Student{
name: String,
}
enum Message{
School(String),
Location{x:i32,y:i32},
ChangeColor(i32, i32, i32),
Name(Student),
ExitColor,
}
fn main(){
let m = Message::ExitColor;
match m {
Message::ExitColor=>println!("{}","exited color"),
Message::ChangeColor(x,y,z) => println!("{}",x),
Message::Name(s) => println!("{}",s.name),
Message::School(s) => println!("{}",s),
Message::Location {x,y} => println!("{}",x),
}
}
与结构体一样,枚举中的元素默认不能使用关系运算符进行比较 (如==, !=, >=), 也不支持像+和*这样的双目运算符,需要自己实现,或者使用match进行匹配。
枚举默认也是私有的,如果使用pub使其变为公有,则它的元素也都是默认公有的。 这一点是与结构体不同的:即使结构体是公有的,它的域仍然是默认私有的。
rust枚举与其他语言的枚举不同的是在指定枚举元素时定义它元素是由什么组成的。
一个result和match与enum的综合例子:
fn main() {
println!("Hello, world!");
match returnResult() {
Ok(T) => println!("{}", T),
Err(RowError::error_1(T)) => println!("{}", T),
Err(RowError::error_2(x, y)) => println!("x={},y={}", x, y),
Err(RowError::error_3 { x, y }) => println!("x={},y={}", x, y),
}
}
enum RowError {
error_1(String),
error_2(i32, i32),
error_3 { x: i32, y: i32 },
}
fn returnResult() -> Result<String, RowError> {
let a = 90;
if a == 30 {
Ok(String::from("test"))
} else if a == 20 {
Err(RowError::error_1(String::from("error is coming")))
} else if a == 50 {
Err(RowError::error_2(5, 20))
} else {
Err(RowError::error_3 { x: 5, y: 30 })
}
}
2.4.2.9 字符串类型
如果要说rust的字符串类型,就不得不先提go的字符串类型。
2.4.2.9.1 Golang中的字符串类型
Go没有专门的字符类型,存储字符直接使用byte来存储,字符串就是一串固定长度的字符连接起来字符序列。与其他编程语言不同之处在于,Go的字符串是字节组成,而其他的编程语言是字符组成。
Go的字符串可以把它当成字节数组来用,并且是不可变的。
Rust的字符串底层也是Vec<u8>动态数组,这点和Go的字符串有点类似,不同的是Go的字符串是定长的,无法修改的。
Rust和Go原声在字符串里面支持unicode,这就导致了很大某方面的不同。
Go中字符串的例子:
package main
import (
"fmt"
"reflect"
)
func main() {
fmt.Println("test")
{
fmt.Println("example 1")
a := "testb"
fmt.Println(a[0]) //output 116
fmt.Println(len(a))
b := "testa你好"
fmt.Println(len(b))
c := b[0:8] // c is string type,alouth is slice of string
fmt.Println("c type is ",reflect.TypeOf(c))
fmt.Println(c)
if c == "testa你" {
fmt.Println("yes")
} else {
fmt.Println("no")
}
}
{
fmt.Println("example 2")
var str []byte
str = []byte("waht")
fmt.Println(string(str))
for i := 0; i < 10; i++ {
str = append(str, byte(i+100))
}
fmt.Println(string(str))
fmt.Println(str)
}
{
fmt.Println("example 3")
str1 := "नमस्ते";
fmt.Println(len(str1))
}
{
fmt.Println("example 4")
str2 := "hello world";
str2slice := str2[0:5]
str2slice = str2slice+" hello"
fmt.Println(str2slice)
fmt.Println(str2)
}
{
a := []int{1, 2, 3, 4}
fmt.Println(len(a))
b := a[0:1]
b = append(b, 5)
fmt.Println(len(b))
for _, value := range a {
fmt.Println(value)
}
str1 := "hello world"
fmt.Println(str1)
str1slice := str1[0:8]
fmt.Println(str1slice)
//compile error
//# command-line-arguments
//./main.go:78:21: cannot assign to ([]byte)(str1slice)
//[]byte(str1slice) = append([]byte(str1slice),byte('a'))
//fmt.Println(str1slice)
}
}
输出结果:
example 1
116
5
11
c type is string
testa你
yes
example 2
waht
wahtdefghijklm
[119 97 104 116 100 101 102 103 104 105 106 107 108 109]
example 3
18
example 4
hello hello
hello world
从上面的例子中,可以看出存储unicode字符最大的特点是不同的字符串所占的字节数不同。例如梵文占6个字节,汉字占3个字节。
另外golang中的string slice不能进行append操作,可以把它当成一个固定的string类型来使用,例如可以比较二者包含的字符串是否相等。
另外String类型底层是byte数组,但是底层的byte数组没有暴露出来,所以无法修改长度。
2.4.2.9.2 Rust中的字符串类型
常用rust字符串类型为&str和String,前者是字符串的引用,后者是基于堆创建的,可增长的字符串。
2.4.2.9.2.1 &str
let s ="hello world";那s的类型就是&str,右边称为字符串字面量literal,程序编译成二进制文件后,这个字符串会被保存在文件内部,所以s是特定位置字符串的引用,这就是为什么s是&str类型。
str生命周期是static,但是引用是有生命周期限制的。
可以在字符串字面量前加上r来避免转义
//没有转义序列
let d: &'static str = r"abc \n abc";
//等价于
let c: &'static str = "abc \\n abc";
2.4.2.9.2.2 String
这时候,一种在堆上声明的字符串String被设计了出来。 它能动态的去增长或者缩减,那么怎么声明它呢?
let x:&'static str = "hello";
let mut y:String = x.to_string();
println!("{}", y);
y.push_str(", world");
println!("{}", y);
那么如何将一个String重新变成&str呢?用 &* 符号
fn use_str(s: &str) {
println!("I am: {}", s);
}
fn main() {
let s = "Hello".to_string();
use_str(&*s);
}
&是两个符号&和的组合,按照Rust的运算顺序,先对String进行Deref,也就是操作。
由于String实现了 impl Deref<Target=str> for String,这相当于一个运算符重载,所以你就能通过获得一个str类型。但是我们知道,单独的str是不能在Rust里直接存在的,因此,我们需要先给他进行&操作取得&str这个结果。
涵盖大部分&str和String的例子。
fn main() {
println!("Hello, world!");
{
println!("example 1");
let mut str1 = String::from("你好 hello");
str1.push_str(" str1");
let mut str2 = &mut str1;
str2.push_str(" test ");
str2.push('a');
println!("{:?}", str1);
let mut str3 = &mut str1;
str3.push_str(" test3 ");
str3.push('3');
println!("{:?}", str1);
// compile error
// error[E0502]: cannot borrow `str1` as immutable because it is also borrowed as mutable
// --> src/main.rs:14:25
// |
//11 | let mut str3 = &mut str1;
// | --------- mutable borrow occurs here
//...
//14 | println!("{:?}",str1);
// | ^^^^ immutable borrow occurs here
//...
//17 | println!("{:?}",str3);
// | ---- mutable borrow later used here
// println!("{:?}",str3);
}
{
println!("example 2");
let mut v1 = vec![1, 2, 3, 4];
let mut v2 = &mut v1;
v2.push(100);
println!("{:?}", v2);
let mut v3 = &mut v1;
v3.push(20);
//compile error
println!("{:?}", v1);
//error[E0502]: cannot borrow `v1` as immutable because it is also borrowed as mutable
// --> src/main.rs:41:25
// |
//37 | let mut v3 = &mut v1;
// | ------- mutable borrow occurs here
//...
//41 | println!("{:?}",v1);
// | ^^ immutable borrow occurs here
//42 | println!("{:?}",v3);
// | -- mutable borrow later used here
// println!("{:?}",v3);
}
{
println!("example 3");
let mut str1 = String::from("hello");
//error[E0277]: the type `std::string::String` cannot be indexed by `{integer}`
// --> src/main.rs:59:23
// |
//59 | let answer = &str1[0];
// | ^^^^^^^ `std::string::String` cannot be indexed by `{integer}`
// |
// = help: the trait `std::ops::Index<{integer}>` is not implemented for `std::string::String`
// compile error
//let answer = &str1[0];
}
{
println!("example 4");
//how to loop the string
for c in "नमस्ते".chars() {
println!("{}", c);
}
}
{
println!("example 5");
let mut a = String::from("testa你好");
let mut b = &a[0..4];
println!("{}", b);
//This will panic
//thread 'main' panicked at 'byte index 6 is not a char boundary; it is inside '你' (bytes 5..8) of `testa你好`', src/libcore/str/mod.rs:2027:5
//let mut c = &a[0..6];
//println!("{}",c);
}
{
println!("example 6");
let mut a = String::from("testa你好");
println!("the length of a is {}", a.len());
}
{
println!("example 7");
let mut a = String::from("test");
let mut b = &mut a;
b.push_str(" str");
println!("{}", b);
println!("{}", a);
//compile error
//error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
// --> src/main.rs:102:23
// |
//99 | let mut b = &mut a;
// | ------ mutable borrow occurs here
//...
//102 | println!("{}",a);
// | ^ immutable borrow occurs here
//...
//106 | println!("{}",b);
// | - mutable borrow later used here
//
//error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
// --> src/main.rs:105:23
// |
//99 | let mut b = &mut a;
// | ------ mutable borrow occurs here
//...
//105 | println!("{}",a);
// | ^ immutable borrow occurs here
//106 | println!("{}",b);
// | - mutable borrow later used here
// println!("{}",a);
// println!("{}",b);
}
{
println!("example 8");
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
// write bug
// error[E0369]: binary operation `+` cannot be applied to type `&std::string::String`
//let s3 = &s1 + &s2; // 注意 s1 被移动了,不能继续使用
println!("{}", s3);
// error[E0382]: borrow of moved value: `s1`
// --> src/main.rs:141:23
// |
//135 | let s1 = String::from("Hello, ");
// | -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
//136 | let s2 = String::from("world!");
//137 | let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
// | -- value moved here
//...
//141 | println!("{}",s1);
// | ^^ value borrowed here after move
// compile error
println!("{}", s2);
}
}
输出结果:
example 1
"你好 hello str1 test a"
"你好 hello str1 test a test3 3"
example 2
[1, 2, 3, 4, 100]
[1, 2, 3, 4, 100, 20]
example 3
example 4
example 5
test
example 6
the length of a is 11
example 7
test str
test str
example 8
Hello, world!
world!
需要注意的主要就是:String类型底层实现是vec<u8>,unicode类型,并且拿着引用可以改变String内容。有点类似中在go做一个特殊的String类型,并且内部包着一个byte数组。
网友评论