IT技术之家

首页 > Android

Android

年前最后一项技能树 Rust ,攻碉堡 ing (Bilibili 视频整理)_洪大宇

发布时间:2022-10-24 18:00:26 Android 0次 标签:rust linux
前言我自己的语言语言学习树做linux 嵌入式开发的话,就拿语言来讲,基本上是完整的, 可以对标linux 社区为什么这样说,内核的调试开发引入了,python BCC 工具,目前是用的最多的C++ 在不考虑模板编程的情况下,和C 差异不大,但是算法提供的多,现成的数据结构也多,同时熟悉面向对象可以对内核编程有帮助Rust 现在小众,但是这个语言,在linux 社区里面 很火,他和C++ 的语法 相差不大,如果C++ 足够熟得话,学习成本较低这3门语言,如果做Linux 嵌入式,主要是别人都...

前言

我自己的语言语言学习树

最新的新闻

 最近的新闻,Rust 审核团队,集体辞职抗议, 还是linux 社区的 linus 牛皮,镇得住场子
 不要慌,咱们是在linux 社区混的,影响不大,该学还是得学 

先下个蛋, 再说,关键问题是内存模型差异巨大, 如果是纯软件,用就用了,驱动编程的接口就很尬,别慌慢慢来

做linux 嵌入式开发的话,就拿语言来讲,基本上是完整的, 可以对标linux 社区为什么这样说,内核的调试开发引入了,python BCC 工具,目前是用的最多的C++ 在不考虑模板编程的情况下,和C 差异不大,但是算法提供的多,现成的数据结构也多,同时熟悉面向对象可以对内核编程有帮助Rust 现在小众,但是这个语言,在linux 社区里面 很火,他和C++ 的语法 相差不大,如果C++ 足够熟得话,学习成本较低这3门语言,如果做Linux 嵌入式,主要是别人都会在看脚本,为了简化工作量,单纯的人工测试,虽然没有运维的水平高,但是简化工作量,还是可以做到的,别人都会工程管理,在linux gnu 开源代码里,用的是 Makefile,但是 大多是 autoconf ,生成的,或者 cmake,但是cmake 语法简单,学习成本低,主要是别人都会日常工作, Markdown 可以快速生成pdf ,在有pdf 转化成 word 也可以 ,说明什么问题清楚明了,也主要是别人都会完全不会的那些,stap 还好,ARM 不是难,是记忆的地方多,而且,得经常用,matlab 做个数据计算啥的方便,可以模拟一些基本电路,也不是难,关键是数学计算,现在忘的差不多了可有可无系列,没啥意思的时候,可以鼓捣,如果不是专门做,我也不建议做,入门容易,但是非常熟练,得花大量时间,好玩是好玩,但是耗时间算法训练,其实是重要,但是,有专门的算法岗,我不太清楚,日常和算法相关的很少,就算涉及了,也是涉及了很多,我们行业的算法,不是leetcode 中的算法,对编程水平的提升,的确有很大的帮助,脑子里有东西在写代码的时候
	乍一看很多,其实很少,计算机语言,本来就是实现设计的工具,或进行计算的工具,真正复杂的地方,涉及到各种协议的算法,硬件也好,
软件也好,,大家都一样,那你做芯片的话,那你就真的很强,但是做linux 嵌入式还是看 linux 内核的熟悉程度

环境布置

建议在ubuntu 搭建环境
sudo apt install rustc # 一键补全rust 环境
#安装rustup
curl https://sh.rustup.rs -sSf | sh

测试 “hello world”

安装完成以后 就会使用 cargo 命令cargo 是一个工程管理工具cargo new hello 创建一个hello的工程进入 src 文件夹
fn main(){
	println!("hello world\n");
}
编译命令
cargo run #就可以开始编译

Rust 语法讲解,官网有详细的文档

我用的是国内翻译好的书上的截图,官方文档是英文的,就这点区别,其他完全一样

vscode的 环境配置

Rust 插件

插件设置

选择 rls 完全够用

剩下的就是 ctrl+shift+p 选择 编译任务 和 ctrl+shift+b 开始 build

Rust 常见问题

1. failed to select a version for the requirement

rust 如果是这种问题,rust 本身也会提供解决问题

2. Blocking waiting for file lock on package cache

直接删除 %USERPROFILE%/.cargo/.package_cache然后修改 %USERPROFILE%/.cargo/config 文件更换为国内源就看以解决这个问题

查看关于rust 工程依赖库的文档,很方便

cargo doc --open

关键语法 所有权 和 枚举 的使用

这个枚举有点骚

所有权的问题,是Rust 编程的核心,C 的核心是 指针 C++ 是模板

不同于C++ 地方

小点

这个分隔符号还是一个挺好的

函数和Python 有点像

变量

let a; 这个 a 自身有块空间a = 3; 直接在 a 上赋值是错误的let b = a; 这种是可以的,为什么可以测试下如果想直接赋值就要使用, mut 进行声明是这个意思 let mut a = 3; a = 5; 这种就没问题

Rust 闭包 和 python 的很像

let f = |x| x + 1;

下面是PYTHON的 lambda

lambda arg1, arg2: arg1 + arg2  # arg1, arg2可以传入默认值

下面是C++的lambda 的表达式的方法

class A
{
    public:
    int i_ = 0;
    void func(int x, int y)
    {
        auto x1 = []{ return i_; };                    // error,没有捕获外部变量
        auto x2 = [=]{ return i_ + x + y; };           // OK,捕获所有外部变量
        auto x3 = [&]{ return i_ + x + y; };           // OK,捕获所有外部变量
        auto x4 = [this]{ return i_; };                // OK,捕获this指针
        auto x5 = [this]{ return i_ + x + y; };        // error,没有捕获x、y
        auto x6 = [this, x, y]{ return i_ + x + y; };  // OK,捕获this指针、x、y
        auto x7 = [this]{ return i_++; };              // OK,捕获this指针,并修改成员的值
    }
};

总结下,就是越来越懒人们

条件判断 和 python 有点像

if a > b{
	print!("hello world\n");
}
if c > d:
	print("hello world")

干脆 大括号也去了得了

for 循环的使用和python3 有点像

let array = [1,2,3,4,5];
for ele in array.iter() {
	print!("ele {}",ele);
}
a = [1,2,3,4,5]
for t in a:
	print(t)

官方说这样的好处在于,不会有访问越界的问题发生

Rust 中的堆栈 和 垃圾回收机制 (GC) Rust 实际上是没有 垃圾回收功能的

截图为敬


String 是根据字符串的大小在堆上开辟一小块内存

毕竟最终都是转化为机器指令的,堆栈是避免不了的rust 中虽然没有垃圾回收机制的,都是默认的释放堆上的资源官方手册上的原话是:内存会自动地在拥有它的变量离开作用域后进行释放看到人家rust 手册。。。 。。。意思就是限制到了作用域中了

C++ 中的析构的模式称为 RAII(Ressource Acquistion Is Initialization)
书中称该设计思想为Rust 设计思想的核心

书中关于变量和数据的交互方式

假如是一个简单的值或者字符(Rust 的 字符是4字节的 支持 unicode 编码,而C++ 和 C 都是 1字节的 utf-8 编码)进行入栈操作的赋值
let x = 5;
let y = x;
意思就是  5 是一个数值,绑定到x 上,  就是发生了入栈了
y = x; y 会 拷贝 x 中的数值,并绑定到自己的身上, 这就和我们平常接触到的语言就一样了
上面的这些是Rust 为了高效的操作 使用了 trait 中的 copy

有一个基本的原则

所有的整数类型,i64 i32 i16 i8 u64 u32 u16 u8所有的字符 char所有的浮点类型 f64 f32布尔类型 bool

在以上的规则中遵循 trait 的 copy 特性

但是String的情况就是在堆上,前面的过程不变,但是为了回收堆上的内存,作了新的决策
以下图为书中的原图,画的很清楚



图4-2

这个过程是必须要清楚的, Rust 中只是浅拷贝
这只是刚刚开始

Rust 的所有权规则

Rust 中的每一个值都有一个对应的变量作为它的所有者 (每个变量的空间就是相对隔离的,其他语言也这样,但是Rust 执行的更加彻底)在同一时间内,值有且仅有一个所有者 (“=” 会发生拷贝)当所有者离开自己的作用域时,他持有的值就会被释放掉(记住前面绑定的概念)

Rust 默认的操作 都是高效的 ,默认即是灵魂

let s1 = String::from("Hello World");
let s2 = s1.clone() ; //这个是操作是深度拷贝的操作

深度拷贝,各自的指针指向各自的空间

Rust 中的元组 和 python 中的 元组 有点类似

let x:(u8,f32,i64) = ('a',3.1415926,-10000);

访问的形式有点怪, 数组的访问形式还是挺好的python3 中的元组结构

所有权和函数

将值传递给函数
进行赋值
将变量传递给函数触发移动或复制

看到了吧, 这就是Rust 的好处, 被移到了函数中,在函数中调用完以后其他地方就无法在使用了

这种返回方式 我都忍不住看看是不是 return 了, 人家就是没有return

返回值与作用域有一个基本原则

将一个值赋值给另一个变量时就会转移所有权,当一个持有堆数据的变量离开作用域时,它的数据就会被drop清理回收,除非又将所有权转到另一个变量上

Rust 的数组初始化 和数组的优化, 且不支持 自增和自减运算符

// 最正常的数组 python的 list 就长这个模样
let a = [1,2,3,4,5];
let b = [3;5]; //初始化 实际上是这个样子 [3,3,3,3,3]
let b: [i32;5] = [1,2,3,4,5]; //数组使用时必须进行初始化 否则 会报错,这点就比较好

红色波浪线就粗误,就是因为没有进行初始化,黄色波浪线就是告警, 访问和数组的访问是一样的

引用与借用

引用的概念和C++ 的引用差不多 (void * const ), 其实指针的存在,有很多的问题, 但是可以解决的问题大于存在的问题

look 这就可以 就继续使用 str_v1 了之前如果要返回必须要在函数中 返回才能用引用的存在是为了不发生移动,转换所有权
书中的示例代码

返回的是参数的所有权,这个所有权就发生了转移,转移到了新的参数上

就是说引用没有转移使用权

借用
但是可变引用就可以解决这个问题

就可以修改了

注意事项

悬垂引用

这就相当nice, C++ C 弱爆了,Rust 不给你犯低级错误的机会

引用规则

切片的概念

一般性代码
这里面有迭代器,中由引用来借用元素哇,return 活久见 只用于提前返回使用上面代码只是为了对比说明 数组切片的意义

字符串切片

剩下的就是重复出错,查看错误现象了

切片存在的意义, 还是为了程序员不犯低级错误python 中的切片, 是为了方便截取字符筛选的, 妈的,这是德国制造吧

所有权的重要性

我理解的是内存空间的所有权,代表谁有权访问这块内存,谁有权修改,一旦离开作用域,在不采取引用的情况下,就会失效mut 是为了告诉可以不可以修改数据总之就是严谨,规避了低级错误引发的大地震

Rust 中的 struct 使用

C 和 C++ 都得 在声明 结构体时都得需要 struct name{ int a}; 加分号Rust 就不需要使用 结构体中的 分号必须先获取结构体的实例才行,才能正常使用结构体符合之前所有的规则

******************************************************** [20%] 分割线

函数中使用 struct

fn build_user(email: String,uername: String)-> User{ //并返回一个结构
	User{
		email: email,
		username: username,
		active:true,
		sign_in_count: 1,
	}
}
可以简化的方法
其他非著名使用方法

可以进少代码量,有点像C++ 的继承的方法, 但是Rust 不是面向对象的


元组式的 struct 访问

Rust 中通过派生 trait 增加使用的功能

这self 更像是 C++ 中的 this指针 和 python 对象中的 self 有异曲同工之妙这其中还不包括 继承和特性的使用,我会把 trait 归到这部分,现在先跳过

Rust 枚举的使用

是为了枚举使用的检测,检测枚举的情况,可以看到枚举的调用信息这是以往C/C++ 都无法达到的

Option 枚举的使用 是为了解决 空值的带来的坏的影响

match 是为了 匹配不同的枚举类型

_ 在match 匹配所有剩下的值 ,_ 在match 中是通配符号

在Rust 中 枚举必须穷举所有的枚举类型 (这是特别需要注意的点)

if let 减少代码量的匹配方法

let if_let = Some(0u32); //还可以进行数值的检测

    if let Some(3u8) = if_let {
        print!("good \n");
    }else{
        print!("Not good\n");
    }
Option<T> 数据类型检测的优势就体现出来了使用Option 时也需要注意数据类型必须一致的问题

Rust 中的 mod 使用,有点像 Class 那样组织数据

pub 可以指定在mod 中 那些时公共拥有的


use 关键字 和 as 关键字 C++ && python3 的用法这样就可以在不同的作用域中进行使用

这样的引入方法就相当的nice 了当然和python 中 一样 也支持相应的通配符号
struct 中 可以使用 new 来创建一个实例,有点像C++ 中的构造函数

接着, 我们 为 Guess 实现 了 一个 关联 函数 new, 用于 创建 新的 Guess 实例 ?。 根据 这个 new 函数 的 定义

trait 共有特性的使用问题,可以共所有的类型使用这种特性,可以在内部,定义成不同的函数进行使用


trait 作为参数 和 语法糖的 使用 方式

需要使用代码进行熟悉 trait 的特性,String,str,vec!,hash_map 常用的和 泛型方法(周六日搞一搞)

******************************************************** [40%] 分割线

trait 特性

 #[derive(Debug)]
   pub struct point<T,Q>{      //这样还是属于自动推导的类型,进行使用,比C++ 的模板方式要更加高效
                            //不至于编译两次,用哪一个生成哪一个,和 Some 的用法是完全一致的
         pub mm: T,
         pub mn: Q
    }

    let mut test_point = point{ //主动告诉编译器的不同的类型选项
        mm: 5i32,
        mn: 23u32
    };
    
    print!("test point {:?}\n",test_point);

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

    pub trait  llp {
        fn llpf(&self) -> String; //这种用法是为了导入相应的结构体中,就是说这种用法没有
    }

    // 其中 trait 可以作为 一种 参数使用
    // 是一种 trait 约束的语法糖 的示例
    // pub fn llob<T:llp>(item: T) 实际上就是这种方式的语法,但是实现了一种语法糖的表现方式
    // pub 关键字也只是让私有变为共有,没有什么特殊的含义,oh yeach,最简单的权限实列

    pub fn llob(item: impl llp){ // 这是一个简单的实现 trait 的特性功能,而且可以作为参数使用
        item.llpf();
    }

    pub trait lloc {
        fn pppy(&self) -> String;
    }

    pub fn llod(ita: impl lloc + llp){
        print!("{} : {}\n",ita.llpf(),ita.pppy()); //可以同时使用两种不同的公共特性接口,
    }

    let test_circle= |x:u32| -> u32 { //这是rust 的闭包,闭包也可以作为一个参数使用
        x+1  
    };

    let gess = test_circle(10);
    print!("gess {}\n",gess); //还可以使用where 语法来解释,使用的trait 特性,减少代码的书写量

    fn some_debug<T,Q>(ita: T,itb: Q)->i32
    where T:lloc,Q:llp
    {
        32       
    }

这种方式,是为了使用trait 接口,对每个结构体都可以是使用这个公共接口,和C++ 的抽象接口,很像,也是形式上很想,意义和原理都不相同

这些是为了简化代码量
适配不同的情况,有适配器的思想和抽象接口的思想在里面

 fn returns_summarizable() -> impl Summary{
        NewsArticle{ // 之前为结构体生已经绑定了一个方法,使用 Summary 的特性,对结构体返回,其实是对trait的返回
            headline: "Oh head".to_string(), //NewArticle 本来就是一个结构体
            location: "Now".to_string(),
            author: "en".to_string(),
            content: "no".to_string()
        }
    }

    let let_test = returns_summarizable();
    print!("let guess {}\n",let_test.summarize());
面向对象编程是为了多态模块化编程也可以实现Rust 也是模块化编程,但是可以实现面向编程的功能,C 的话需要借助lds 连接器脚本 实现

new,为自己分配,在Rust 的智能指针出,会有解释 因为整数中实现了 display 的方法,所以包含了tostring 的特性可以将证书转化为 String 的字符串, String 集成了很多的方法上面的意思是 当且仅当 Display 和 PartialOrd 中的 cmp 和 display 都实现了之后,才会实现 cmp_display( cmp && dispaly)也就是说间接的实现了流程上的判断, 符合什么样的条件触发,cmp_display 的实现这中方法,在代码达到一定的程度时,他的优越性就显现出来了

引用都会有生命周期, 一般情况下都是默认的,随着编译器的优化,对生命周期的标注会越来越少

标注生命周期是为了编译器计算变量的生命周期(了解就好)

终于看到了 static, 说的太多没用,就是为了 在全局使用, 和 C/C++ python 的globa 一个意思,

这个就和前面的 普通变量的赋值 扯上了关系

这个语言真他娘的刺激


当个人 不好吗? 简单点不好吗, 有必要,搞的比C++ 还复杂吗

Rust 的自动话测试

运行cargo test ,就会对代码模块进行测试看看有没有问题, 其他的,这个知道就行了

闭包的类型

******************************************************** [50%] 分割线

Rust 的 闭包 和 trait 也是重点

剩下的50% 学习点

Rust 的高级特性 和 智能指针,就得慢慢来了,还包括安全用法和不安全用法的区别Rust 的 STL 库 的使用Web 服务器的实验项目 >> 这个是熟悉语言的最好方式有哔哩哔哩的视频,我得去看一下,有助于理解Rust 中宏的定义和使用

需要声明引用的生命周期的案例

fn test_alive<'a>(x:&'a i32,y: &'a i32) -> &'a i32{ 
    // 这里存在生命周期的不确定性,所以得显著的表明出来
    //这个周期的声明肯定是由 test_alive 这个函数开始的,以这个函数未结束
    //所以这个生命周期的生命也可以理解
    if x > y {
        x
    }else{
        y
    }
}

Rust & 就是指针

let ptr = &5; //活久见,直接指向了全局区的值,你他娘的,我大C的棺材盖压不住了
let ptr_test:&'static i32 = &7; //这是不是很刺激,其实是一样的道理,只不过省略了

trait trait 闭包 和 trait约束的
trait 的使用

pub trait Human{ //声明一个共有的特性,这个待定,定义公共的特性在里面
//也就是常见的C++ 抽象接口,但是意义不同
//C++ 可以通过抽象基类调用其子类,形成了抽象接口
    fn eat(&self) -> String;
}

pub struct Woman{
    pub name:String,
    pub age:u32
}

impl Human for Woman  {
    fn eat(&self)->String{
        format!("{} eat apple\n",self.name)
    }
}

fn main() {
    let lili = Woman{
        name:String::from("Lidali"),
        age:22,
    };
    print!("{}",lili.eat());
}

struct 的使用

pub trait Human{ //声明一个共有的特性,这个待定,定义公共的特性在里面
    fn eat(&self) -> String;
}

pub struct Woman{
    pub name:String,
    pub age:u32,
    pub sleep_time:u32,
}

impl Human for Woman  {
    fn eat(&self)->String{ //共有特性
        format!("{} eat apple\n",self.name) 
    }
}

impl Woman{ //为我们增加单独的函数使用情况
   pub fn sleep(&self)-> bool{
        if self.sleep_time > 8{
            true
        }else{
            false
        }
    }
}

fn test_trait(it: impl Human){ //trait 作为参数使用,怎么样就是抽象接口,实时证明trait 就是被用作抽象接口
   print!("human eat {}\n",it.eat());
}

fn main() {
    let lili = Woman{
        name:String::from("Lidali"),
        age:22,
        sleep_time:9
    };
    print!("{}",lili.eat());
    print!("lili sleep st: {}\n",lili.sleep());
    test_trait(lili); //运行之后看打印,就知道怎么回事儿了
}

实时证明trait 就是被用作抽象接口, 之前的我理解错了, 在抽象接口的基础上理解,约束的问题,就容易了

trait 就是为抽象接口设计出来的

trait 就是为抽象接口设计出来的

trait 就是为抽象接口设计出来的

pub trait Human{ //声明一个共有的特性,这个待定,定义公共的特性在里面
    fn eat(&self) -> String;
}

pub struct Woman{
    pub name:String,
    pub age:u32,
    pub sleep_time:u32,
}

impl Human for Woman  {
    fn eat(&self)->String{ //共有特性
        format!("{} eat apple\n",self.name) 
    }
}

impl Woman{ //为我们增加单独的函数使用情况
   pub fn sleep(&self)-> bool{
        if self.sleep_time > 8{
            true
        }else{
            false
        }
    }
}

fn test_trait(it:& impl Human){ //trait 作为参数使用,怎么样就是抽象接口
   print!("human eat {}\n",it.eat());
}

fn opt_trait(x: Woman) -> impl Human{ //返回对应的数据结构的特性方法
    x
}

fn main() {
    let lili = Woman{
        name:String::from("Lidali"),
        age:22,
        sleep_time:9
    };
    print!("{}",lili.eat());
    print!("lili sleep st: {}\n",lili.sleep());
    test_trait(&lili); //所有权的问题得注意
    let trait_ues = opt_trait(lili);
    print!("trait opt {} \n",trait_ues.eat());
}

还是之前的代码,但是返回了,业务中,可以广泛的应用

有条件的实现 trait 约束


pub trait one_pub{
    fn echo(&self) -> String;
}

pub trait two_pub{
    fn print(&self) -> String;
}

struct trait_debug{
    name:String,
}

impl one_pub for trait_debug{
    fn echo(&self) -> String{
        "one_pub".to_string()
    }
}

impl two_pub for trait_debug{
    fn print(&self) -> String{
        "two_pub".to_string()
    }
}

struct sercert<T>{
    doit:T,
}

impl<T> sercert<T> {
    fn new(doit:T) -> Self {
        Self{
            doit
        }
    }
}

impl<T:one_pub+two_pub> sercert<T>{
    fn todosomething(&self){
        print!("yeah I am here\n");       
    }
}

fn main(){
    let pre = trait_debug{
        name:String::from("PRE"),
    };

    let one = sercert::new(pre);
    one.todosomething();
}
什么叫做有条件one: 同时有pub_one 和 pub_two 这两个特性的类型才行two: 如果没有,不好意思会直接报错,这就叫做有条件的 通过特性推导类型three: 例子少,但是还是能够说明这个问题所以通过了特性,将数据类型 锁定到了一个范围之内,从而形成了约束, so, 书中给的例子,有说服力应为 Display 是所有的 基础数据类型的 特性,u32 i32 i8 u8 。。。Partiaload 是所有比较大小的数据类型 u32 i32 i8 u8 。。。拿着些就特别有说服力

这就是trait 的约束

再次理解生命周期

struct alive_str<'a>{
    part:&'a str,
}

fn main(){
  let kacha = String::from("baidu.com");
  let first_kacha = kacha.split('.').next().expect("Error");
  let index = alive_str{
      part:first_kacha
  };

  print!("kacha first {}\n",index.part);
}

如果在结构体中 有引用,那么就得显示的说明,声明周期,为什么呢,因为你不知道,到底谁先挂掉,所以要告诉编译器,part 的生命周期和alive_str 一样,但是,只是标注出来,显示告诉编译器,但是无法改变生命周期

还有就是只有引用存在标注生命周期的需求

编译器 目前 使用 了 3 种 规则 来 计算 引用 的 生命 周期 (书中原话)

每一个 引用 参数 都会 拥有 自己的 生命 周期 参数。 换句话说, 单 参数 函数 拥有 一个 生命 周期 参数: fn foo<’ a>( x: &‘a i32); 双 参数 函数 拥有 两个 不同 的 生命 周期 参数: fn foo<’ a, 'b>( x: &'a i32, y: &'b i32); 以此类推当 只 存在 一个 输入 生命 周期 参数 时, 这个 生命 周期 会被 赋予 给 所有 输出 生命 周期 参数, 例如 fn foo<’ a>( x: &'a i32) -> &'a i32。当 拥有 多个 输入 生命 周期 参数, 而 其中 一个 是& self 或& mut self 时, self 的 生命 周期 会被 赋予 给 所有 的 输出 生命 周期 参数。 这条 规则 使 方法 更加 易于 阅读 和 编写, 因为 它 省略 了 一些 不必 要的符号

如果符合这三条就不需要显示的标注生命周期, 默认情况下符合以上,就不需要,标注,如果no,就需要标注,好的结束,nice



到这里就可以完全弄懂 trait 和 生命周期 的意图了

闭包和迭代器

struct CloseCircle<T>
where T:Fn(u32)->u32                                    //闭包的特性约束 FnMut(u32)->u32 FnOnce(u32)->u32 消耗捕获的变量
{
    func:T
}

fn main(){
    let test = |num|{
        print!("num is {}\n",num);                      //这是默认返回的特性
        num + 10
    };

    let o = test(1);
    print!("o {}\n",o);
    
    let cankao = |x| x+10;                              //最简闭包最为使用的功能,显示的使用一个闭包并进行返回,也就是可以当作参数使用
    let cankao_v1 = cankao(10);
    print!("cankao_v1 {}\n",cankao_v1);
    let y = 100;
    let cankao_v2 = |x| x == y;                         //只能在闭包中使用捕获上下文的功能
    print!("cankao_v2: {}\n",cankao_v2(100));

    let close_circle = CloseCircle{
        func:|x:u32| -> u32 {
            x+120
        },
    };

   print!("close_circle test {}\n",(close_circle.func)(130));

   let v1 = vec![1,2,3,4];
   let mut iter = v1.iter();
   iter.next(); iter.next(); iter.next(); iter.next();
   print!("iter {:?}\n",iter);
}
获取枚举变量中的值
let test_enum = Some(&5);
print!("test enum {}\n",test_enum.unwrap());
由此可知这是由 枚举的 共有trait 获取 枚举中的值, 这个理念不错,我喜欢Rust 的编译器 有点烦 还管怎么 命名的,要不然 还告警 … …unwrap() 这个 trait 方法 记住意思very 重要,在以后的编程里面,就是相当nice的玩意

迭代器的消耗问题

let v1 = vec![1,2,3,4];
   let mut iter = v1.iter();
   iter.next();
   iter.next();
   iter.next();
   iter.next();
   
   print!("iter {:?}\n",iter);
   print!("v1 {} 的数组访问 {} \n",0,v1[1]); 

   for it in iter{
    //所谓的消耗了迭代器,就是 指针的位置移动到了终点
    //按照C++,python 的情况下的理解就是这个样子,所以,书中的这个消耗
    //你是说得对,但是,不能这么玩呀
       print!("证明是否消耗了迭代器: {}\n",it);
   }

所谓的消耗了迭代器,就是 指针的位置移动到了终点

现在想一想这个特性的约束还是 十分的巧妙在里面

生成新的迭代器的方法

 let v1 = vec![1,2,3,4];
   let mut iter = v1.iter();
   iter.next(); iter.next(); iter.next(); iter.next();
   print!("iter {:?}\n",iter);
   print!("v1 {} 的数组访问 {} \n",0,v1[1]); 

   for it in iter{
    //所谓的消耗了迭代器,就是 指针的位置移动到了终点
    //按照C++,python 的情况下的理解就是这个样子,所以,书中的这个消耗
    //你是说得对,但是,不能这么玩呀
       print!("证明是否消耗了迭代器: {}\n",it);
   }

   let test_opt = Some(&5);
   print!("Test Opt {}\n",test_opt.unwrap());

   //迭代器是有惰性的,除非使用,否则没有任何的变化
   let v1_it:Vec<_> = v1.iter().map(|x| 2*x).collect(); //只会在迭代器移动是使用数值生成一个新的迭代器,但是不会对原有数据修改
   
   for t in v1{
       print!("update vec t {}\n",t);
   }

迭代器存在惰性

就需要使用,关键是collect 特性,促使迭代器动起来, 和 python 的 yield 的 关键字有异曲同工之妙

iter 中的 filter 的特性 可以更具闭包 选择某些数据,然后在使用 collect 收集器起来,进行返回很实用迭代器中返回的是Option Some(T),所以涉及到了enum 的解包问题 现在想想闭包和 trait ,这俩好棒呀,还有Option 这个枚举真的不错道理上来讲,迭代器要比 loop while for 这些循环 要高效一些

自定义创建一个迭代器

#[derive(Debug)]
struct ItWonderful{
    cnt: u32,
}

impl Iterator for ItWonderful{                          //使用迭代器的特性
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item>{
        self.cnt += 1;
        if self.cnt < 6 {
            Some(self.cnt)
        }else{
            None
        }
    }
}
// 对于一般的u32 i32 u64 i64 都有 copy 的特性

fn main(){  
   let mut dabu = ItWonderful{
        cnt: 0,
   };

   for _it in 0..5{
        dabu.next(); // dabu 会发生变化,
   }
   print!("dabu {:?}\n",dabu);
}

这个next 方法就是相应的迭代器

还得养成一个new的习惯

struct ItWonderful{
    cnt: u32,
}

impl ItWonderful {
    fn new() -> ItWonderful {
     ItWonderful{
         cnt:0
     }
    }
}

就是为了减少代码量

要记住字串符没有 Copy 的 特性,得clone 进行使用

再看 Copy 特性的问题

let  _str = "hello"; 			//栈中的数据区域,无需回收 实际类型为 str 字符串切片
let _str_c = _str; 				//_str_c 获取到了_str 的所有权 "hello的" 其实是字符数组的 Copy 功能
							    //常规赋值发生拷贝
print!("_str {}\n",_str);

let _str = String::from("hello"); //在堆上开辟的字符串,在堆上的话就只有 浅拷贝 ,没有 Copy 特性
let _str_c = _str; // ---- once
let _str_d = _str; // FIXME: 是无法访问的

Box 智能指针的使用场景 (书中原话)

? 当你 拥有 一个 无法 在 编译 时 确定 大小 的 类型, 但又 想要 在 一个 要求 固定 尺寸 的 上下文 环境 中 使用 这个 类型 的 值 时。
? 当你 需要 传递 大量 数据 的 所有权, 但又 不 希望 产生 大量 数据 的 复制 行为 时。
? 当你 希望 拥有 一个 实现 了 指定 trait 的 类型 值, 但又 不关心 具体 的 类型 时

Deref trait 定义 引用的含义,自定义指针,快乐 ~^^~

use std::ops::Deref;

struct auto_ptr<T>(T);

//实现一个智能指针的方法 new
impl<T> auto_ptr<T> { 
    fn new(x:T) -> auto_ptr<T> {
        auto_ptr(x)
    }
}

//为自定义的智能指针添加
//解引用的方法
impl<T> Deref for auto_ptr<T>{  
    type Target = T;
    fn deref(&self) -> &T{
        &self.0
    }
}

// 这是一个字符串切片的表面量
fn main(){  
   let int_ptr = auto_ptr::new(0u32);
   print!("init_ptr {}\n",*int_ptr);
}

书中关于: type Target = T 的解释

type Target = T; 语法 ? 定义 了 Deref trait 的 一个 关联 类型。 关联 类型 是一 种 稍微 有些 不同 的 泛 型 参数 定义 方式

慢慢来以后解释

Rust 函数默认 引用解引用的 隐式转换

Rust 会在 类型 与 trait 满足 下面 3 种 情形 时 执行 解 引用 转换:
? 当 T: Deref< Target= U> 时, 允许& T 转换 为& U。
? 当 T: DerefMut< Target= U> 时, 允许& mut T 转换 为& mut U。
? 当 T: Deref< Target= U> 时, 允许& mut T 转换 为& U。

******************************************************** [60%] 分割线

Rust 强大的告警功能

其实没错误只是,不符合他的命名规则

JetBrains 27 寸战神 名不虚传


补全效果比智能检测比 Vscode 好的多


Rust 元组和数组 的区别

元组不支持迭代,可以自己实现,官方也不建议创建迭代,因为 tuple 支持不同的数据类型,但是满足迭代器的条件,就是每次返回一个相同的 数据类型的,就算是使用Option<T> 这个T 的类型是唯一的,所以无法完成迭代官方如果要使用迭代的话, 建议使用数组,可以在元组中使用多个数组,在对数组迭代,访问

元组和 数组的小用法

    let test = (1, 2, 3, 4); //元组的的使用方法
    let (first, second, third, fourth) = test; //常见的元组解包使用

    let test = [1, 2, 3, 4];
    let [first, second, third, fourth] = test; //常见的数组解包

    let test = [1, 2, 3, 4, 5];

    let mut it = test.iter(); // 迭代器必须是变化的,换句话说是必须是移动的
    let it = it.next();

    print!("Some<T> {:?}\n", it);

    for it in test.iter() {
        // 使用 for 迭代 对 Option<T> Some(T) 解包
        print!("it {}\n", it);
    }

元组和数组的结合访问

fn print_array<'a, T>(x: &'a [T; 5], y: &'a str)
where
    T: std::fmt::Display, //对参数追加trait 约束,为了正常使用{} ,格式化输出
{
    print!("array {}: ", y);
    for it in x.iter() {
        print!("{} ", *it);
    }
    print!("\n");
}

fn main() {
    let array_one: [i32; 5] = [1, 2, 3, 4, 5];

    print_array(&array_one, "one");

    let array_two: [i32; 5] = [7, 8, 9, 0xa, 0xb];

    print_array(&array_two, "two");

    let tuple_mux = (&array_one, &array_two);

    print_array(tuple_mux.0, "tuple first");
    print_array(tuple_mux.1, "tuple two");
}

Result
### Rust 打印小方法, 和 C中的打印有些相同

let test = 0x1234;
print!("0x{:04x}\n",test);

一下截图来自通过例子学Rust


这是在以当前的作用域为有效的声明,和遮蔽有关系,这个这个官方,解释的很官方,加一个mut 一样是可以修改的

看下面所示的代码示例
fn main(){
	let mut _nums = 0x100;
	{
		let mut _nums = _nums;
		_nums = 0x200;
		print!("nums 0x{:03x}\n",_nums);
	}
}


Rust 中的显示转换

这就和 C 中的强制类型转换变的会一致

我感觉写出声明心里踏实点

as 关键字 可以作为 显示声明符合

这个和 typedef 相差不大 这个使用

From Into 特性 用作网络解包,可以整块解包,我还是喜欢C 的 ptr





主要是根据元组的顺序推导出来的

逐步可以解构访问的枚举方式

引用 解引用 解构

fn main() {
    let ref mut _ptr;
    let mut _value = 0x100;
    _ptr = &mut _value;

    print!("ptr dereference is 0x{:04x} \n", *_ptr);
}

这种声明就容易读

Rust 使用熟悉

#[allow(dead_code)]
enum Color<'a> {
    //双元的生命周期
    Red(&'a str, &'a i32),
    Black(&'a str, &'a i32),
}

fn main() {
    #[allow(unused_imports)]
    use Color::{Black, Red};
    //对Color 进行初始化的示例使用

    let user_color = Red("red", &0x2);

    match user_color {
        Red("red", &0x2) => print!("yeach matched \n"),
        _ => print!("yeach nothing matched \n"),
    }
}

卫语句的使用也很使用

这种有利于 在枚举的时候 动态的获取数据类型

这种使用方法很普遍,尤其在逻辑越多的时候,越明显

// `age` 函数,返回一个 `u32` 值。
fn age() -> u32 {
    15
}

fn main() {
    println!("Tell me type of person you are");
    // 这个绑定的语法相当好用

    match age() {
        n @ 1..=10 => println!("this switch is {}\n", n),
        n @ 11..=13 => println!("this switch is {}\n", n),
        n @ 14..=16 => println!("this switch is {}\n", n),
        _ => println!("default switch \n"),
    }
}

减少的逻辑代码很客观

循环标签的使用

fn main() {
    let mut cnt = 0;
    let pl = 'level_1: loop {
        cnt += 101;
        while cnt > 2 {
            if cnt == 100 {
                break 'level_1 cnt;
            }
            cnt -= 1;
        }
    };

    print!("pl value is {}\n", pl);
}

跳出第几层 的循环,不用在一层的一层的跳,咱们直接跳,这就很灵活了,实际开发中,经常会用到


官方中文学习手册
https://rustwiki.org/docs/

Move trait 的 特点

没有 move 的情况

编译器写的很清楚,无法获取 a 的 mutable attr

加上move 之后

move 的作用是将 强制 将 闭包 外的 所有权转移到 闭包内使用,否则默认的情况下,只能捕获借用,不能获取完整的所有权

和C++ 一样的析构函数,手动回收资源,用于自己创作的自定义类型

感冒头痛 … …

Some 解包小技巧

fn main() {
    let mut data = Some(0);
    let mut _use_data = || -> Option<i32> {
        //返回某些数值并使用? 解包,在Option 返回的闭包中
        data = Some(5);
        Some(data? + 10)
    };

    println!("data {} \n", data.unwrap());
}