- 变量的可变性:let x不可重新赋值,let mut x 可重新赋值,const x常量。
- 隐藏:let x = 5
{
Let x= 6
} 此变量作用域中x的值是六,此变量作用域外x的值是5.
- 数据类型:静态数据类型,一般情况下编译器在编译时期推断类型。
- 元组:let out(i32,f64,u8) = (500,6.4,1), 元组的解构 let (x,y,z) = out, 元组的索引 let a = out.0 let b = out.1 let c = out.2。
- 数组:let a = [1,2,3,4,5] let a[i32;5] = [1,2,3,4,5](i32数组类型,5数组长度) let a[3;5](3数组初值,5数组长度)
- 数组的访问:同c++
- 函数:以fn开头。fn function(){}
- 函数参数:fn function(x:i32,y:string){}
- 语句和表达式:语句执行一些操作不返回值,表达式计算并返回一个值。表达式是语句的一部分。Let y = 6是语句,6是表达式。{}是一个表达式。
- 具有返回值的函数:fn fuction() ->i32 { 5 } 。fn function() ->i32 {x+1;}错误x+1;是语句。X+1才是表达式。
- If表达式:let a = 1; if a==0 {} else if a==1 {} else {}。 If中的条件表达式必须返回布尔值。If表达式可以返回一个值。
- loop循环:loop { printf!(“aaa”);} ,跳出循环 loop { break 1 } 。loop表达式可以返回一个值。
- While循环: let a=0 while a==0 {printf!(“aaa”);} 类似c++。While循环可否返回一个值?
- For循环:let a[3;5]; for element in a.iter() {printf!(“{}”,a[element]);}
- 所有权和移动语义:所有权概念是为了妥善管理堆内存而设计的。当赋值或传参或函数返回对象时,一个对象中的堆内存地址被拷贝到另一个对象中,前一个对象被禁止使用堆内存,这个是移动语义。
- Drop:对象离开栈时具有堆内存所有权的对象会被编译器调用drop,用以释放其中的堆内存。
- 引用与所有权:引用是指向栈内变量的地址,所以没有所有权。
- 借用:获取引用当作函数参数称为借用。
- 引用的可变性:let a = 1; let b = &mut a;因为变量a不可变,所以指向a的引用b也不可变,不管b是不是可变引用。改为let mut a = 1; let b = &mut a;后b也可变了。
- 引用限制:在一个变量的可变引用的作用域内不可以在定义这个变量的引用。在一个变量的不可变引用作用域内不可以定义可变引用,但是可以定义多个该变量的不可变引用。
- 没有所有权的Slice:一个变量中有一组值在堆内存。Slice就是这一组堆内存子集的不可变引用。Let s = string::from(“hello word); let hello = &s[0..5];/let hello = &s[..5];。Let word=&s[6..11]/let word=&s[6..];
- 结构体和元组的区别:结构体需要命名在其内部的所有元素。结构体定义一种类型,每一个结构体定义种个类型,元组的类型就是元组。
- 结构体:结构体的可变性要整体定义,而不是定义每一个字段
Struct user{name:string,id:i32};
let ltj = user{name:string::from”ltj”,id:110111198909238237};
let mut ltj = user{name:string::from”ltj”,id:110111198909238237};
ltj.id=0;
- 元组结构体:用定义元组的方式定义结构体struct user(string,i32) let ltj=user(string::from(“ltj”),0);
- 单元结构体:没有任何字段的结构体,用于在某个类型上实现trait而不需要存储数据。
- 结构体的所有权:结构体的字段可以是有所有权的变量,也可以是没有所有权的引用,但是引用字段要声明其生命周期,以便于编译器管理。
- 方法语法:方法定义在结构体的上下文中,第一个参数为self,这个参数不需要传参,默认传入方法所在的结构体。这个参数和以获取结构体的所有权,也可以获取结构体的可变或不可变引用。
- 关联函数:impl块中定义的不含self参数的函数。String::from。
- 多个impl块:struct user{name:string,id:i32} impl user{ fn getName()->string{name} } impl user{fn getId()->i32{id}}。结构体的函数可以分散在多个结构体impl块中。
- 枚举:enmu user{man,women}
- 枚举值:let ltj = user::man let yy = user::women; struct computer{ owner:user }
- 枚举中放入数据:emum ipadress{ v4(u8,u8,u8,u8), v6(string)}
- 枚举和结构的区别:枚举中的每一个字段都类似于一个结构体,但是他们是在一个统一的类型之下的。如果把枚举的字段都单独定义成结构体,那么就会有种类型。
- 枚举中的方法:和结构体相同。
- Option<T>:
enum Option<T> {
Some(T),
None,
} 当某个变量可能为空值时,可以引入此枚举。
- Match模式匹配:enum user{man,woman} let ltj = user::man match ltj { user::man => 0, user::woman=>1},match类似于c++switch。
- Match使用枚举值:enum user{man(i32),woman(i32)} let ltj = user::man(35) match ltj { user::man(old) => old, user::woman(old)=>old}
- 匹配option<T>:let old = Some(5); match old { Some(old)=>old,None=>None};
- 穷尽匹配:rust编译器保证,match的所有分支匹配包含了枚举的所有值。否则编译器会报错。
- “_”通配符:如果match中不想匹配枚举的所有值,可以使用”_”,用来匹配剩余没有写到代码里的枚举值。Emum class {one,tow,three,four,five} let room = class::one mathc room { class::one=> 1, _ => 0 };
- Let if简写match:当match中的匹配只关注某些特定值时,let if 可以简化match的代码。 Emum class {one,tow,three,four,five} let room = class::one let if class::one = room {1} else {0}
- 在函数中使用泛型:fn returnSelf <T> (x:T)->T{ x }
- 在结构体中使用泛型:struct user<T,Y>{name:T,id:Y}
- 枚举中定义泛型:enum ipAdress <T,Y> { v4(T),v6(Y)}
- 方法中定义泛型:
enum ipAdress <T> { v4(T),v6(T)}
impl <T> ipAdress<T>{
fn getAdress<T>(&Self)->T{
match Self {
ipAdress::v4(adr)=>adr,
ipAdress::v6(adr)=>adr
}
}
}
- 在泛型结构或枚举中为特定类型定义函数:
enum ipAdress <T> { v4(T),v6(T)}
impl ipAdress<string>{
fn clearAdr(&mut Self) { v4.clear() }
}只有当ipadress 中T是string类型的时候,ipadress才能使用这个函数。
- 泛型的声明:泛型是指在尖括号中声明的字母,这个字母泛指各种类型,不同的字母说明两个类型不相同。泛型可以在结构体和impl和函数体中声明。代码中是可以混用这些泛型的。例如:
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
- Trait:共享的行为合集。可以实现默认行为也可以不实现。
pub trait voice { fn call(&Self)->string;}
pub struct dark { callVoice:string }
impl voice for dark {
fn call(&Self)->string{
Self.callVoice
}
}
- Trait作用域和孤儿规则:跨包使用trait需要满足两个条件,一是要引用trait所在的作用域,二是trait必须是共有的。trait孤儿规则是,可以为本地类型实现外部trait,也可以为外部类型实现本地trait,但是不能为外部类型实现外部trait。孤儿规则的目的是,让代码有序,不会重复实现同一个类型的同一个trait。
- Trait默认行为:
pub trait voice { fn theVoice(&Self)->string;
fn call(&Self)->string{
Self.theVoice()
}
}
pub struct dark { callVoice:string }
impl voice for dark {
fn theVoice(&Self)->string{
Self.callVoice
}
} 默认行为可以调用trait中的其他行为,不管其他行为有没有默认实现。如dark中实现了theVoice行为,那么我们就可以调用dark.call()行为。
- Trait作为参数:pub fn action(v:impl voice){v.call();}
- 泛型和trait bound:pub fn action<T:voice> (v:T){v.call();} 尖括号中的内容将泛型T绑定到了voide 行为集合上。也就是说该函数只接受实现了voice行为集合的类型。
- 通过+指定多个trait:pub fn action<T:voice+Display> (v:T){v.call();}
- Where 格式化trait bound:
Pub fn someFunc<T,I,U>(a:T,b:I,c:U)->string
Where T:Display+Clone
I:voice
U:debug{}
- 返回trait:fn getfunc()->impl voice{ dark{callVoice:string::from(“gaga”)}}目前智能发返回实现trait的单一类型。例如:
fn getfunc()->impl voice{ if 1{
dark{callVoice:string::from(“gaga”)
}
Else{
Chook{callVoice::string::from(“gegeda”)
}
}这是行不通的。因为trait不是类型,只是可共享的行为合集。而函数不能返回多个类型。17章介绍返回同一trait的多种类型的方法。
- Trait bound 为部分类型遮挡方法而为部分类型展示方法:
pub trait voice { fn theVoice(&Self)->string;
fn call(&Self)->string{
Self.theVoice()
}
}
pub struct dark<T> { callVoice:T }
impl <T> dark<T>{
fn new (a:T)->Self{
Self{a}
}
} 这个方法在t为任何类型时datk都可以调用。
Impl<T:Display> dark<T:Display>{
Fn fly(&Self)->bool{
True
}
}而这个方法只有在t类型实现了Display trait时,dark才可以调用。
- 生命周期:rust中的生命周期的规则本质时为了防止垂悬引用。每个变量或引用都有自己的生命周期。有些可以直接从代码中推断,例如:
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
rust不支持空值,这段代码中r的声明和初始化是分开的,如果在给定r一个值之前使用r,编译器会报错。继续看,r引用了x,但是在打印r的时候,x的生命周期已经结束,所以编译器会报错。接下来看生命周期。Rust的借用检查器把r的生命周期标记为’a,把x的生命周期标记为’b,然后检查到’a比‘b大,这不符合编译器的要求,编译器会报错,因为变量的生命周期应该比他的引用的生命周期要长。
- 函数中泛型生命周期:函数中引用作为参数或返回值的话其泛型生命周期不规定某个引用的确定生命周期,而是表示引用之间生命周期的关系。而且传入的参数和返回值必须遵循这种关系,否则编译器会报错。例如一个函数传入两个字符串slice,返回其中较长的一个:
#![allow(unused_variables)]
fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
} 这个函数意味着参数x,y和返回值的生命周期存在某种关系。该关系表明返回值的生命周期应该是两个参数生命周期的公共部分。实际上这种关系在函数内部表现得并不明显。但是在函数调用的地方就很容易看出这其中的关系:
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
} 这段代码从程序的逻辑上看,是能够成功运行的,因为longest会返回string1的引用,所以在打印的时候result指向的string1没有被销毁。但是编译器仍然会报错。因为在函数longest中规定了返回值的生命周期是string1和string2的交集,也就是string2的生命周期。所以编译器认为在打印函数所在的代码段已经超出了result的生命周期范围,result不能被使用。
再看函数泛型生命周期:函数泛型生命周期表明了函数中引用之间的关系,编译器根据这种关系再通过传入的变量的生命周期,就可以确定每一个使用泛型生命周期的引用的真实生命周期。更深一步的说,函数的业务逻辑中隐含着各个引用的生命周期关系,rust只不过是要求程序员明确的把这种关系声明在函数的签名中而已。如下例子:
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
} 首先这个例子的函数签名并没有真实反应业务逻辑中引用的声明周期关系。返回值的生命周期应该和参数x指向的变量的生命周期相同。然而代码中的返回值的生命周期实际上是result的生命周期。其次,result的生命周期在函数结束时也结束了,即便不规定生命周期关系,这回导致返回的引用是一个垂悬引用,编译器会报错。如果函数返回一个有用所有权的变量就不会有问题。
- 结构体中的生命周期:
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.')
.next()
.expect("Could not find a '.'");
let i = ImportantExcerpt { part: first_sentence };
} 这意味着结构体importantExcerpt的生命周期应该不大于其part字段的生命周期。而part字段的生命周期应该不大于first_sentence的生命周期。
60.生命周期省略规则:第一条规则是每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数: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
的生命周期被赋给所有输出生命周期参数。
编译器一次对一个函数使用这三个规则,如果还是无法确定引用的真实生命周期,编译器会报错,让程序员自己声明引用的生命周期。
- 方法中的生命周期:
#![allow(unused_variables)]
fn main() {
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
} 方法level适用59条中规则一。
#![allow(unused_variables)]
fn main() {
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
} 方法announce_and_return_part适用于59条规则三
- 静态生命周期:静态生命周期存活于整个应用运行期间,被声明为’static。字符串面值的生命周期是’static。
- 结合泛型参数trait bount和生命周期:
#![allow(unused_variables)]
fn main() {
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
} 由于’a也是泛型,所以他和T都可以放在尖括号中。只不过’a规定的是生命周期的关系。T规定的是参数类型的关系。