柚子快报邀请码778899分享:Rust学习笔记
目录
一、开发环境配置
二、VS code 开发插件:rust-analyzer编辑
三、rust项目基本构建
使用cargo 创建rust项目:
构建项目
编译并执行可执行文件
检查代码
发布打包
四、 变量与可变性
变量
常量
shadowing(隐藏)
五、数据类型
标量和复合类型
标量类型:
整数类型
整数字面值
整数溢出
浮点类型
数值操作
布尔类型
字符类型
复合类型
Tuple(元组)
创建Tuple
获取Tuple的元素值
访问Tuple的元素
数组
声明数组
数组的用处
数组的类型
编辑 访问数组的元素
String类型
创建String
更新String
六、函数
函数定义
函数体中的语句与表达式
函数的返回值
七、控制流
if表达式
使用else if处理多重条件
Rust循环
loop循环
while条件循环
for循环
Range
match
if let
八、所有权
什么事所有权
函数调用
所有权规则
变量作用域
借用
九、Slice(切片)
十、结构体
基本结构体:
元组结构体:
空结构体:
定义结构体中的方法
十一、枚举
十二、Rust模块系统
Crate
Package
Cargo的惯例
Module
私有边界
十三、各种关键字
pub关键字
super关键字
use关键字
as关键字
十四、使用外部包(package)
十五、常用的集合
Vector
十六、HashMap,v>
创建HashMap
方法一:new()函数
方法二:collect方法
HashMap和所有权
访问HashMap中的值
遍历HashMap
更新HashMap,v>
替换现有的Value
基于现有的V来更新V
Hash函数
十七、Rust错误处理概述
panic时可展开(默认)或中止调用栈
使用panic!产生的回溯信息
Result枚举处理错误
匹配不同的错误
unWrap:match表达式的一种快捷方法
expect:unWrap的补充
传播错误
十八、泛型
函数定义中的泛型
Struct定义中的泛型
Enum定义中的泛型
方法定义中的泛型
十九、Trait
这是我观看杨旭老师的Rust教程的笔记,笔记中省略了一些细节部分,如有不理解的请大家观看杨旭老师的视频。
视频链接:杨旭老师B站的视频
Rust官网:Rust 语言 中文网 官方网站
《Rust 程序设计语言》简体中文版:Rust 程序设计语言 - Rust 程序设计语言 简体中文版
一、开发环境配置
安装rust管理工具链
下载链接: https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe
安装步骤: 运行安装的exe文件:
因为我用的是gcc编译器,所以要先选择自定义安装进行设置
设置完成后还会有一些其他配置如果没有特殊需求直接回车就行(默认选择)
然后在选择1进行安装
安装完成后可进行版本配置查看
rustc --version
rustc是对程序较小的时候进行调试的,如果是项目的调试我们需要用到cargo
安装完rustc也会自动安装cargo,也可以查看cargo相关版本信息
cargo --version
PS:至此rust环境配置完成
二、VS code 开发插件:rust-analyzer
三、rust项目基本构建
使用cargo 创建rust项目:
cargo new 项目名称
会生成如下结构:(附说明)
其中 Cargo.toml是项目配置信息,类似于json
PS: 在Rust里面,代码包称为crate
构建项目
cargo build
PS1:构建项目后会在当前目录下生成可执行exe文件
PS2:第一次运行cargo build 会在顶层目录下生成cargo.lock文件
该文件负责追踪项目依赖的精确版本不需要手动修改该文件
编译并执行可执行文件
cargo run
检查代码
cargo check
PS:不会产生执行文件
PS:cargo check 比 cargo build 快得多
发布打包
cargo build --release
PS:编译时会进行优化(代码会运行更快,但编译时间更长)
PS:会在target/release下生成可执行文件而不是 target/debug
四、 变量与可变性
变量
声明变量使用let关键字默认情况下,变量是不可变的(需要在变量前面加mut)
注意:以上变量的修改会报出如下警告(目前未找到合适的解决办法)
常量
可以在main函数外声明也可以在main函数内声明常量在绑定值以后也是不可变的,但是它与不可变的变量有很多区别
注意:
不可以使用mut,常量永远不可变声明const,它的类型必须标注常量可以在任何作用域内进行声明,包括全局作用域常量只可以绑定常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值在程序运行期间,常量在其声明的作用域内一直有效命名规范:Rust里常量使用全大写字母,每个单词之间用下划线分开 例如:const MAX_COUNT: u32 = 100_000;
分别输出结果
shadowing(隐藏)
可以使用相同的名字声明新的变量,新的变量就会shadow(隐藏)之前声明的同名变量
注意:在后续的代码中这个变量名代表就是新的变量
五、数据类型
标量和复合类型
Rust是静态编译语言,在编译时必须知道所有变量的类型基于使用的值,编译器通常能够推断出它的具体类型但是如可能的类型比较多(例如把String转为整数的parse方法),就必须添加类型的标注,否则编译会报错
标量类型:
一个标量类型代表一个单个的值Rust有四个主要的标量类型:整数类型、浮点类型、布尔类型、字符类型
整数类型
整数类型没有小数部分例如u32就是一个无符号的整数类型,占据32位的空间无符号整数类型以u开头有符号整数类型以i开头
Rust的整数类型列表
所占位数有符号整数无符号整数8字节i8u816字节i16u1632字节i32u3264字节i64u64128字节i128u128archisizeusize每种都分i和u,以及固定的位数有符号范围:到 无符号范围: 0到isize和usize类型 isize和usize类型的位数由程序运行的计算机的架构所决定: 例如:如果是64位计算机,则isize就是i64,usize是u64 使用isize或usize的主要场景是对某种集合进行索引操作
整数字面值
字面值说明Decimal(十进制)_222Hex(十六进制)0xffOctal(八进制)0o77Binary(二进制)0b1111_0000Byte(u8 only)b'A'
注意:
除了byte类型外,所有的数值字面值都允许使用类型后缀。 例如 57u8如果不清楚使用哪些类型,可以使用Rust默认类型i32
整数溢出
假设操作:u8的范围是0-255,这时却把一个u8变量的值设为256
调试模式下编译:Rust会检查出整数溢出,如果发生溢出,程序在运行时就会panic发布模式下编译(加--release):Rust不会检查可能导致panic的整数溢出 如果溢出则会执行“环绕”操作:256变成0,257变成1,258变成2......
浮点类型
Rust有两种基础的浮点类型,也就是含有小数部分的类型 f32,32位,单精度 f64,64位,双精度Rust的浮点类型使用了IEEE-754标准来表述f64是默认类型
数值操作
运行符号说明+加-减*乘/除%取余
布尔类型
Rust的布尔类型也有两个true和false占一个字节大小
字符类型
Rust语言中char类型被用来描述语言中最基础的单个字符字符类型的字面值使用单引号占用4字节大小是Unicode标量,可以表示比ASCll多的拼音、中日韩文、零长度空白字符、emoji表情 范围:U+0000 ~ U+D7FF、U+E000 ~ U+10FFFF
复合类型
复合类型可以将多个值放在一个类型里Rust提供了两种基础的复合类型:元组(Tuple)、数组
Tuple(元组)
Tuple可以将多个类型的多个值放在一个类型里Tuple的长度是固定的:一旦声明就无法改变
创建Tuple
在小括号里,将值用逗号分开 Tuple中的每个位置都对应一个类型,Tuple中各元素的类型不必相同
获取Tuple的元素值
可以使用模式匹配来解构一个Tuple来获取元素的值
访问Tuple的元素
在tuple变量使用点标记,后接元素的索引号
数组
数组也可以将多个值放在一个类型里数组中每个元素的类型必须相同数组的长度也是固定的
声明数组
数组的用处
如果想让你的数据存放在栈上而不是堆上,或者想保证有固定数量的元素,这时使用数组更有好处数组没有Vector灵活 Vector和数组类型,它由标准库提供 Vector的长度可以改变 如果你不确定应该用数组还是Vector,那么估计你应该用Vector
例如:月份是固定的所有可以使用数组
数组的类型
数组的类型以这种形式表示:[类型;长度]
另一种声明数组的方法:如果数组的每个元素的值都相同,那么可以在中括号里指定初始值,然后是一个“;”,最后是数组长度
访问数组的元素
数组是栈上分配的单个块内存可以使用索引来访问数组的元素如果访问的索引超出了数组的范围,那么编译会通过,但是运行会报错(runtime时会panic)
String类型
一个String由3部分组成 ptr:指向存放字符串内容的内存指针 len:长度(字符串的字节数) capacity:容量长度和容量存放在stack上ptr存放在heap上
创建String
可以使用from函数从字符串字面值创建出String类型使用String::new()使用to_String()
fn main() {
// 创建String
let mut s = String::new();
// 使用to_string
let s1 = "hello";
println!("s1:{}", s1.to_string());
// 使用from函数创建字符串
let s = String::from("hello");
}
更新String
push_str()方法:把一个字符串切片附加到string
// 使用from函数创建字符串
let mut s2 = String::from("hello");
s2.push_str(" world");
println!("s2:{}", s2);
push()方法:把单个字符附加到String
fn main() {
// 创建String
let mut s = String::new();
// 使用to_string
let s1 = "hello";
println!("s1:{}", s1.to_string());
// 使用from函数创建字符串
let mut s2 = String::from("hello");
s2.push_str(" world");
// 把单个字符附加到String
s2.push('!');
println!("s2:{}", s2);
}
遍历String的方法
对于标量值:chars()方法对于字节:bytes()方法对于字形簇:很复杂,标准库未提供
六、函数
函数定义
声明函数使用fn关键字依照惯例,针对函数和变量名,Rust使用snake case命名规范: 所有的字母都是小写的,单词之间使用下划线分开
函数的参数
parameters(形参),arguments(实参)在函数签名里,必须声明每个参数的类型
函数体中的语句与表达式
函数体是由一系列语句组成,可选的由一个表达式结束Rust是一个基于表达式的语言语句是执行一些动作的指令表达式会计算产生一个值函数的定义也式语句
语句不返回值,所以不可用使用let将一个语句赋给一个变量
函数的返回值
在->符号后边声明函数返回值的类型,但是不可用为返回值命名在Rust里面,返回值就是函数体里面最后一个表达式的值若想提前返回,需要使用return关键字,并指定一个值
七、控制流
if表达式
使用else if处理多重条件
如果使用了多于一个else if,那么最好使用match来重构代码
Rust循环
Rust提供了3中循环:loop,while和for
loop循环
loop关键字告诉Rust反复的执行一块代码,直到叫停可以在loop循环中使用break关键字来告诉程序何时停止循环
while条件循环
每次执行循环体之前都要判断一次条件
for循环
可以使用while或loop来遍历集合,但是易错且低效使用for循环更简洁紧凑,它可以针对集合中的每个元素来执行一些代码
Range
标准库提供指定一个开始数字和一个结束数字,Range可以生成它们之间的数字(不含结束)rev方法可以反转Range
match
允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码 模式可以是字面值、变量名、通配符...match匹配必须穷举所有的可能(可以使用“_”通配符替代其余没有列出的值)
if let
处理只关心一种匹配而忽略其它匹配的情况不需要穷举所有可能可以把if let 看作match的语法糖
八、所有权
所有权是Rust最独特的特性,它让Rust无须GC就可以保证内存安全
什么事所有权
Rust的核心特性就是所有权所有程序在运行时都必须管理它们使用计算机内存的方式 有些语言有垃圾收集机制,在程序运行时,它们会不断地寻找不再使用的内存 在其他语言中,程序员必须显式地分配和释放内存Rust采用了第三种方式 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则 当程序运行时,所有权特性不会减慢程序的运行速度
函数调用
当你的代码调用函数时,值被传入到函数(也包括指向heap的指针)。函数本地的变量被压到stack上。当函数结束后,这些值会从stack上弹出
所有权规则
每个值都有一个变量,这个变量是该值的所有者每个值同时只能有一个所有者
变量作用域
Scope就是程序中一个项目的有效范围
借用
一个函数中的变量传递给另外一个函数作为参数暂时使用,函数参数离开自己的作用域的时候将所有权还给当初递给它的变量
九、Slice(切片)
let 切片变量 = &变量[起始位置..结束位置]起始位置最小是0结束位置是数组、向量、字符串的长度
十、结构体
基本结构体:
元组结构体:
空结构体:
注意: 一旦struct的实例是可变的,那么实例中所有的字段都是可变的
定义结构体中的方法
在impl块里定义方法方法的第一个参数可以是&self,也可以获得其所有权或可变借用。和其他参数一样
十一、枚举
枚举允许我们列举所有可能的值来定义一个类型
十二、Rust模块系统
Package(包):Cargo特性,让你构建、测试、共享crateCrate(单元包):一个模块树,它可产生一个library或可执行文件Module(模块):让你控制的代码组织、作用域、私有路径
Crate
Crate的类型:binary、libraryCrate Root:这是源码文件、Rust编译器从这里开始,组成Crate的根Module
Package
包含1个Cargo.toml,它描述了如何构建这些Crates只能包含0-1个library crate可以包含任意数量的binary crate但必须至少包含一个crate(library或binary)
Cargo的惯例
src/main.rs:binary crate 的crate root,crate名与package名相同src/lib.rs:package包含一个library crate,library crate的crate root,crate名与package名相同Cargo 会把crate root 文件交给rustc来构建library或binary
Module
在一个crate内,将代码进行分组增加可读性,易于复用控制项目的私有性。public、private
建立module:在src/lib.rs
module树:
crate |--front_of_house |--hosting | |--add_to_waitlist | |--seat_at_table |--serving |--take_order |--serve_order |--take_payment
私有边界
模块不仅可以组织代码,还可以定义有边界如果想把函数或struct等设为私有,可以将它放到某个模块中Rust中所有条目默认是私有的父级模块无法访问子模块中的私有条目子模块里可以使用所有祖先模块中的条目使用pub关键字可以将某些条目标记为公共的
十三、各种关键字
pub关键字
使用pub关键字可以将某些条目标记为公共的
super关键字
用来访问父级模块路径中的内容,类似文件系统中的“..”
use关键字
可以使用use关键字将路径导入到作用域内(仍遵循私有性规则)
as关键字
as关键字可以引入的路径指定本地的别名
十四、使用外部包(package)
Cargo.toml添加依赖的包(package)use将特定条目引入作用域
十五、常用的集合
Vector
由标准库提供可存储多个值只能存储相同类型的数据值在内存中连续存放
fn main() {
// 创建Vector
let v:Vec
// 使用vec!宏初始化Vector
let mut v1 = vec![1,2,3,4,5];
// 添加元素
v1.push(6);
// 通过索引获取元素
let value1 = v1[0];
println!("value1:{:?}",value1);
// 通过get方法获取元素
let value2 = v1.get(0).unwrap();
// 通过索引修改元素
v1[0] = 100;
// 通过索引删除元素
v1.remove(0);
// 通过索引插入元素
v1.insert(0,1000);
println!("v1:{:?}",v1);
// 索引 vs get处理访问越界
// 索引会返回panic
// get返回None
}
十六、HashMap
键值对的形式存储数据,一个键对应一个值Hash函数:决定如何在内存中存放K和V数据存储在heap(堆)上HashMap不再Prelude(预编译库)中
创建HashMap
方法一:new()函数
创建空HashMap:new()函数添加数据:insert()方法
// 首先要导入HashMap
use std::collections::HashMap;
fn main() {
// 创建HashMap
// 如果不指明数据类型则会报错
let mut scores:HashMap
// 或者直接初始化赋值,利用Rust的类型推导
scores.insert(String::from("tom"), 100);
}
方法二:collect方法
在元素类型为Tuple的Vector上使用collect方法,可以组建一个HashMap:要求Tuple有两个值:一个作为K,一个作为Vcollect方法可以把数据整合成很多种集合类型,包括HashMap(返回值需要显示指明类型)
fn main() {
let teams = vec![String::from("Blue"),String::from("Yellow")];
let intial_score = vec![10,50];
// 使用下划线,HashMap会自动推导
let scores:HashMap<_,_> = teams.iter().zip(intial_score.iter()).collect();
}
HashMap和所有权
对于实现了Copy trait的类型,值会被复制到HashMap中对于拥有所有权的值,值会被移动,所有权会转移给HashMap 如果将值的引用插入到HashMap,值本身不会移动
use std::collections::HashMap;
fn main() {
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// 因为String类型的所有权已经发生改变所以无法使用field_value和field_name了
println!("{}:{}",field_name,field_value);//---->error
// 如果插入的是引用则可以继续使用
map.insert(&field_name, &field_value);
println!("{}:{}",field_name,field_value) //----->OK
}
访问HashMap中的值
get方法(参数:Key,返回:Option<&V>)
use std::collections::HashMap;
fn main() {
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(&field_name, 10);
// 获取HashMap中的值
let score = map.get(&field_name);
match score {
Some(s) => println!("{}",s),
None => println!("None"),
}
}
遍历HashMap
for循环
// 遍历HashMap
for (key,v) in &map {
println!("{}:{}",key,v);
}
更新HashMap
HashMap大小可变每个K同时只能对应一个VKey存在,对应一个Value:替换现有的Value,保留现有的Value,忽略新的Value,合并现有的Value和新的ValueK不存在:添加一对Key,Value
替换现有的Value
如果向HashMap插入一对key,value,然后再插入同样的key,但是不同value,那么原来的value会被替换掉
// 替换现有的Value
let mut scores= HashMap::new();
scores.insert(String::from("Alice"), 10);
scores.insert(String::from("Alice"), 25);
println!("{:?}",scores);
只在Key不对应任何值的情况下,才插入Value
let mut scores = HashMap::new();
scores.insert(String::from("Bob"), 10);
// entry方法:检查指定的K是否对应一个V,参数为K,返回一个enum Entry代表值是否存在
let e = scores.entry(String::from("Bob"));
println!("{:?}",e);
// Entry的or_insert方法:如果K不存在则插入V,否则返回V
e.or_insert(50);
scores.entry(String::from("Bob")).or_insert(50);
println!("{:?}",scores);
基于现有的V来更新V
let text = "hello world wonderful world";
let mut map = HashMap::new();
// split_whitespace方法将字符串以空格分割成一个Vec
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:#?}",map);
Hash函数
默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(DoS)攻击。不是可用的最快的Hash算法,但是具有更好的安全性可以指定不同的hasher来切换到另一个函数,hasher是实现BuildHasher trait的类型
十七、Rust错误处理概述
panic时可展开(默认)或中止调用栈
默认情况下,panic发生程序展开调用栈(工作量大),Rust沿着调用栈往回走,清理每个遇到的函数中的数据,或立即终止调用栈,不进行清理,直接停止程序,内存需要OS进行清理将panic设置从展开设置为中止:在Cargo.toml中的profile设置panic=‘abort’
#cargo.toml
# 在生产环境中如果发生panic,那么就中止执行,然后让OS来清理内存
[profile.release]
panic = 'abort
使用panic!产生的回溯信息
panic!可能出现在我们写的代码中或我们所依赖的代码中通过设置环境变量Rust_BACKTRACE可得到回溯信息
具体流程:
在mian.rs中编写如下代码
fn main() {
let v = vec![1, 2, 3, 4, 5];
v[99];
}
运行后会产生如下提示panic
根据提示获取panic产生的回溯信息
注意:一定要在cmd终端命令下而不是powershell
use std::fs::File;
fn main(){
let f = File::open("foo.txt");
let f = match f {
Ok(file)=>file,
Err(error)=>{
panic!("{:?}",error);
}
};
}
Result枚举处理错误
基本用法
fn main() {
enum Result
OK(T), //操作成功的情况下,OK变体里返回的数据类型
Err(E), //操作失败的情况下,Err变体里返回的数据类型
}
}
用match表达式处理Result:和Option枚举一样,Result及其变体也是由prelude带入作用域
use std::fs::File;
fn main(){
let f = File::open("foo.txt");
let f = match f {
Ok(file)=>file,
Err(error)=>{
panic!("{:?}",error);
}
};
}
错误信息
匹配不同的错误
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("{:?}",e),
},
other_error => panic!("{:?}",other_error),
},
};
}
unWrap:match表达式的一种快捷方法
fn main() {
// 正常的match方法
use std::{error, fs::File};
let f = File::open("hello.txt");
let f = match f {
Ok(file)=>file,
Err(error)=>{
panic!("{:?}",error)
}
};
// unwrap方法:等同于上面的match
let f = File::open("hello.txt").unwrap();
}
expect:unWrap的补充
unWrap无法自定义错误信息,expect可以指定错误信息
let f = File::open("hello.txt").expect("该文件无法打开");
传播错误
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(error) => return Err(error),
};
// 如果File::open操作成功的话,就创建一个string用于获取文件内容
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),//如果操作成功则s是match的返回值,而match的返回值就是这个函数的返回值,所以s就是这个函数的返回值
// 如果操作失败则返回从上面传递的错误信息
Err(error) => Err(error),
}
}
fn main() {
let Result= read_username_from_file();
}
“?”运算符作为传播错误的快捷方式
fn read_username_from_file() -> Result
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(error) => return Err(error),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s)
Err(error) => Err(error),
}
}
//等价于“?”运算符
fn read_username_from_file() -> Result
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
}
?与from函数
Trait std::convert::From上的from函数:用于错误之间的转换 被?所应用的错误,会隐式的被from函数处理当?调用from函数时:它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型from函数用于针对不同错误原因,返回同一种错误类型,只要每个错误类型实现了转换为所返回的错误类型的from函数?运算符只能用于返回Result的函数
?运算符链式调用
fn read_username_from_file() -> Result
let mut s = String::new();
let mut f = File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
?运算符与main函数
main函数返回类型是()main函数的返回值类型也可以是Result
use std::fs::File;
use std::error::Error;
fn main()->Result<(),Box
let f = File::open("hello.txt")?;
Ok(())
}
十八、泛型
提高代码复用能力处理重复代码问题泛型是具体类型或其他属性的抽象代替:你编写的代码不是最终的代码,而是一种模版,里面有一些“占位符”,编译器在编译时将“占位符”替换为具体的类型类型参数:T
fn largest
函数定义中的泛型
泛型函数:参数类型,返回类型
fn largest
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
Struct定义中的泛型
可以使用多个泛型的类型参数(太多类型参数:你的代码需要重组为多个更小的单元)
struct Point
x: T,
y: T
}
fn main() {
let integer = Point{x: 5, y: 10};
let float = Point{x: 1.0, y: 4.0};
}
Enum定义中的泛型
可以让枚举的变体持有泛型数据类型:Option
enum Option
Some(T),
None,
}
enum Result
Ok(T),
Err(E),
}
fn main() {
}
方法定义中的泛型
为struct或enum实现方法的时候,可在定义中使用泛型注意: 把T放在impl关键字后,表示在类型T上实现方法:impl
struct Point
x: T,
y: U,
}
impl
fn mixup(V,M)(self,other:Point
Point{
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point{x: 5, y: 10};
let p2 = Point{x: "Hello", y: 'c'};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
十九、Trait
Trait告诉Rust编译器:某种类型具体有哪些并且可以与其他类型共享的功能Trait:抽象的定义为共享行为Trait bounds(约束):泛型类型参数指定为实现了特定行为的类型
定义Trait
定义:把方法签名放在一起,来定义实现某种目的的所必需的一组行为关键字:trait只有方法签名,没有具体实现trait可以有多个方法:每个方法签名占一行,以“;”结尾实现trait的类型必须提供具体的方法实现 pub trait Summary {
// 方法的签名也就是行为,可以有多个
fn summarize(&self) -> String;
fn summarize1(&self) -> String;
}
fn main() {} 在类型上实现trait 与类型实现方法类似
在lib.rs里定义trait
//lib.rs
// 定义一个trait
pub trait Summary {
// 方法的签名也就是行为,可以有多个
fn summarize(&self) -> String;
}
pub struct NewsArticle{
pub headline:String,
pub author:String,
pub location:String,
pub content:String,
}
// 实现该NewsArticle的trait
impl Summary for NewsArticle{
fn summarize(&self) -> String{
format!("{}, by {} ({})",self.headline,self.author,self.location)
}
}
// 推特的推文
pub struct Tweet{
pub username:String,
pub content:String,
pub reply:bool,
pub retweet:bool,
}
// 实现该Tweet的trait
impl Summary for Tweet{
fn summarize(&self) -> String{
format!("{}, {}",self.username,self.content)
}
}
在main.rs中调用
use demo24::Tweet;
use demo24::Summary; //必须有,否则会报错summarize,因为summarize只有在Summary所在的当前作用域下才能使用,且Summary必须是pub公共的
fn main() {
let tweet = Tweet{
username: String::from("tweet"),
content: String::from("tweet content"),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
实现trait的约束
可以在某个类型上实现某个trait的前提条件是这个类型或这个trait是在本地crate里定义的无法为外部类型来实现外部的trait(一致性,孤儿规则),此约束确保其他人的代码不能破坏您的代码,反之亦然,如果没有这个约束,两个crate可以为同一类型实现同一个trait,Rust就不知道应该使用哪个实现了
默认实现
默认实现的方法可以调用trait中其它的方法,及时这些方法没有默认实现。
注意:无法从方法的重写实现里面调用默认的实现
还是上面的例子
// 定义一个trait
pub trait Summary {
// 方法的签名也就是行为,可以有多个
//设置了一个默认实现
fn summarize(&self) -> String{
String::from("(Read more...)")
}
}
pub struct NewsArticle{
pub headline:String,
pub author:String,
pub location:String,
pub content:String,
}
// 去掉了NewsArticle具体的实现,变成空实现
impl Summary for NewsArticle{
}
// 推特的推文
pub struct Tweet{
pub username:String,
pub content:String,
pub reply:bool,
pub retweet:bool,
}
// 实现该Tweet的trait
//而Tweet的实现则会对默认实现进行重写
impl Summary for Tweet{
fn summarize(&self) -> String{
format!("{}, {}",self.username,self.content)
}
}
Trait作为参数
第一种方法:使用impl Trait语法(Trait bound语法的语法糖)
// item代表传入的参数实时实现Summary的具体实现,在上面的例子中是Tweet或NewsArticle
pub fn notify(item:impl Summary){
println!("{}",item.summarize());
}
第二种方法:使用Trait bound语法
pub fn notify
println!("{}",item.summarize());
}
指定多个Trait bound
第一种方法:使用+指定
use std::fmt::Display;
// impl Trait语法
pub fn notify1(item:impl Summary+Display){
println!("{}",item.summarize());
}
// Trait bound语法
pub fn notify
println!("{}",item.summarize());
}
第二种方法:使用where子句(在方法签名后指定where子句)
pub fn notify2
where
T:Summary+Display,
U:Clone+Debug,
{
format!("Breaking news! {}",a.summarize())
}
实现Trait作为返回类型
impl Trait语法
注意:impl Trait 只能返回确定的同一种类型,返回可能不同类型的代码会报错
// 实现Trait作为返回类型
pub fn notify3(flag: bool) -> impl Summary {
if flag {
NewsArticle {
headline: String::from("Penguins win again"),
content: String::from("The penguin team scored again in a contest against the sharks"),
author: String::from("Iceburgh"),
location: String::from("New York"),
}
}else {
// 由于这个函数返回的类型不止一个,因此会报错
Tweet{
username: String::from("Penguin1"),
content: String::from("Penguin1: I am a penguin"),
reply: false,
retweet: false
}
}
}
Trait Bound方法
// 如果导入的是数字vec,则要+Copy
// 如果是str类型的vex,因为String存储在堆内存上所以要+Clone
fn largest
let mut largest = list[0].clone();//并且如果传入的是str类型,对于获取也要加Clone
for item in list.iter(){
if item > &largest{ //>符号对应的方法std::cmp::PartialOrd(存在预导入模块中)
largest = item.clone();
}
}
largest
}
fn main() {
// let number_list = vec![1,2,3,4,5];
// let result = largest(&number_list);
// println!("largest number is {}", result);
let str_list = vec![String::from("hello"),String::from("world")];
let result = largest(&str_list);
println!("largest number is {}", result);
}
使用trait Bound有条件的实现方法
在使用泛型类型参数的impl块上使用Trait bound,我们可以有条件的为实现了特定Trait的类型来实现方法
柚子快报邀请码778899分享:Rust学习笔记
文章链接
大家都在找:
rust:rust语言
笔记:笔记本
发表评论