searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

rust基础学习

2025-09-08 02:21:33
1
0

1.rust所有权

语句和表达式:前者不返回值,而后者返回值。在rust中,每一个代码块的最后一行可以放一个表达式,这个表达式就作为这整个代码块的返回值。

fn if_test() {
    let condition = true;
    let x = {
        if condition {
            12
        } else {
            let y = 99;
            y + 1
        }
    };
    println!("{}", x);
}

不同语言的垃圾清理规则:

  • java:有gc收集器,当察觉到某个值没有使用之后,自动回收内存
  • c++:开发者手动回收内存
  • rust:当拥有这个值的变量走出作用范围后,内存就会自动收集(调用drop函数)

所有权,就是因为heap内存管理而产生的。rust中的所有权规则:

  • 每个值都对应一个变量,这个变量就是这个值的所有者(变量拥有这个值)
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域的时候,这个值就会被清理
fn test1() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1); // 报错,因为"hello"的所有权被s2夺走,s1失效!
}
fn take_ownership(some_string: String) {
    println!("{}", some_string);
}

fn ownership_test() {
    /* 在这里例子中,s传入take_ownership中,该函数的some_string也指向了这个s指向的值
    而String实现了drop strait,因此s从此失效。
    这种情况,在rust中,我们说s“移动”到了some_string中
     */
    let s = String::from("hello");
    
    take_ownership(s);
    // s已经失效了!报错
    println!("{}", s);
}

上面的例子中,外部的String作为参数传进来后自己就失效了,这在有的情况下是不太方便的。因此,我们使用引用。注意下面的代码:

fn calculate_length(s: &String) -> usize {
    // &表示引用:允许你引用某些值而!!!不取得他的所有权!!!!
    // 我们把引用作为函数参数的行为叫做借用
    // 引用不能被修改,比如这里就不能使用: s.push_str("hello")
    s.len()
}

fn test() {
    let s = String::from("hello");
    let l = calculate_length(&s);
    println!("{}", s);  // 仍然有效!
}

引用不能被修改,那么可以不可以通过添加mut来让他可以被修改呢?注意下面的代码:

fn calculate_length_mut(s: &mut String) -> usize {
    s.push_str("hello");    // 可以修改!
    s.len()
}

fn mut_ref_test() {
    let mut s = String::from("hello");  // 被引用的变量一定要是mut的
    calculate_length_mut(&mut s);   // 同一个作用域里不能同时存在多个可变引用,但是可以存在多个不可变引用
}

字符串切片:字符串切片(&str)其实就是字符串的部分引用,是不可变的:

fn string_stripe_test() {
    let s = String::from("hello world");

    let hello = &s[..5];
    let world = &s[6..];

    let whole = &s[..];

    println!("{} {} is equal to: {}", hello, world, whole);
}

/*
这里有一个小建议:在定义函数的时候,完全使用字符串切片代替字符串引用,只有好处没有坏处,如下:
fn some_function(s: &String),不好
fn some_function(s: &str),好!
为什么好呢?因为你如果传进去的是字符串切片(比如let s = "hello"的s),直接传进去就好了
如果传进去的是String,那么就可以创建一个完整的字符串切片传进去,没有损失任何东西
比如 let my_string = String::from("hello")。这样子传进去:&my_string[..]
 */

2.结构体和枚举

结构体:

struct User {
    username: String,
    email: String, 
    sign_in_count: u64,
    active: bool,
 }

 fn build_user(username: String, active: bool) -> User {
    User {
        email: String::from("aaa@email.com"),
        // 如果传进来的参数名刚好和字段名相同,就可以简写
        username,
        sign_in_count: 111,
        // 这里也是简写
        active,
    }
}

// 如果想要基于某一个struct实例来创建一个新的实例,可以使用struct的更新语法,如下
fn struct_update_test() {
    let user1 = User {
        email: String::from("aa@email.com"),
        username: String::from("chr"),
        active: true,
        sign_in_count: 100,
    };

    let user2 = User {
        username: String::from("zyj"),
        // 这里直接更具user1来创建了user2,只是username不一样
        ..user1
    };

    println!("{},{},{},{}", user2.username, user2.email, user2.active, user2.sign_in_count);
}

fn tuple_struct_test() {
    // 这个叫做tuple struct。适用于想要给一个tuple取名字,让他不同于其他的tuple
    //(就算里边的每个元素的类型都是一样的。就像下边的Color和Point一样)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}


struct Rectangle {
    width: u32,
    length: u32,
}

// 为这个struct定义一个方法
impl Rectangle {
    fn area(&self) -> u32 {
        // 里边使用&,表明我们这里不需要它的所有权(当然也可以去掉&,甚至是加上mut)
        // 在调用的时候,不需要使用(&rec).area()
        // 因为rust他会自动根据情况来添加&,&mut,或者是*
        self.length * self.width
    }

    // 关联函数(他不是一个方法!)(其实就是java中的静态函数,dddd)
    // 通常用于构造器
    // 函数里边没有self,说明就是一个关联函数
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

枚举类型:

enum Message {
    // 枚举类型,下面的Quit、Move称为该枚举类型的变体
    // 这个变体是可以附加信息的,比如这里的Write就附加了一个String字符串
    Quit,
    Move { x: u32, y: u32 },
    Write(String),
    ChangeColor(u32, u32, u32),
}

// 当然也可以为枚举类型创建方法,一样的
impl Message {
    fn function(&self) {
        println!("do nothing");
    }
}

fn test_1() {
    let q = Message::Quit;
    let m = Message::Move { x: 12, y: 245 };
    let w = Message::Write(String::from("hello"));
    let c = Message::ChangeColor(1, 2, 3);

    w.function();
}

/*
在其他编程语言中,几乎都存在null这个东西。
而在rust中,不存在null。在标准库中提供了一个类似的概念
Option<T>枚举
enum Option<T> {
    Some(T),
    None,
}

这样做,可以让None引发的相关错误在编译时期就被发现
 */

枚举类型常常配合 match 使用:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> i32 {
    // match 是一个表达式,因此会返回一个值
    // 注意match里边是 fat arrow( => )
    // match的分支中还可以绑定枚举类型的附加值!!!
    // 同时,这个match必须覆盖到所有的匹配。比如对于Option枚举来说,必须要有Some和None的分支
    // 当然,可以使用“_”来表示通配符
    let value = match coin {
        Coin::Penny => {
            println!("it is a penny!");
            1
        },
        Coin::Nickel => {
            println!("it is a Nickel!");
            5
        },
        Coin::Dime => {
            println!("it is a Dime!");
            10
        },
        Coin::Quarter => {
            println!("it is a Quarter!");
            25
        },
    };
    value
}

fn main() {
    test_1();
    let my_coin = Coin::Dime;
    let my_value = value_in_cents(my_coin);
}

3.常用容器

fn test() {
    let mut v = vec![1, 2, 3];
    // 使用for i in v默认会调用v的into_iter()方法,会得到v的所有权
    // 所以这里使用了可变引用(因为我们还想要改变里边的数字)
    for i in &mut v {
        *i += 100;
    }
    let number = v[0];
    println!("{}", number);
}

use std::{collections::HashMap, vec};

fn test06() {
    let mut hm = HashMap::new();
    hm.insert(String::from("blue"), 10);
    hm.insert(String::from("yellow"), 15);


    // 另一种创建hashmap的方法:使用collect
    // collect可以把数据整合成很多集合类型,包括HashMap
    let teams = vec![String::from("blue"), String::from("yellow")];
    let scores = vec![12, 11];

    // 这里必须提前声明这个变量的类型。因为collect方法可以整合成许多类型
    let zip_hashmap: HashMap<_, _> = teams.iter().zip(scores.iter()).collect();
}

fn test07() {
    // HashMap和所有权
    // 被添加进入hashmap的数据的所有权会被hashmap夺走
    let mut h = HashMap::new();
    let s1 = String::from("hello");
    let s2 = String::from("world");
    h.insert(s1, s2);
    // println!("{}, {}", s1, s2);     // 报错,因为s1和s2的所有权已经被夺走了
    // h.insert(&s1, &s2) 这样所有权不会被夺走
}


fn test08() {
    // 如何获取Hashmap的值呢?使用get方法,同样,返回一个Option
    let mut h = HashMap::new();
    h.insert(String::from("hello"), 11);
    h.insert(String::from("world"), 22);

    let s = String::from("wold");
    // 注意get方法需要接收的是一个引用类型!
    let score = h.get(&s);

    match score {
        Option::Some(number) => println!("{}", number),
        Option::None => println!("none!"),
    };


    // 如何对hashmap遍历?注意&!!!!因为通常来说,我们遍历了hashmap之后还要使用它
    // 你如果不加&,那么hashmap的所有权就被夺走了!反复强调!
    for (k, v) in &h {
        println!("{}: {}", k, v);
    }
}


fn test09() {
    // 如何修改hashmap中的值呢?
    let mut scores = HashMap::new();
    scores.insert(String::from("yellow"), 25);
    
    // 检查blue这个key存不存在?返回一个Entry
    let e = scores.entry(String::from("blue"));
    // 这个or_insert方法会检查这个entry对象有没有value,如果没有,则插入一个默认值
    // 最后返回这个value的可变引用
    e.or_insert(50);
    // 由于yellow已经存在了,所以就没有插入
    scores.entry(String::from("yellow")).or_insert(100);

    println!("{:?}", scores);
}


fn test10() {
    // 这个or_insert方法,他会返回这个key所对应的value的可变引用。注意下面这个例子:
    let text = "hello world fan print fan fan sit";

    let mut compute = HashMap::new();

    for word in text.split_whitespace() {
        // 下面这个times的类型是 &mut i32,对i32的可变引用
        let times = compute.entry(word).or_insert(0);
        *times += 1;
    }

    println!("{:#?}", compute);
}

4.错误处理

在rust中的错误处理通常有两种:

  • 可恢复的错误:返回一个Result<T, E>,将错误传递下去
  • 不可恢复的错误:使用panic!宏中断

如下是打开文件的处理方式

fn test() {
    let f = File::open("hello.txt");
    let f = match f {
        // 这里的file和error变量都是通过模式匹配结构出来的临时变量(枚举变体携带的信息)
        Result::Ok(file) => file,
        Result::Err(error) => {
            panic!("error opening the ile: {:?}", error);
        }
    };
}

// 传播错误,而不去解决它。看下面一个例子
fn read_username_from_file() -> Result<String, io::Error> { // 这里模仿其他方法,将该函数的执行结果返回
    let f = File::open("hello.txt");

    let mut f = match f {
        Result::Ok(file) => file,
        Result::Err(e) => return Result::Err(e),
    };
    
    let mut s = String::new();
    // 注意到,这里的read_to_string方法用到了自身的可变引用,因此上边的f必须是mut的
    match f.read_to_string(&mut s) {
        // 注意,这里我们没有用到Ok的这个附加值,因为我们要的是可变引用s
        Result::Ok(_) => Result::Ok(s),
        Result::Err(e) => Result::Err(e),
    }
}

// 使用 ? 来修改上面的函数
fn read_username_from_file_improved() -> Result<String, io::Error> {
    /*
    把?作用于一个Result,则有两种可能:
    1. 如果Result是Ok,则Ok附带的值就是整个表达式的值,程序继续;
    2. 如果Result是Err,则Err就是整个函数!!!的返回值(相当于直接使用了return Err 
    */
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Result::Ok(s)


    // ? 只能用于返回值为Result的函数里边!
}

5.trait

rust中的trait和java中的interface差不多(个人感觉)

// 定义一个Summary trait,实现这个trait的结构体必须实现trait里边的方法
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String, 
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

将trait作为函数参数:

pub fn notify(item: impl Summary) {
    println!("this item has implemented the trait: {}", item.summarize());
}

// 使用加号来表明这个类型需要实现多个trait
pub fn notify_1<T: Summary + Display>(item: T) {
    println!("this is more simple, {}", item.summarize());
}

// 但是多个参数都指定多个trait,这会导致函数签名不好看
pub fn notify_2<T, U>(a: T, b: U) -> String
where 
    T: Summary + Display,
    U: Clone + Debug,
{
    format!("this way: {}", a.summarize())
}

6.生命周期

生命周期的主要目标就是:避免悬垂引用

函数参数结构体包含多个引用时,Rust 编译器需要明确知道这些引用的存活关系,否则会报错要求手动标注

生命周期的标注:描述了多个引用的生命周期之间的关系(因此标注单个引用的生命周期没有任何意义),但是不会影响到生命周期

下面是一个典型的场景(函数的返回引用依赖于输入参数)

// 错误版本
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// 正确标注版本
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    ...
}

当结构体中包含引用时,同样,结构体不能存活的比引用长:

struct BookShelf<'a> {
    books: &'a [String],
}

fn main() {
    let my_books = vec![
        String::from("rust programming"),
        String::from("cpp programming")
    ];

    let shelf = BookShelf {
        books: &my_books
    };
}

生命周期的省略规则:

  • 每个引用类型的输入参数都有自己的生命周期
  • 如果只有一个输入生命周期桉树,那么该生命周期被赋给所有输出生命周期参数
  • 如果有多个输入生命周期参数,但是其中一个是 &self 或者 &mut self,则 self 的生命周期会被赋给所有输出生命周期参数

经过上面的规则后,如果仍然存在没有确定生命周期的引用,那么编译器就会报错

7.函数式编程

函数式编程的风格: 将一个函数作为参数传入函数中; 将函数作为返回值进行返回; 将函数赋值给变量,晚点执行。。。

闭包: 是匿名函数 保存为变量、作为参数 可以在一个地方创建闭包,然后在另一个上下文调用闭包;来完成运算 可从其定义的作用域捕获值

fn generate_workout(intensity: u32, random_number: u32) {

    // 这就是一个闭包,将一段函数赋值给一个变量
    // 闭包不需要标注参数类型,以及返回值类型
    let mut expensive_closure = Cacher::new(| num: u32 | {
        println!("calculating slowly....");
        thread::sleep(Duration::from_secs(3));
        num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure.value(intensity));
        println!("Next, do {} situps!", expensive_closure.value(intensity));
    } else {
        if random_number == 3 {
            println!("take a break.");
        } else {
            println!("run for {} minutes!", expensive_closure.value(intensity));
        }
    }
}

// 使用struct来存储闭包,可以达到如下效果:
// 当闭包需要被执行的时候,就执行,同时结构体可以将这个执行的值给保存下来
// 这样,无论使用多少次闭包的值,闭包都只会被执行一次!
struct Cacher<T>
where
// 当需要指定fn的trait时,首先使用fn。如果闭包里边的情况会需要实现FnOnce或者FnMut,编译器会告诉你
    T: Fn(u32) -> u32,
{
    calculate: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where 
    T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation: calculation,
            value: Option::None
        }
    }

    fn value(&mut self, args: u32) -> u32 {
        match self.value {
            Option::Some(val) => val,
            Option::None => {
                let val = (self.calculation)(args);
                self.value = Option::Some(val);
                val
            }
        }
    }
}

迭代器相关:

  • iter():创建不可变引用迭代器
  • into_iter():取得容器所有权(使用for e in v默认调用into_iter())
  • into_iter():创建可变引用迭代器

例子:

fn iter_test() {
    let mut v = vec![String::from("hello"), String::from("hey")];

    let mut it1 = v.iter();
    let e1 = it1.next(); // e1的类型是Option<&String>

    // iter_mut方法,得到 &mut String
    let mut it3 = v.iter_mut();
    let e3 = it3.next();

    // 使用into_iter方法,创建的迭代器会取得所有权 String
    let mut it2 = v.into_iter();
    let e2 = it2.next();    // Option<String>
}

// map方法,collect方法常用
fn map_test() {
    let v1 = vec![1, 2, 3];
    let it1 = v1.iter();
    // 在iter上调用map方法会产生一个新的iter
    let it2 = it1.map(|val| {
        val + 1
    });
    // 在iter上调用collect方法会将所有元素放到一个集合里边,collect方法是一个消耗性方法
    let v2: Vec<_> = it2.collect();
    for i in v2 {
        println!("{}", i);
    }
    // 原来的集合没有变化
    for i in v1 {
        println!("{}", i);
    }

    // 通常像下面一样使用链式法则
    // let v3: Vec<_> = v1.iter().map(|a| a + 1).collect();
}

迭代器的 filter、map、collect、next都是很常用的

struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec[Shoe], my_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(| shoe | {
        shoe.size == my_size
    }).collect()
}

8.智能指针

/**
 * Box<T>是最简单的智能指针,它在stack上拥有一个指针,其指向heap上的数据
 * 没有其他的额外功能了
 * 那我感觉Box<T>其实就是Cpp中的指向T类型的指针
 * 所以以后想要在rust中使用指针,直接使用Box就行
 */
fn box_test_1() {
    // 这里的10就是放在heap上的
    let a = Box::new(10);
    println!("a is: {}", a);
    // 当a离开作用域时,它会释放这个10以及在stack上的指针
}

// 可以使用Box来实现rust中的链表:

enum List {
    // Cons中有一个指向List的指针
    Cons(i32, Box<List>),
    Nil,
}
// 用来遍历一个链表!
fn read_a_list(l: &List) {
    let result = match l {
        // 这里的next是 &Box<List>
        List::Cons(value, next) => {
            println!("{}", value);
            read_a_list(next);  // 由于Box实现了Deref strait,他会自动隐式解引用 &Box<List> -> &List
        },
        List::Nil => {},
    };
}

deref trait:

fn deref_test_1() {
    let x = 14;
    let y = &x; // 这里y是一个指针(引用)
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // 这里需要解引用才能获得y所指向的值
}

fn deref_test_2() {
    let x = 14;
    let y = Box::new(x);
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // Box实现了deref,因此我们可以使用*y来访问它所指向的值
}

/**
 * 函数和方法的隐式解引用转化
 * 当把某类型的引用传递给函数或者方法时,但他的类型与定义的参数类型不匹配,
 * 编译器就会自动调用deref转换
 * 如下一个例子(在编译的时候发生,因此没有额外的性能开销):
 */

fn hello(name: &str) {
    println!("hello, {}", name);
}

fn deref_test_4() {
    let m = MyBox::new(String::from("rust"));


    // 这里 &MyBox<String>  -->  &String   -->   &str
    // 编译器自动不断地调用 deref方法,从而最终转化为 &str
    // 不然就应该是这样的: hello(&(*m)[..]),其中 *m 是String
    hello(&m);
}

Rc指针:

fn rc_test_1() {
    /**
     * 这里的要求是,a是一个链表,5--10--nil
     * 我们想要创建b链表: 3--a(5--10--nil),且不复制
     * c链表:4--a(5--10-nil)
     * 如果还要使用普通的Box来完成,就会发现,a如果被b链接了,它的所有权就被b拿走了
     * 再想要使用c链接a就不行了
     * 
     * 这种需要多个指针指向一个数据的情况,就需要使用Rc<T>这个指针!具体的使用如下:
     */
    let a = Rc::new(ListRc::Cons(5, Rc::new(ListRc::Cons(10, Rc::new(ListRc::Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));

    // Rc::clone()  它只是增加引用,不会执行数据的深度拷贝操作
    // 相当于b多了一个3节点,然后连接上a
    let b = ListRc::Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = ListRc::Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c dies  = {}", Rc::strong_count(&a));
}

fn my_rc_test() {
    // 以下示例中,s, pt1, pt2同时指向字符串hello
    let s = Rc::new(String::from("hello"));
    println!("{}", Rc::strong_count(&s));
    // let pt1 = Rc::new(s);
    // let pt2 = Rc::new(s); 报错,因为s的所有权已经被pt1抢走了!
    let pt1 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
    let pt2 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
}

RefCell智能指针,可以:

  • 通过不可变引用修改内部数据
  • 在运行时借用检查,如果违反规则就出发panic
  • 单线程专用
fn refCell_test() {
    let r = RefCell::new(String::from("hello"));
    {
        // 通过不可变引用修改内部数据
        let mut change = r.borrow_mut();
        change.push_str(", rust!");
    }
    // 输出 hello, rust!
    println!("{}", r.borrow());
}

// 一个常用的场景是,搭配Rc指针,实现多个所有者共享可变数据
fn test1() {
    let account = Rc::new(RefCell::new(100));

    let chr = Rc::clone(&account);
    let zyj = Rc::clone(&account);

    *chr.borrow_mut() += 100;
    *zyj.borrow_mut() -= 10;

    // 输出190
    println!("there are {} left in the account.", account.borrow());
}

RefCell 要求在任何时刻,只能存在:

  • 一个可变借用(borrow_mut)
  • 或者多个不可变借用(borrow)

9.并发

/**
 * Concurrent(并发): 程序不同部分独立执行
 * Parallel(并行): 程序不同部分同时运行
 * 
 * 实现线程的方式:
 * 1. 调用OS的api创建线程,1:1(运行时较小,一个操作系统的线程对应一个语言中创建的线程)Rust
 * 2. 语言自己实现的线程,M:N(运行时更大)
 */
use std::thread::{self, spawn};
use std::time::Duration;

fn test_1() {
    // main程序结束后,不管这个spawned线程有没有结束,整个程序结束
    // 因此这个线程大概率是执行不完的
    thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test_2() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 调用handle的join方法会阻塞当前运行线程的执行,直到handle所表示的线程终结
    // 因此这里就会spawned线程1到10,再执行主线程的1到5
    handle.join().unwrap();

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test3() {
    let v = vec![1, 2, 3];
    // 这里我在线程中使用到了闭包外部的数据 v,提示有误
    // 因为v的寿命有可能比这个闭包还要短,这会导致问题
    // 所以现在可以使用move关键字来使得闭包获得 v 的所有权
    let handle = thread::spawn(move || {
        for e in v.iter() {
            println!("{}", e);
        }
    });

    handle.join().expect("something wrong.");
}

消息传递:

use std::sync::mpsc;
fn test_4() {
    // 消息传递
    // 使用mpsc::channel创建一个通道,multiple producer and single consumer
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        // 这个线程必须拥有通道发送端的所有权才能往通道里发送消息
        let value = String::from("hello!");
        sender.send(value).unwrap();
    });
    // 这里的recv方法会阻塞当前的线程,直到有消息传入到通道
    // try_recv方法不会阻塞
    let received= receiver.recv().unwrap();
    println!("Got: {}", received);
}

fn test_5() {
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            // 这里不能使用iter(),因为我们必须获得它的所有权才能send
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推荐使用这种方法来接收消息,底层用到了recv方法
    // 会阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 使用多个发送者
fn test_6() {
    let (sender, receiver) = mpsc::channel();
    // 克隆!
    let another_sender = mpsc::Sender::clone(&sender);

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    thread::spawn(move || {
        let values = vec![String::from("###Cpp"),
         String::from("###java"),
         String::from("###rust"),
         String::from("###C#")];

        for value in values.into_iter() {
            another_sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推荐使用这种方法来接收消息,底层用到了recv方法
    // 会阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 以上都是使用通信的方法实现并发

use std::sync::Mutex;

fn test_7() {
    // Mutex 是一个智能指针,互斥锁
    let m = Mutex::new(5);
    {   
        // 访问数据前,先使用lock来获得锁。这个方法是阻塞的
        // lock可能失败,返回一个MetexGuard智能指针
        let mut num = m.lock().unwrap();
        *num += 12;
    }
    println!("{}", m.lock().unwrap());
}

use std::sync::Arc;
// 使用Arc来进行原子引用计数(和Rc一模一样,只是用在多线程环境下)
fn test_8() {
    // let counter = Mutex::new(0);
    let counter = Arc::new(Mutex::new(0));
    let mut handles = Vec::new();

    for _ in 0..10 {
        // 多个线程同时访问这个互斥锁里边的数据
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles.into_iter() {
        handle.join().unwrap();
    }

    println!("{}", counter.lock().unwrap());
}
0条评论
作者已关闭评论
陈****然
3文章数
0粉丝数
陈****然
3 文章 | 0 粉丝
陈****然
3文章数
0粉丝数
陈****然
3 文章 | 0 粉丝
原创

rust基础学习

2025-09-08 02:21:33
1
0

1.rust所有权

语句和表达式:前者不返回值,而后者返回值。在rust中,每一个代码块的最后一行可以放一个表达式,这个表达式就作为这整个代码块的返回值。

fn if_test() {
    let condition = true;
    let x = {
        if condition {
            12
        } else {
            let y = 99;
            y + 1
        }
    };
    println!("{}", x);
}

不同语言的垃圾清理规则:

  • java:有gc收集器,当察觉到某个值没有使用之后,自动回收内存
  • c++:开发者手动回收内存
  • rust:当拥有这个值的变量走出作用范围后,内存就会自动收集(调用drop函数)

所有权,就是因为heap内存管理而产生的。rust中的所有权规则:

  • 每个值都对应一个变量,这个变量就是这个值的所有者(变量拥有这个值)
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域的时候,这个值就会被清理
fn test1() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1); // 报错,因为"hello"的所有权被s2夺走,s1失效!
}
fn take_ownership(some_string: String) {
    println!("{}", some_string);
}

fn ownership_test() {
    /* 在这里例子中,s传入take_ownership中,该函数的some_string也指向了这个s指向的值
    而String实现了drop strait,因此s从此失效。
    这种情况,在rust中,我们说s“移动”到了some_string中
     */
    let s = String::from("hello");
    
    take_ownership(s);
    // s已经失效了!报错
    println!("{}", s);
}

上面的例子中,外部的String作为参数传进来后自己就失效了,这在有的情况下是不太方便的。因此,我们使用引用。注意下面的代码:

fn calculate_length(s: &String) -> usize {
    // &表示引用:允许你引用某些值而!!!不取得他的所有权!!!!
    // 我们把引用作为函数参数的行为叫做借用
    // 引用不能被修改,比如这里就不能使用: s.push_str("hello")
    s.len()
}

fn test() {
    let s = String::from("hello");
    let l = calculate_length(&s);
    println!("{}", s);  // 仍然有效!
}

引用不能被修改,那么可以不可以通过添加mut来让他可以被修改呢?注意下面的代码:

fn calculate_length_mut(s: &mut String) -> usize {
    s.push_str("hello");    // 可以修改!
    s.len()
}

fn mut_ref_test() {
    let mut s = String::from("hello");  // 被引用的变量一定要是mut的
    calculate_length_mut(&mut s);   // 同一个作用域里不能同时存在多个可变引用,但是可以存在多个不可变引用
}

字符串切片:字符串切片(&str)其实就是字符串的部分引用,是不可变的:

fn string_stripe_test() {
    let s = String::from("hello world");

    let hello = &s[..5];
    let world = &s[6..];

    let whole = &s[..];

    println!("{} {} is equal to: {}", hello, world, whole);
}

/*
这里有一个小建议:在定义函数的时候,完全使用字符串切片代替字符串引用,只有好处没有坏处,如下:
fn some_function(s: &String),不好
fn some_function(s: &str),好!
为什么好呢?因为你如果传进去的是字符串切片(比如let s = "hello"的s),直接传进去就好了
如果传进去的是String,那么就可以创建一个完整的字符串切片传进去,没有损失任何东西
比如 let my_string = String::from("hello")。这样子传进去:&my_string[..]
 */

2.结构体和枚举

结构体:

struct User {
    username: String,
    email: String, 
    sign_in_count: u64,
    active: bool,
 }

 fn build_user(username: String, active: bool) -> User {
    User {
        email: String::from("aaa@email.com"),
        // 如果传进来的参数名刚好和字段名相同,就可以简写
        username,
        sign_in_count: 111,
        // 这里也是简写
        active,
    }
}

// 如果想要基于某一个struct实例来创建一个新的实例,可以使用struct的更新语法,如下
fn struct_update_test() {
    let user1 = User {
        email: String::from("aa@email.com"),
        username: String::from("chr"),
        active: true,
        sign_in_count: 100,
    };

    let user2 = User {
        username: String::from("zyj"),
        // 这里直接更具user1来创建了user2,只是username不一样
        ..user1
    };

    println!("{},{},{},{}", user2.username, user2.email, user2.active, user2.sign_in_count);
}

fn tuple_struct_test() {
    // 这个叫做tuple struct。适用于想要给一个tuple取名字,让他不同于其他的tuple
    //(就算里边的每个元素的类型都是一样的。就像下边的Color和Point一样)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}


struct Rectangle {
    width: u32,
    length: u32,
}

// 为这个struct定义一个方法
impl Rectangle {
    fn area(&self) -> u32 {
        // 里边使用&,表明我们这里不需要它的所有权(当然也可以去掉&,甚至是加上mut)
        // 在调用的时候,不需要使用(&rec).area()
        // 因为rust他会自动根据情况来添加&,&mut,或者是*
        self.length * self.width
    }

    // 关联函数(他不是一个方法!)(其实就是java中的静态函数,dddd)
    // 通常用于构造器
    // 函数里边没有self,说明就是一个关联函数
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

枚举类型:

enum Message {
    // 枚举类型,下面的Quit、Move称为该枚举类型的变体
    // 这个变体是可以附加信息的,比如这里的Write就附加了一个String字符串
    Quit,
    Move { x: u32, y: u32 },
    Write(String),
    ChangeColor(u32, u32, u32),
}

// 当然也可以为枚举类型创建方法,一样的
impl Message {
    fn function(&self) {
        println!("do nothing");
    }
}

fn test_1() {
    let q = Message::Quit;
    let m = Message::Move { x: 12, y: 245 };
    let w = Message::Write(String::from("hello"));
    let c = Message::ChangeColor(1, 2, 3);

    w.function();
}

/*
在其他编程语言中,几乎都存在null这个东西。
而在rust中,不存在null。在标准库中提供了一个类似的概念
Option<T>枚举
enum Option<T> {
    Some(T),
    None,
}

这样做,可以让None引发的相关错误在编译时期就被发现
 */

枚举类型常常配合 match 使用:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> i32 {
    // match 是一个表达式,因此会返回一个值
    // 注意match里边是 fat arrow( => )
    // match的分支中还可以绑定枚举类型的附加值!!!
    // 同时,这个match必须覆盖到所有的匹配。比如对于Option枚举来说,必须要有Some和None的分支
    // 当然,可以使用“_”来表示通配符
    let value = match coin {
        Coin::Penny => {
            println!("it is a penny!");
            1
        },
        Coin::Nickel => {
            println!("it is a Nickel!");
            5
        },
        Coin::Dime => {
            println!("it is a Dime!");
            10
        },
        Coin::Quarter => {
            println!("it is a Quarter!");
            25
        },
    };
    value
}

fn main() {
    test_1();
    let my_coin = Coin::Dime;
    let my_value = value_in_cents(my_coin);
}

3.常用容器

fn test() {
    let mut v = vec![1, 2, 3];
    // 使用for i in v默认会调用v的into_iter()方法,会得到v的所有权
    // 所以这里使用了可变引用(因为我们还想要改变里边的数字)
    for i in &mut v {
        *i += 100;
    }
    let number = v[0];
    println!("{}", number);
}

use std::{collections::HashMap, vec};

fn test06() {
    let mut hm = HashMap::new();
    hm.insert(String::from("blue"), 10);
    hm.insert(String::from("yellow"), 15);


    // 另一种创建hashmap的方法:使用collect
    // collect可以把数据整合成很多集合类型,包括HashMap
    let teams = vec![String::from("blue"), String::from("yellow")];
    let scores = vec![12, 11];

    // 这里必须提前声明这个变量的类型。因为collect方法可以整合成许多类型
    let zip_hashmap: HashMap<_, _> = teams.iter().zip(scores.iter()).collect();
}

fn test07() {
    // HashMap和所有权
    // 被添加进入hashmap的数据的所有权会被hashmap夺走
    let mut h = HashMap::new();
    let s1 = String::from("hello");
    let s2 = String::from("world");
    h.insert(s1, s2);
    // println!("{}, {}", s1, s2);     // 报错,因为s1和s2的所有权已经被夺走了
    // h.insert(&s1, &s2) 这样所有权不会被夺走
}


fn test08() {
    // 如何获取Hashmap的值呢?使用get方法,同样,返回一个Option
    let mut h = HashMap::new();
    h.insert(String::from("hello"), 11);
    h.insert(String::from("world"), 22);

    let s = String::from("wold");
    // 注意get方法需要接收的是一个引用类型!
    let score = h.get(&s);

    match score {
        Option::Some(number) => println!("{}", number),
        Option::None => println!("none!"),
    };


    // 如何对hashmap遍历?注意&!!!!因为通常来说,我们遍历了hashmap之后还要使用它
    // 你如果不加&,那么hashmap的所有权就被夺走了!反复强调!
    for (k, v) in &h {
        println!("{}: {}", k, v);
    }
}


fn test09() {
    // 如何修改hashmap中的值呢?
    let mut scores = HashMap::new();
    scores.insert(String::from("yellow"), 25);
    
    // 检查blue这个key存不存在?返回一个Entry
    let e = scores.entry(String::from("blue"));
    // 这个or_insert方法会检查这个entry对象有没有value,如果没有,则插入一个默认值
    // 最后返回这个value的可变引用
    e.or_insert(50);
    // 由于yellow已经存在了,所以就没有插入
    scores.entry(String::from("yellow")).or_insert(100);

    println!("{:?}", scores);
}


fn test10() {
    // 这个or_insert方法,他会返回这个key所对应的value的可变引用。注意下面这个例子:
    let text = "hello world fan print fan fan sit";

    let mut compute = HashMap::new();

    for word in text.split_whitespace() {
        // 下面这个times的类型是 &mut i32,对i32的可变引用
        let times = compute.entry(word).or_insert(0);
        *times += 1;
    }

    println!("{:#?}", compute);
}

4.错误处理

在rust中的错误处理通常有两种:

  • 可恢复的错误:返回一个Result<T, E>,将错误传递下去
  • 不可恢复的错误:使用panic!宏中断

如下是打开文件的处理方式

fn test() {
    let f = File::open("hello.txt");
    let f = match f {
        // 这里的file和error变量都是通过模式匹配结构出来的临时变量(枚举变体携带的信息)
        Result::Ok(file) => file,
        Result::Err(error) => {
            panic!("error opening the ile: {:?}", error);
        }
    };
}

// 传播错误,而不去解决它。看下面一个例子
fn read_username_from_file() -> Result<String, io::Error> { // 这里模仿其他方法,将该函数的执行结果返回
    let f = File::open("hello.txt");

    let mut f = match f {
        Result::Ok(file) => file,
        Result::Err(e) => return Result::Err(e),
    };
    
    let mut s = String::new();
    // 注意到,这里的read_to_string方法用到了自身的可变引用,因此上边的f必须是mut的
    match f.read_to_string(&mut s) {
        // 注意,这里我们没有用到Ok的这个附加值,因为我们要的是可变引用s
        Result::Ok(_) => Result::Ok(s),
        Result::Err(e) => Result::Err(e),
    }
}

// 使用 ? 来修改上面的函数
fn read_username_from_file_improved() -> Result<String, io::Error> {
    /*
    把?作用于一个Result,则有两种可能:
    1. 如果Result是Ok,则Ok附带的值就是整个表达式的值,程序继续;
    2. 如果Result是Err,则Err就是整个函数!!!的返回值(相当于直接使用了return Err 
    */
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Result::Ok(s)


    // ? 只能用于返回值为Result的函数里边!
}

5.trait

rust中的trait和java中的interface差不多(个人感觉)

// 定义一个Summary trait,实现这个trait的结构体必须实现trait里边的方法
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String, 
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

将trait作为函数参数:

pub fn notify(item: impl Summary) {
    println!("this item has implemented the trait: {}", item.summarize());
}

// 使用加号来表明这个类型需要实现多个trait
pub fn notify_1<T: Summary + Display>(item: T) {
    println!("this is more simple, {}", item.summarize());
}

// 但是多个参数都指定多个trait,这会导致函数签名不好看
pub fn notify_2<T, U>(a: T, b: U) -> String
where 
    T: Summary + Display,
    U: Clone + Debug,
{
    format!("this way: {}", a.summarize())
}

6.生命周期

生命周期的主要目标就是:避免悬垂引用

函数参数结构体包含多个引用时,Rust 编译器需要明确知道这些引用的存活关系,否则会报错要求手动标注

生命周期的标注:描述了多个引用的生命周期之间的关系(因此标注单个引用的生命周期没有任何意义),但是不会影响到生命周期

下面是一个典型的场景(函数的返回引用依赖于输入参数)

// 错误版本
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// 正确标注版本
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    ...
}

当结构体中包含引用时,同样,结构体不能存活的比引用长:

struct BookShelf<'a> {
    books: &'a [String],
}

fn main() {
    let my_books = vec![
        String::from("rust programming"),
        String::from("cpp programming")
    ];

    let shelf = BookShelf {
        books: &my_books
    };
}

生命周期的省略规则:

  • 每个引用类型的输入参数都有自己的生命周期
  • 如果只有一个输入生命周期桉树,那么该生命周期被赋给所有输出生命周期参数
  • 如果有多个输入生命周期参数,但是其中一个是 &self 或者 &mut self,则 self 的生命周期会被赋给所有输出生命周期参数

经过上面的规则后,如果仍然存在没有确定生命周期的引用,那么编译器就会报错

7.函数式编程

函数式编程的风格: 将一个函数作为参数传入函数中; 将函数作为返回值进行返回; 将函数赋值给变量,晚点执行。。。

闭包: 是匿名函数 保存为变量、作为参数 可以在一个地方创建闭包,然后在另一个上下文调用闭包;来完成运算 可从其定义的作用域捕获值

fn generate_workout(intensity: u32, random_number: u32) {

    // 这就是一个闭包,将一段函数赋值给一个变量
    // 闭包不需要标注参数类型,以及返回值类型
    let mut expensive_closure = Cacher::new(| num: u32 | {
        println!("calculating slowly....");
        thread::sleep(Duration::from_secs(3));
        num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure.value(intensity));
        println!("Next, do {} situps!", expensive_closure.value(intensity));
    } else {
        if random_number == 3 {
            println!("take a break.");
        } else {
            println!("run for {} minutes!", expensive_closure.value(intensity));
        }
    }
}

// 使用struct来存储闭包,可以达到如下效果:
// 当闭包需要被执行的时候,就执行,同时结构体可以将这个执行的值给保存下来
// 这样,无论使用多少次闭包的值,闭包都只会被执行一次!
struct Cacher<T>
where
// 当需要指定fn的trait时,首先使用fn。如果闭包里边的情况会需要实现FnOnce或者FnMut,编译器会告诉你
    T: Fn(u32) -> u32,
{
    calculate: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where 
    T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation: calculation,
            value: Option::None
        }
    }

    fn value(&mut self, args: u32) -> u32 {
        match self.value {
            Option::Some(val) => val,
            Option::None => {
                let val = (self.calculation)(args);
                self.value = Option::Some(val);
                val
            }
        }
    }
}

迭代器相关:

  • iter():创建不可变引用迭代器
  • into_iter():取得容器所有权(使用for e in v默认调用into_iter())
  • into_iter():创建可变引用迭代器

例子:

fn iter_test() {
    let mut v = vec![String::from("hello"), String::from("hey")];

    let mut it1 = v.iter();
    let e1 = it1.next(); // e1的类型是Option<&String>

    // iter_mut方法,得到 &mut String
    let mut it3 = v.iter_mut();
    let e3 = it3.next();

    // 使用into_iter方法,创建的迭代器会取得所有权 String
    let mut it2 = v.into_iter();
    let e2 = it2.next();    // Option<String>
}

// map方法,collect方法常用
fn map_test() {
    let v1 = vec![1, 2, 3];
    let it1 = v1.iter();
    // 在iter上调用map方法会产生一个新的iter
    let it2 = it1.map(|val| {
        val + 1
    });
    // 在iter上调用collect方法会将所有元素放到一个集合里边,collect方法是一个消耗性方法
    let v2: Vec<_> = it2.collect();
    for i in v2 {
        println!("{}", i);
    }
    // 原来的集合没有变化
    for i in v1 {
        println!("{}", i);
    }

    // 通常像下面一样使用链式法则
    // let v3: Vec<_> = v1.iter().map(|a| a + 1).collect();
}

迭代器的 filter、map、collect、next都是很常用的

struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec[Shoe], my_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(| shoe | {
        shoe.size == my_size
    }).collect()
}

8.智能指针

/**
 * Box<T>是最简单的智能指针,它在stack上拥有一个指针,其指向heap上的数据
 * 没有其他的额外功能了
 * 那我感觉Box<T>其实就是Cpp中的指向T类型的指针
 * 所以以后想要在rust中使用指针,直接使用Box就行
 */
fn box_test_1() {
    // 这里的10就是放在heap上的
    let a = Box::new(10);
    println!("a is: {}", a);
    // 当a离开作用域时,它会释放这个10以及在stack上的指针
}

// 可以使用Box来实现rust中的链表:

enum List {
    // Cons中有一个指向List的指针
    Cons(i32, Box<List>),
    Nil,
}
// 用来遍历一个链表!
fn read_a_list(l: &List) {
    let result = match l {
        // 这里的next是 &Box<List>
        List::Cons(value, next) => {
            println!("{}", value);
            read_a_list(next);  // 由于Box实现了Deref strait,他会自动隐式解引用 &Box<List> -> &List
        },
        List::Nil => {},
    };
}

deref trait:

fn deref_test_1() {
    let x = 14;
    let y = &x; // 这里y是一个指针(引用)
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // 这里需要解引用才能获得y所指向的值
}

fn deref_test_2() {
    let x = 14;
    let y = Box::new(x);
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // Box实现了deref,因此我们可以使用*y来访问它所指向的值
}

/**
 * 函数和方法的隐式解引用转化
 * 当把某类型的引用传递给函数或者方法时,但他的类型与定义的参数类型不匹配,
 * 编译器就会自动调用deref转换
 * 如下一个例子(在编译的时候发生,因此没有额外的性能开销):
 */

fn hello(name: &str) {
    println!("hello, {}", name);
}

fn deref_test_4() {
    let m = MyBox::new(String::from("rust"));


    // 这里 &MyBox<String>  -->  &String   -->   &str
    // 编译器自动不断地调用 deref方法,从而最终转化为 &str
    // 不然就应该是这样的: hello(&(*m)[..]),其中 *m 是String
    hello(&m);
}

Rc指针:

fn rc_test_1() {
    /**
     * 这里的要求是,a是一个链表,5--10--nil
     * 我们想要创建b链表: 3--a(5--10--nil),且不复制
     * c链表:4--a(5--10-nil)
     * 如果还要使用普通的Box来完成,就会发现,a如果被b链接了,它的所有权就被b拿走了
     * 再想要使用c链接a就不行了
     * 
     * 这种需要多个指针指向一个数据的情况,就需要使用Rc<T>这个指针!具体的使用如下:
     */
    let a = Rc::new(ListRc::Cons(5, Rc::new(ListRc::Cons(10, Rc::new(ListRc::Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));

    // Rc::clone()  它只是增加引用,不会执行数据的深度拷贝操作
    // 相当于b多了一个3节点,然后连接上a
    let b = ListRc::Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = ListRc::Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c dies  = {}", Rc::strong_count(&a));
}

fn my_rc_test() {
    // 以下示例中,s, pt1, pt2同时指向字符串hello
    let s = Rc::new(String::from("hello"));
    println!("{}", Rc::strong_count(&s));
    // let pt1 = Rc::new(s);
    // let pt2 = Rc::new(s); 报错,因为s的所有权已经被pt1抢走了!
    let pt1 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
    let pt2 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
}

RefCell智能指针,可以:

  • 通过不可变引用修改内部数据
  • 在运行时借用检查,如果违反规则就出发panic
  • 单线程专用
fn refCell_test() {
    let r = RefCell::new(String::from("hello"));
    {
        // 通过不可变引用修改内部数据
        let mut change = r.borrow_mut();
        change.push_str(", rust!");
    }
    // 输出 hello, rust!
    println!("{}", r.borrow());
}

// 一个常用的场景是,搭配Rc指针,实现多个所有者共享可变数据
fn test1() {
    let account = Rc::new(RefCell::new(100));

    let chr = Rc::clone(&account);
    let zyj = Rc::clone(&account);

    *chr.borrow_mut() += 100;
    *zyj.borrow_mut() -= 10;

    // 输出190
    println!("there are {} left in the account.", account.borrow());
}

RefCell 要求在任何时刻,只能存在:

  • 一个可变借用(borrow_mut)
  • 或者多个不可变借用(borrow)

9.并发

/**
 * Concurrent(并发): 程序不同部分独立执行
 * Parallel(并行): 程序不同部分同时运行
 * 
 * 实现线程的方式:
 * 1. 调用OS的api创建线程,1:1(运行时较小,一个操作系统的线程对应一个语言中创建的线程)Rust
 * 2. 语言自己实现的线程,M:N(运行时更大)
 */
use std::thread::{self, spawn};
use std::time::Duration;

fn test_1() {
    // main程序结束后,不管这个spawned线程有没有结束,整个程序结束
    // 因此这个线程大概率是执行不完的
    thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test_2() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 调用handle的join方法会阻塞当前运行线程的执行,直到handle所表示的线程终结
    // 因此这里就会spawned线程1到10,再执行主线程的1到5
    handle.join().unwrap();

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test3() {
    let v = vec![1, 2, 3];
    // 这里我在线程中使用到了闭包外部的数据 v,提示有误
    // 因为v的寿命有可能比这个闭包还要短,这会导致问题
    // 所以现在可以使用move关键字来使得闭包获得 v 的所有权
    let handle = thread::spawn(move || {
        for e in v.iter() {
            println!("{}", e);
        }
    });

    handle.join().expect("something wrong.");
}

消息传递:

use std::sync::mpsc;
fn test_4() {
    // 消息传递
    // 使用mpsc::channel创建一个通道,multiple producer and single consumer
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        // 这个线程必须拥有通道发送端的所有权才能往通道里发送消息
        let value = String::from("hello!");
        sender.send(value).unwrap();
    });
    // 这里的recv方法会阻塞当前的线程,直到有消息传入到通道
    // try_recv方法不会阻塞
    let received= receiver.recv().unwrap();
    println!("Got: {}", received);
}

fn test_5() {
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            // 这里不能使用iter(),因为我们必须获得它的所有权才能send
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推荐使用这种方法来接收消息,底层用到了recv方法
    // 会阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 使用多个发送者
fn test_6() {
    let (sender, receiver) = mpsc::channel();
    // 克隆!
    let another_sender = mpsc::Sender::clone(&sender);

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    thread::spawn(move || {
        let values = vec![String::from("###Cpp"),
         String::from("###java"),
         String::from("###rust"),
         String::from("###C#")];

        for value in values.into_iter() {
            another_sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推荐使用这种方法来接收消息,底层用到了recv方法
    // 会阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 以上都是使用通信的方法实现并发

use std::sync::Mutex;

fn test_7() {
    // Mutex 是一个智能指针,互斥锁
    let m = Mutex::new(5);
    {   
        // 访问数据前,先使用lock来获得锁。这个方法是阻塞的
        // lock可能失败,返回一个MetexGuard智能指针
        let mut num = m.lock().unwrap();
        *num += 12;
    }
    println!("{}", m.lock().unwrap());
}

use std::sync::Arc;
// 使用Arc来进行原子引用计数(和Rc一模一样,只是用在多线程环境下)
fn test_8() {
    // let counter = Mutex::new(0);
    let counter = Arc::new(Mutex::new(0));
    let mut handles = Vec::new();

    for _ in 0..10 {
        // 多个线程同时访问这个互斥锁里边的数据
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles.into_iter() {
        handle.join().unwrap();
    }

    println!("{}", counter.lock().unwrap());
}
文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0