RUST总结

RUST总结


https://www.wenjiangs.com/docs/rustprimer
https://kaisery.github.io/trpl-zh-cn/foreword.html
http://llever.com/gentle-intro/readme.zh.html

Rust 是一种 预编译静态类型(ahead-of-time compiled)语言,这意味着你可以编译程序,并将可执行文件送给其他人,他们甚至不需要安装 Rust 就可以运行。

Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。
(我们把代码所需要的库叫做 依赖(dependencies))。

cargo new 命令生成的 Cargo.toml 的内容
第一行,[package],是一个片段(section)标题,表明下面的语句用来配置一个包。
接下来的三行设置了 Cargo 编译程序所需的配置:项目的名称、版本和作者。
最后一行,[dependencies],是罗列项目依赖的片段。在 Rust 中,代码包被称为 crates。

cargo build 构建项目 或者用 cargo run 在一个命令中同时编译并运行生成的可执行文件
cargo build --release 并使用 target/release 下的可执行文件进行测试
cargo check 的命令。该命令快速检查代码确保其可以编译,但并不产生可执行文件

在 Rust 中,变量默认是不可变的,在变量名前使用 mut 来使一个变量可变

静态方法(static method):关联函数是针对类型实现的,在这个例子中是 String,而不是 String 的某个特定实例
ep: let mut guess = String::new(); //::new 那一行的 :: 语法表明 new 是 String 类型的一个 关联函数(associated function)

你不可能凭空就知道应该 use 哪个 trait 以及该从 crate 中调用哪个方法。crate 的使用说明位于其文档中。
Cargo 有一个很棒的功能是:运行 cargo doc --open 命令来构建所有本地依赖提供的文档,并在浏览器中打开。
例如,假设你对 rand crate 中的其他功能感兴趣,你可以运行 cargo doc --open 并点击左侧导航栏中的 rand。

Rust 是一门基于表达式(expression-based)的语言,并且你将编写的大部分 Rust 代码是由表达式组成的。
let y = 6; 中的 6 是一个表达式,它计算出的值是 6。函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),{},也是一个表达式

let y = {
  let x = 3;
  x + 1
  };

表达式的结尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值。

具有返回值的函数
函数可以向调用它的代码返回值。我们并不对返回值命名,但要在箭头(->)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。
使用 return 关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。

  fn five() -> i32 {
  5
  }

Rust 的核心功能(之一)是 所有权(ownership)。

  1. Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
  2. 值有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。
    copy and move and clone

如下是一些 Copy 的类型:
所有整数类型,比如 u32 。
布尔类型, bool ,它的值是 true 和 false 。
所有浮点数类型,比如 f64 。
字符类型, char 。
元组,当且仅当其包含的类型也都是 Copy 的时候。比如, (i32, i32) 是 Copy 的,
但 (i32, String) 就不是。

当枚举值包含数据时,你可
以根据需要处理多少情况来选择使用 match 或 if let 来获取并使用这些值

模块文件系统的规则
让我们总结一下与文件有关的模块规则:
如果一个叫做 foo 的模块没有子模块,应该将 foo 的声明放入叫做 foo.rs 的文件中。
如果一个

私有性规则
总的来说,有如下项的可见性规则:

  1. 如果一个项是公有的,它能被任何父模块访问
  2. 如果一个项是私有的,它能被其直接父模块及其任何子模块访问

为了一次将某个命名空间下的所有名称都引入作用域,可以使用 * 语法,这称为 glob 运算符

enum TrafficLight {
Red,
Yellow,
Green,
}
use TrafficLight::*;
fn main() {
let red = Red;
let yellow = Yellow;
let green = Green;
}

RUST类型

Rust内置的原生类型 (primitive types) 有以下几类:

  • 布尔类型:有两个值 true 和 false 。
  • 字符类型:表示单个Unicode字符,存储为4个字节。
  • 数值类型:分为有符号整数 ( i8 , i16 , i32 , i64 , isize(自适应类型) )、
  • 无符号整数 ( u8 , u16 , u32 , u64 , usize ) 以及浮点数 ( f32 , f64 )。
  • 字符串类型:最底层的是不定长类型 str ,更常用的是字符串切片 &str 和
    堆分配字符串 String , 其中字符串切片是静态分配的,有固定的大小,并
    且不可变,而堆分配字符串是可变的。
  • 数组:具有固定大小,并且元素都是同种类型,可表示为 [T: N] 。
  • 切片:引用一个数组的部分数据并且不需要拷贝,可表示为 &[T] 。
  • 元组:具有固定大小的有序列表,每个元素都有自己的类型,通过解构或者索
    引来获得每个元素的值。
  • 指针:最底层的是裸指针 *const T 和 *mut T ,但解引用它们是不安全
    的,必须放到 unsafe 块里。
  • 函数:具有函数类型的变量实质上是一个函数指针。
  • 元类型:即 () ,其唯一的值也是 () 。

trait

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // methods with default implementations elided
}

这段代码表
明实现 Iterator trait 要求同时定义一个 Item 类型,这个 Item 类型被用作 next 方法的
返回值类型。换句话说, Item 类型将是迭代器返回元素的类型。

所有的trait中都有一个隐藏的类型Self(大写S),代表当前这个实现了此trait
的具体类型。trait中定义的函数,也可以称作关联函数(associated function)。

  • 函数的第一个参数如果是Self相关的类型,且命名为self(小写s),这个参数可以被称
    为“receiver”(接收者)。
  • 具有receiver参数的函数,我们称为“方法”(method),可
    以通过变量实例使用小数点来调用。
  • 没有receiver参数的函数,我们称为“静态函
    数”(static function),可以通过类型加双冒号::的方式来调用。在Rust中,函数
    和方法没有本质区别。

基本语法

  • 绑定

let a = 1;

  • move

let a: String = String::from(“xyz”);
let b = a; //a无效

  • copy

对于实现 Copy 特性的变量,在move时会拷贝资源到新内存区域,并把新内存区域的资
源 binding 为 b 。

let a: i32 = 100;
let b = a;
println!("{}", a);   //a有效

let a: String = String::from("xyz");
let b = a.clone(); // <-注意此处的clone
println!("{}", a); //a和b对应的资源值相同,但是内存地址并不一样。

move关键字,会把闭包中的外部变量的所有权move到包体内,发生了所
有权转移

let x: T = something;
let y = x;
  • 类型 T 没有实现 Copy 特性: x 所有权转移到 y 。
  • 类型 T 实现了 Copy 特性:拷贝 x 所绑定的 资源 为 新资源 ,并把 新资
    源 的所有权绑定给 y , x 依然拥有原资源的所有权。

算数运算符都有对应的trait的,他们都在 std::ops 下:

一元操作符
- : 取负
* : 解引用
! : 取反
& 和 &mut : 租借,borrow

二元操作符
+ : 加法。实现了 std::ops::Add 。
- : 减法。实现了 std::ops::Sub 。
* : 乘法。实现了 std::ops::Mul 。
/ : 除法。实现了 std::ops::Div 。
% : 取余。实现了 std::ops::Rem 。

位运算符
& : 与操作。实现了 std::ops::BitAnd 。
| : 或操作。实现了 std::ops::BitOr 。
^ : 异或。实现了 std::ops::BitXor 。
<< : 左移运算符。实现了 std::ops::Shl 。
>> : 右移运算符。实现了 std::ops::Shr 。

逻辑运算符有三个,分别是 && 、 || 、 ! 。
Rust里这个boolean运算符只能用在bool类型变量

比较运算符
比较运算符所实现的trait只有两个
std::cmp::PartialEq 和 std::cmp::PartialOrd
== 和 != 实现的是 PartialEq
< 、 > 、 >= 、 <= 实现的是 PartialOrd 。
类型转换运算符 as

重载运算符
就是为类型实现对应运算符的trait

格式化字符串
基础运算符和字符串格式化

五个宏和两个trait:

format! 、 format_arg! 、 print! 、 println! 、 write! ; Debug 、Display

& 符号就是 引用,它们允许你使用值但不获取其所有权
与使用 & 引用相反的操作是 解引用(dereferencing),它使用解引用运算符, * 。

可变引用
首先,必须将 s 改为 mut 。然后必须创建一个可变引用 &mut s 和接受一个可变引用
some_string: &mut String 。
不过可变引用有一个很大的限制:在特定作用域中的特定数据有且只有一个可变引用

我们将获取引用作为函数参数称为 借用(borrowing)
我们 也 不能在拥有不可变引用的同时拥有可变引用
在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
引用必须总是有效。

悬垂引用
所谓悬垂指针是其指向的内存可能已经被释放可能已分配给其它持有者

fn dangle() -> &String {
    // dangle 返回一个 String 引用
    let s = String::from("hello"); // s 是新创建的 String
    &s // 我们返回 String 的引用,s
} // 这里,s 移出了作用域,并被丢弃。它的内存被释放
  // 危险!

“字符串 slice” 的类型声明写作 &str

使用一个由中括号中的 [starting_index…ending_index] 指定的 range 创建一个 slice

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

字符串字面值就是 slice

let s = "Hello, world!";
&str 是一个不可变引用。

其他类型的 slice

fn first_array (a:&[i32])->&[i32] {
    return &a[..]
}

这个 slice 的类型是 &[i32] 。它跟字符串 slice 的工作方式一样,通过存储第一个集合元素
的引用和一个集合总长度。你可以对其他所有集合使用这类 slice。

简而言之,在模式的上下文中, & 匹配一个引用并返回它的值,而
ref 匹配一个值并返回一个引用。
模式中的 & 并不能 创建 引用,它会 匹配 值中已
经存在的引用。因为 & 在模式中已经有其他意义 ,不能够使用 & 在模式中创建引用。
相对的,为了在模式中创建引用,可以在新变量前使用 ref 关键字.

条件 Err(ref error) if error.kind() == ErrorKind::NotFound 被称作 match guard:它是一个进一步完善

match 分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹
配会继续并考虑 match 中的下一个分支。模式中的 ref 是必须的,这样 error 就不会被
移动到 guard 条件中而仅仅只是引用它

通常当匹配模式时,模式所引入的变量将绑定一个值。Rust 的所有权规则意味着这个值将被移动到
match 中,或者任何使用此模式的位置。

使用 ref 和 ref mut 在模式中创建引用
们将看到使用 ref 来创建引用,这样值的所有权就不会移动到模式的变量中。

一个带有变量的模式的例子,并
接着在 match 之后使用这整个值。这会编译失败,因为值 robot_name 的一部分在第一个

match 分支时被移动到了模式的变量 name 中:
    let robot_name = Some(String::from("Bors"));
    match robot_name {
        Some(name) => println!("Found a name: {}", name),
        None => (),
    }
    println!("robot_name is: {:?}", robot_name);

当被模式匹配命中的时候,未实现 Copy 的类型会被默认的
move掉
这个例子会编译失败,因为当 name 绑定 robot_name 的 Some 中的值时,其被移动到了
match 中。因为 robot_name 的部分所有权被移动到了 name 中,就不再能够在 match 之
后的 println! 中使用 robot_name ,因为 robot_name 不再有所有权。

使用 ref mut 来创建一个值的可变引用

    let mut robot_name = Some(String::from("Bors"));
    match robot_name {
        Some(ref mut name) => *name = String::from("Another name"),
        None => (),
    }
    println!("robot_name is: {:?}", robot_name);

匹配守卫(match guard)是一个指定与 match 分支模式之后的额外 if 条件,它也必须被
满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。

@ 绑定
at 运算符 @ 允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。

    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),
        Message::Hello { id: 10...12 } => println!("Found an id in another range"),
        Message::Hello { id } => println!("Found some other id: {}", id),
    }

  • 模式的全部语法

多个模式

  • match 表达式中,可以使用 | 语法匹配多个模式,它代表 或(or)的意思。
  • 通过 … 匹配值的范围
    范围只允许用于数字或 char 值,因为编译器会在编译时检查范围不为空。 char 和 数字值
    是 Rust 唯一知道范围是否为空的类型。
fn main() {
    let x = 1;
    match x {
        1 | 2 => println!("one or two"),
        3 => println!("three"),
        4...10 => println!("four through ten"),
        _ => println!("default anything"),
    }
}

解构结构体

fn main() {
    let p = Point { x: 0, y: 7 };
    let Point { x: a, y: b } = p;
    fn main() {
        let p = Point { x: 0, y: 7 };

        match p {
            Point { x, y: 0 } => println!("On the x axis at {}", x),
            Point { x: 0, y } => println!("On the y axis at {}", y),
            Point { x, y } => println!("On neither axis: ({}, {})", x, y),
        }
    }
}

解构枚举

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

解构引用

当模式所匹配的值中包含引用时,需要解构引用之中的值,这可以通过在模式中指定 & 做
到。这让我们得到一个包含引用所指向数据的变量,而不是包含引用的变量。

  • 忽略模式中的值

使用 _ 忽略整个值

fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

使用嵌套的 _ 忽略部分值

当不需要 Some 中的值时在模式内使用下划线来匹配 Some 成员 (Some(_)

    let numbers = (2, 4, 8, 16, 32);
    match numbers {
        (first, _, third, _, fifth) => println!("Some numbers: {}, {}, {}", first, third, fifth),
    }

通过在名字前以一个下划线开头来忽略未使用的变量

_x 仍会将值绑定到变量,而 _ 则完全不会绑定

用 … 忽略剩余值

对于有多个部分的值,可以使用 … 语法来只使用部分并忽略其它值,同时避免不得不每一
个忽略值列出下划线。

    let numbers = (2, 4, 8, 16, 32);
    match numbers {
        (_, second,.. ) => {
            println!("Some numbers:  {}", second);
        }
    }
  • 面向对象编程:

面向对象编程语言所共享的一些特性往往是对象、封装和继承

  • 对象包含数据和行为:rust 是面向对象的:结构体和枚举包含数据而 impl 块提供了在结构体和枚举之上的方法。

  • 封装隐藏了实现细节: rust 可以使用 pub 关键字来决定模块、类型、函数和方法是公有的,而默认情况下其他一切都是私有的。
    在代码中不同的部分使用 pub 与否可以封装其实现细节。

  • 继承(Inheritance)是一个很多编程语言都提供的机制,一个对象可以定义为继承另一个对
    象的定义,这使其可以获得父对象的数据和行为,而无需重新定义。

  • Rust 无法定义一个结构体继承父结构体的成员和方法。
    选择继承有两个主要的原因。第一个是为了重用代码:一旦为一个类型实现了特定行为,继
    承可以对一个不同的类型重用这个实现

  • Rust:任何实现了 Summarizable trait 的类型都可以使用 summary 方法而无须进一步实现。这类似于
    父类有一个方法的实现,而通过继承子类也拥有这个方法的实现。当实现 Summarizable trait
    时也可以选择覆盖 summary 的默认实现,这类似于子类覆盖从父类继承的方法实现。
    Rust 代码可以使用默认 trait 方法实现来进行共享

  • 第二个使用继承的原因与类型系统有关:表现为子类型可以用于父类型被使用的地方。这也
    被称为 多态(polymorphism),这意味着如果多种对象共享特定的属性,则可以相互替代使
    用。

trait Draw {
    fn draw(&self);
}

struct MyScreen {
    components: Vec<Box<Draw>>,
}

impl MyScreen
{
    fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

struct MyButton {
    width: u32,
    height: u32,
    label: String,
}
impl Draw for MyButton {
    fn draw(&self) {
        // Code to actually draw a button
        println!("Draw button, {} {} {}",self.width,self.height,self.label);
    }
}
struct MySelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}
impl Draw for MySelectBox {
    fn draw(&self) {
        // Code to actually draw a select box
        println!("Draw select box,{} {}",self.width,self.height);
    }
}
fn main() {
    let screen = MyScreen {
        components: vec![
            Box::new(MySelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No"),
                ],
            }),
            Box::new(MyButton {
                width: 50,
                height: 10,
                label: String::from("OK"),
            }),
        ],
    };
    screen.run();
}

Box 具有所有权的智能指针

Box 是堆上分配的内存,通过 Box::new() 会创建一个堆空间并返回一个指向堆空间的指针

Rust 中是在编译期编译器借助lifetime 对堆内存生命期进行分析,在生命期结束时自动插入 free 。

在编译期间不能确定大小的数据类型都需要使用堆上内存,因为编译器
无法在栈上分配 编译期未知大小 的内存,所以诸如 String, Vec 这些类型的内存其
实是被分配在堆上的。
换句话说,我们可以很轻松的将一个 Vec move 出作用域而不必担心消耗,因为数据实际上不会被复制。
另外,需要从函数中返回一个浅拷贝的变量时也需要使用堆内存而不能直接返回一个指向函数内部定义变量的引用。

  • Box类型永远执行的是move语义,不能是copy语义。
  • Rust中的copy语义就是浅复制。对于Box这样的类型而言,浅复制必然会造成二次释放问题。

nightly 版本中引入 box 关键词,可以用来取代 Box::new() 申请一个堆空间,也可以用在模式匹配上面

这种写法和Box::new()函数调用并没有本质区别。将来box关键字可能会
同样支持各种智能指针,从而根据上下文信息自动判断执行。

比如let p:
Rc=box T{value:1};就可以构造一个Rc指针。

fn main() {
    let mut boxed = Some(Box::new(5));
    match boxed {
        Some (ref mut unboxed) => {
            println!("Some{}",unboxed);
            *unboxed = Box::new(6);
        },
        None => println!("None"),
    }
    let b = boxed;
    match b{
        Some (ref unboxed) => println!("Some{}",unboxed),
        None => println!("None"),
    }
}

Rc 和Arc 使用引用计数的方法,让程序在同一时刻,实现同一资源的多个所有权拥有
者,多个拥有者共享资源

Arc 是原子引用计数,是 Rc 的多线程版本。 Arc 通过 std::sync::Arc
引入。

use std::rc::Rc;
struct Owner {
    name: String,
}
struct Gadget {
    id: i32,
    owner: Rc<Owner>,
}
fn main() {
    // Create a reference counted Owner.
    let gadget_owner: Rc<Owner> = Rc::new(Owner {
        name: String::from("Gadget Man"),
    });
    // Create Gadgets belonging to gadget_owner. To increment th
    //e reference
    // count we clone the `Rc<T>` object.
    let gadget1 = Gadget {
        id: 1,
        owner: gadget_owner.clone(),
    };
    let gadget2 = Gadget {
        id: 2,
        owner: gadget_owner.clone(),
    };
    drop(gadget_owner);
    // Despite dropping gadget_owner, we're still able to print out the name
    // of the Owner of the Gadgets. This is because we've only dropped the
    // reference count object, not the Owner it wraps. As long as there are
    // other `Rc<T>` objects pointing at the same Owner, it willremain
    // allocated. Notice that the `Rc<T>` wrapper around Gadget.owner gets
    // automatically dereferenced for us.
    println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);
    println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);
    // At the end of the method, gadget1 and gadget2 get destroyed, and with
    // them the last counted references to our Owner. Gadget Mannow gets
    // destroyed as well.
}

Mutex

  1. Mutex 会等待获取锁令牌(token),在等待过程中,会阻塞线程。同时只有一个线程的 Mutex 对象获取到锁;
  2. Mutex 通过 .lock() 或 .try_lock() 来尝试得到锁令牌,被保护的对象,必须通过这两个方法返回的 RAII 守卫来调用,不能直接操作;
  3. 当 RAII 守卫作用域结束后,锁会自动解开;
  4. 在多线程中, Mutex 一般和 Arc 配合使用。
use std::sync::Mutex;
fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }
    println!("m = {:?}", m);
}
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::thread;
const N: usize = 10;

fn main() {
    let data = Arc::new(Mutex::new(0));

    let (tx, rx) = channel();

    for _ in 0..10 {
        let (data, tx) = (data.clone(), tx.clone());
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            *data += 1;
            if *data == N {
                tx.send(()).unwrap();
            }
        });
    }
    rx.recv().unwrap();
    println!("{:?}", *data); //10
}

RwLock

  1. 同时允许多个读,最多只能有一个写;
  2. 读和写不能同时存在;
use std::sync::RwLock;
let lock = RwLock::new(5);

读写锁的方法

  1. .read()
  2. .try_read()
  3. .write()
  4. .try_write()
    注意需要对 .try_read() 和 .try_write() 的返回值进行判断。

Cell, RefCell

通常,我们要修改一个对象,必须

  1. 成为它的拥有者,并且声明 mut ;
  2. 或 以 &mut 的形式,借用;

而通过 Cell , RefCell ,我们可以在需要的时候,就可以修改里面的对象。而不受编译期静态借用规则束缚。

Cell 只能用于 T 实现了 Copy 的情况;
.get() 方法,返回内部值的一个拷贝。

use std::cell::Cell;

fn main() {
    let c = Cell::new(5);
    let ret = c.get();
    println!("{}", ret);
    c.set(10);
    let ret = c.get();
    println!("{}", ret);
}
  • RefCell 实现了 运行时借用 ,这个借用是临时的。
  • 在不确定一个对象是否实现了 Copy 时,直接选 RefCell ;
  • RefCell 只能用于线程内部,不能跨线程;
  • RefCell 常常与 Rc 配合使用(都是单线程内部使用);
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
fn main() {
    let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
    shared_map.borrow_mut().insert("africa", 92388);
    shared_map.borrow_mut().insert("kyoto", 11837);
    shared_map.borrow_mut().insert("piccadilly", 11826);
    shared_map.borrow_mut().insert("marbles", 38);

    for (i, j) in shared_map.borrow_mut().iter() {
        println!("{}{}", i, j);
    }
}

.into_inner() 取出包裹值

闭包(小的匿名函数)

  • 闭包不要求像 fn 函数那样在参数和返回值上注明类型。他们储存在变量中并
    被使用,不用命名他们或暴露给库的用户调用。
  • 闭包通常很短并只与对应相对任意的场景较小的上下文中。
    之所以把它称为“闭包”是因为它们“包含在环境中”(close over their environment)。
fn main() {

    // let plus_one = |x: i32| -> i32 {x + 1 };
    // println!("{}",plus_one(1));
    // assert_eq!(2, plus_one(1));
    let num = 5;
    let plus_num = |x:i32| x+num;
    println!("{}",plus_num(5));
}

闭包的实现

在Rust中,函数和闭包都是实现了 Fn 、 FnMut 或 FnOnce 特质(trait)的类
型。任何实现了这三种特质其中一种的类型的对象,都是 可调用对象 ,都能像函
数和闭包一样通过这样 name() 的形式调用,我们使用trait系统来重载运算符。

闭包作为参数:

静态分发:

fn call_with_one<F>(some_closure: F) -> i32
where
    F: Fn(i32) -> i32,   //实现了函数调用Fn trait
{
    some_closure(1)
}
fn main() {
    let answer = call_with_one(|x| x + 2);
    assert_eq!(3, answer);
}

动态分发:

fn call_with_one(some_closure: &Fn(i32)->i32) -> i32
{
    some_closure(1)
}
fn main() {
    let answer = call_with_one(&|x| x + 2);
    assert_eq!(3, answer);
}

返回闭包

fn factory() -> Box< (Fn(i32)-> i32)> {
    let num =5;
    Box::new(move |x| x + num)
}

fn main() {
    let f = factory();
    let answer = f(1);
    assert_eq!(6,answer);
}

泛型

use std::ops::Add;
#[derive(Debug)]
struct Point<T: Add<T, Output = T>> {
    x: T,
    y: T,
}

fn add<T: Add<T, Output = T>>(a: T, b: T) -> T {
    a + b
}

impl<T: Add<T, Output = T>> Add for Point<T> {
    type Output = Point<T>;
    fn add(self, p: Point<T>) -> Point<T> {
        Point {
            x: self.x + p.x,
            y: self.y + p.y,
        }
    }
}

fn main() {
    println!("{}", add(100i32, 1i32));
    println!("{}", add(100.11f32, 100.22f32));
    println!("{:?}", add(Point { x: 1, y: 2 }, Point { x: 3, y: 4 }));
}

迭代器分为三个部分:迭代器,适配器,消费者

迭代器本身提供了一个惰性的序列

适配器,则是对迭代器进行遍历,并且其生成的结果是另一个迭代器,可以被链
式调用直接调用下去。

map 函数  (1..20).map(|x| x+1); //所有元素+1
skip
take
filter
zip 将两个迭代器的内容压缩到一起
enumerate 迭代器的下标显示出来
let v: Vec<_> = (1..20).filter(|x| x%2 == 0).collect(); //筛选出所有的偶数。

消费者主要作用就是将迭代器转换成其他类型的值,而非另一个迭代器。

一个典型的消费者就是 collect,它负责将迭代器里面的所有数据取出
取第几个值所用的 .nth()
用来查找值的 .find()
调用下一个值的 next()
累加 fold
let result2 = numbers.iter().fold(0, |acc, &x| acc + x)

输入与输出

宏定义

宏定义格式是: macro_rules! macro_name { macro_body }

macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            println!("function {:?} is called", stringify!($func_name))
        }
    };
}
fn main() {
    create_function!(foo);
    foo();
}
  • $func_name ,宏定义里面的变量都是以 $ 开头的
  • ident 表示这个变量是一个 identifier,这是语法层面的类型(designator)
    普通的类型如 char, &str, i32, f64 这些是语义层面的类型
    create_function 的参数 foo 正好是一个标识符( ident ),所以能匹配上

宏支持以下几种指示符:

ident: 标识符,用来表示函数或变量名
expr: 表达式
block: 代码块,用花括号包起来的多个语句
pat: 模式,普通模式匹配(非宏本身的模式)中的模式,例如 Some(t) ,(3, 'a', _)
path: 路径,注意这里不是操作系统中的文件路径,而是用双冒号分隔的限定
名(qualified name),如 std::cmp::PartialOrd
tt: 单个语法树
ty: 类型,语义层面的类型,如 i32 , char
item: 条目,
meta: 元条目
stmt: 单条语句,如 let a = 42;

重复(repetition)

$(…),* , $(…);* , $(…),+ , $(…);+ 这样的用来表示重复。

macro_rules! vector {
($($x:expr),*) => {
{
let mut temp_vec = Vec::new();
$(temp_vec.push($x);)*
temp_vec
}
};
}
fn main() {
    let a = vector![1, 2, 4, 8];
    println!("{:?}", a);
}

递归(recursion)

macro_rules! find_min {
($x:expr) => ($x);
($x:expr, $($y:expr),+) => (
std::cmp::min($x, find_min!($($y),+))
)
}
fn main() {
println!("{}", find_min!(1u32));
println!("{}", find_min!(1u32 + 2 , 2u32));
println!("{}", find_min!(5u32, 2u32 * 3, 4u32));
}

卫生(hygienic Macro)

即编译器或运行时会保证宏里面定义的变量或函数不会与外面的冲突,在宏里面以普通方
式定义的变量作用域不会跑到宏外面。

导入导出(import/export)
rustc -Z unstable-options --pretty=expanded hello.rs

线程

  • 创建 spawn

  • 等待线程退出join

消息传递
线程之间交换信息

  • 通道( channel )
use std::sync::mpsc;
use std::thread;
fn main() {
    // 创建一个通道
    let (tx, rx): (mpsc::Sender<i32>, mpsc::Receiver<i32>) = mpsc::channel();
    // 创建线程用于发送消息
    thread::spawn(move || {
        // 发送一个消息,此处是数字id
        tx.send(1).unwrap();
    });
    // 在主线程中接收子线程发送的消息并输出
    println!("receive {}", rx.recv().unwrap());
}

异步通道(Channel)

mpsc::channel()

通道是可以同时支持多个发送者的,通过 clone 的方式来实现
异步通道具备消息缓存的功能
消息发送和接收的顺序是一致的,满足先进先出原则。

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    let mut handles = vec![];
    for i in 0..100 {
        let mytx = tx.clone();
        let hd = thread::spawn(move || {
            let val = format!("HI {}", i);
            mytx.send(val).unwrap();
        });
        handles.push(hd);
    }
    for received in rx {
        println!("Got:{}", received);
    }
    //let received = rx.recv().unwrap();
    //println!("Got:{}", received);

    for i in handles {
        i.join().unwrap();
    }
}

同步通道

mpsc::sync_channel(0)

同步通道是需要指定缓存的消息个数的,但需要注意的是,最小可以是0,表示没有缓存
发送者是会被阻塞的。当通道的缓存队列不能再缓存消息时,发送者发送消息
时,就会被阻塞

use std::sync::mpsc;
use std::thread;
fn main() {
    // 创建一个同步通道
    let (tx, rx): (mpsc::SyncSender<i32>, mpsc::Receiver<i32>) = mpsc::sync_channel(0);
    // 创建线程用于发送消息
    let new_thread = thread::spawn(move || {
        // 发送一个消息,此处是数字id
        println!("before send");
        tx.send(1).unwrap();
        println!("after send");
    });
    println!("before sleep");
    thread::sleep_ms(5000);
    println!("after sleep");
    // 在主线程中接收子线程发送的消息并输出
    println!("receive {}", rx.recv().unwrap());
    new_thread.join().unwrap();
}

共享内存

通过static变量

use std::thread;
static mut VAR: i32 = 5;

fn main() {
    let new_thread = thread::spawn(move || unsafe {
        println!(" thread var {}", VAR);
        VAR = VAR + 1;
    });
    new_thread.join().unwrap();
    unsafe {
        println!("var {}", VAR);
    }
}

通过堆内存在多个线程间访问box创建的变量

use std::sync::Arc;
use std::thread;

fn main() {
    let var: Arc<i32> = Arc::new(5);
    let share_var = var.clone();

    let new_thread = thread::spawn(move || {
        println!("thread value {} ,addresd {:p}", share_var, &*share_var);
    });

    new_thread.join().unwrap();
    println!("thread value {} ,addresd {:p}", var, &*var);
}
use std::sync::Arc;

let var : Arc<i32> = Arc::new(5);
let share_var = var.clone();

内嵌于语言中的: std::marker 中的 Sync 和 Send trait

可拓展的并发Send 和Sync

通常并不需要手动实现 Send 和 Sync trait,因为由 Send 和 Sync 的类型组成的类型,自
动就是 Send 和 Sync 的。

对于不能由编译器自动推导出来的类型,要使它们具有 Send 或 Sync 的约定,可以由人手动实现。实现的时候,必须使用
unsafe 前缀,因为 Rust 默认不信任程序员,由程序员自己控制的东西,统统标
记为 unsafe ,出了问题(比如,把不是线程安全的对象加上 Sync 约定)由程序员自行负责。

Send 允许在线程间转移所有权:

任何完全由 Send 的类型组成的类型也会自动被标记为 Send 。几乎所有基本类型都是
Send 的,除了裸指针(raw pointer)。

Sync 允许多线程访问:

Sync 标记 trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用。
Mutex 是 Sync 的,正如 “在线程间共享 Mutex ” 部分所讲的它可以
被用来在多线程中共享访问。

同步
同步指的是线程之间的协作配合,以共同完成某个任务。
两个关键点:一是共享资源的访问, 二是访问资源的顺序。

一:等待一段时间后,再接着继续执行。

std::thread::sleep
std::thread::sleep_ms
std::thread::park_timeout
std::thread::park_timeout_ms

二:当前线程自己主动放弃当前时间片的调度,让调度器重新选择线程来执行
std::thread::yield_now

三:需要其他线程参与,才能把等待的线程叫醒的等待

std::thread::JoinHandle::join ,
std::thread::park ,
std::sy
nc::Mutex::lock

通知 std::sync::Condvar 条件变量
通知和等待几乎都是成对出现

如 std::sync::Condvar::wait 和 std::sync::Condvar::notify_one ,
std::sync::Condvar::notify_all 。

#![allow(unused)]
fn main() {
    use std::sync::{Arc, Condvar, Mutex};
    use std::thread;

    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair2 = pair.clone();

    // Inside of our lock, spawn a new thread, and then wait for it to start.
    thread::spawn(move || {
        let &(ref lock, ref cvar) = &*pair2;
        let mut started = lock.lock().unwrap();
        *started = true;
        // We notify the condvar that the value has changed.
        cvar.notify_one();
    });

    // Wait for the thread to start up.
    let &(ref lock, ref cvar) = &*pair;
    let mut started = lock.lock().unwrap();
    while !*started {
        started = cvar.wait(started).unwrap();
    }
}

Condvar 需要和 Mutex 一同使用,因为有 Mutex 保护, Condvar 并发才是安全的。

Mutex::lock 方法返回的是一个 MutexGuard ,在离开作用域的时候,自动销毁,从而自动释放锁,从而避免锁没有释放的问题。

Condvar 在等待时会释放锁的,被通知唤醒时会重新获得锁,从而保证并发安全

锁存在的目的就是为了保证资源
在同一个时间,能有序地被访问,而不会出现异常数据。但其实要做到这一点,也
并不是只有锁,包括锁在内,主要涉及两种基本方式:

原子类型

原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操
作,还具备较高的并发性能,从硬件到操作系统,到各个语言,基本都支持。

在std::sync::atomic 中
包括 AtomicBool , AtomicIsize , AtomicPtr , AtomicUsize

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    let var: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(5));
    let share_var = var.clone();

    let new_thread = thread::spawn(move || {
        println!("thread:{}", share_var.load(Ordering::SeqCst));
        share_var.store(9, Ordering::SeqCst);
    });
    new_thread.join().unwrap();
    println!("main thread:{}", var.load(Ordering::SeqCst));
}

use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
    let var: Arc<Mutex<u32>> = Arc::new(Mutex::new(5));
    let share_var = var.clone();
    // 创建一个新线程
    let new_thread = thread::spawn(move || {
        let mut val = share_var.lock().unwrap();
        println!("share value in new thread: {}", *val);
        // 修改值
        *val = 9;
    });
    // 等待新建线程先执行
    new_thread.join().unwrap();
    println!("share value in main thread: {}", *(var.lock().unwrap()));
}

Rust为了提高安全性,已然在 val 销毁的时候,自动释放锁了。同时我们发现,为了修改共享的值,开发者必须要调用 lock 才行,这样就又解决了一个安全问题。

常见Trait

  • AsRef 和 AsMut

功能是配合泛型,在执行引用操作的时候,进行自动类型转
得到了一个类型为 &U 的新引用。

AsRef 只是类型转换, foo 对象本身没有被消耗;AsMut 是AsRef 的可变(mutable)引用版本。

  • Borrow, BorrowMut, ToOwned

Borrow: Borrow 可以认为是 AsRef 的严格版本, Borrow 的前后类型之间要求必须有内部等价性。

BorrowMut 是 Borrow 的可变(mutable)引用版本。

ToOwned ToOwned 为 Clone 的普适版本。它提供了 .to_owned() 方法,用于类型转换。

Deref Deref 是 deref 操作符 * 的 trait,比如 *v

属性

属性(Attribute)是一种通用的用于表达元数据的特性

#[meta] 外部属性
#![meta] 内部属性

条件编译
条件编译基本上就是使用 cfg 这个属性

[features]
# 此字段设置了可选依赖的默认选择列表,
# 注意这里的"session"并非一个软件包名称,
# 而是另一个featrue字段session
default = ["jquery", "uglifier", "session"]

# 类似这样的值为空的feature一般用于条件编译,
# 类似于`#[cfg(feature = "go-faster")]`。
go-faster = []

# 此feature依赖于bcrypt软件包,
# 这样封装的好处是未来可以对secure-password此feature增加可选项目。
secure-password = ["bcrypt"]

# 此处的session字段导入了cookie软件包中的feature段落中的session字段
session = ["cookie/session"]

传播错误的简写: ?

use std::fs::File;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

? 所使用的错误值被传递给了 from 函数,它定义于标准库的 From trait 中,其用来将错误从一种类型转换为另一种类型。到问号运算符调用 from 函数时,收到的错误类型被转换为定义为当前函数返回的错误类型。这在当一个函数返回一个错误类型来代表所有可能失败的方式时很有用,即使其可能
会因很多种原因失败。只要每一个错误类型都实现了 from 函数来定义如将其转换为返回的
错误类型,问号运算符会自动处理这些转换。

macro_rules! try {
    ($e:expr) => {
        match $e {
            Ok(val) => val,
            Err(err) => return Err(::std::convert::From::from(err)),
        }
    };
}

try! 事实上就是 match Result 的封装,当遇到 Err(E) 时会提早返回,
::std::convert::From::from(err) 可以将不同的错误类型返回成最终需要的
错误类型,因为所有的错误都能通过 From 转化成 Box

use std::fmt::Debug;
fn my_print<T : Debug>(x: T) {
    println!("The value is {:?}.", x);
}
fn main() {
    my_print("China");
    my_print(41_i32);
    my_print(true);
    my_print(['a', 'b', 'c'])
}

上面这段代码中,my_print函数引入了一个泛型参数T,所以它的参数不是一个
具体类型,而是一组类型。冒号后面加trait名字,就是这个泛型参数的约束条件。
它要求这个T类型实现Debug这个trait。

泛型约束还有另外一种写法,即where子句。示例如下:

fn my_print<T>(x: T) where T: Debug {
    println!("The value is {:?}.", x);
}

Rust提供了一个特殊的attribute, Derive
它可以帮我们自动impl某些trait。

#[derive(Copy, Clone, Default, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Foo {
    data : i32
} fn main() {
    let v1 = Foo { data : 0 };
    let v2 = v1;
    println!("{:?}", v2);
}

aturon提出了impl trait这个方案。此方案引入了一个新的
语法,可以表达一个不用装箱的匿名类型,以及它所满足的基本接口。
示例如下:

#![feature(conservative_impl_trait)]
fn multiply(m: i32) -> impl Fn(i32)->i32 {
    move |x|x*m
}
fn main() {
    let f = multiply(5);
    println!("{}", f(2));
}

这里的impl Fn(i32)->i32表示,这个返回类型,虽然我们不知道它的具体名
字,但是知道它满足Fn(size)->isize这个trait的约束。因此,它解决了“返回不装
箱的抽象类型”问题。

Rust 学习体会

  • 内存安全方面优势:
    基本数据类型,编程语言都差不多,数字,字符,字符串,枚举,结构体等,用法也都相似,但是rust由于引入了所有权,如果不理解这个概念,刚开始直接上手写代码,很容易出编译错误。
    在C++中,程序员比较自由,空指针,野指针,悬垂指针,使用未初始刷的内存,非法释放,缓冲区溢出,执行非法函数指针等造成了内存的不安全,
    因为编译可以通过,执行的时候会出错,如果内存泄露了,排查定位也比较繁琐,虽然后期有解决方法,但由于是靠程序员自己控制,效果也不太好。
    但是Rust语言设计的核心将“所有权”的理念直接融入到了语言之中,不照着编译器的规则就会编译错误。
    通过语言的机制和编译器的功能,把易犯错、不易检查的问题解决在编译期,避免运行时的内存错误。
    Rust 语言建立了严格的安全内存管理模型:
    ·所有权系统。每个被分配的内存都有一个独占其所有权的指针。只有当该指针被销毁
    时,其对应的内存才能随之被释放。
    ·借用和生命周期。每个变量都有其生命周期, 一旦超出生命周期,变量就会被自动释
    放。如果是借用,则可以通过标记生命周期参数供编译器检查的方式,防止出现悬垂
    指针,也就是释放后使用的情况。

  • 线程安全有优势:
    C /C++ 灵活性比较大,比较容易出现线程不安全,需要依赖程序员自己控制,但是rust借助编译器,通过Send & Sync两个特殊的trait实现了Rust线程安全。
    在实际应用中一般用Arc和mutex配合使用,实现安全的跨线程共享变量
    例:

use std::string::String;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
fn main() {
    let m1 = Arc::new(Mutex::new(String::from("hello")));
    let m = m1.clone();
    let a = thread::spawn(move || {
        let mut num = m.lock().unwrap();
        num.push_str(" world");
    });
    a.join();
    println!("m = {:?}", m1);
}
  • 高效易用的异步并发:
    在线程切换和跨线程共享数据上会产生很多额外开销。即使是一个什么都不做的线程也会用尽珍贵的系统资源,而这就是异步代码要减少的开销。

有两种方式:一种是通过async/await!

async fn dance() { ... }

async fn learn_and_sing() {
    // 在唱歌之前等待学歌完成
    // 这里我们使用 `await!` 而不是 `block_on` 来防止阻塞线程,这样就可以同时执行 `dance` 了。
    let song = await!(learn_song());
    await!(sing_song(song));
}
 async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();
     // `join!` 类似于 `await!` ,但是可以等待多个 future 并发完成
    join!(f1, f2)
}
 fn main() {
    block_on(async_main()); //阻塞等待线程完成
}

一种是链式风格:

let mut reactor = Core::new().unwrap();
let chained_future = my_fut().and_then(|retval| my_fn_squared(retval));
let retval2 = reactor.run(chained_future).unwrap();
println!("{:?}", retval2);

把future当成一个未来会执行的普通函数和函数链,只有最后手动调用block_on 或者run到reactor后,他们自己会循环按照前后顺序轮询这个函数链是否执行完了。
在实际使用中我们主要关注如何构造future函数链,比如上面的例子,把学歌和唱歌组合一起future链,跳舞单独一个future,把相关的操作写成一个future链,等到多个future链异步执行,可以提升性能减少开销。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值