16、Ownership System(所有权系统)
Ownership System(所有权系统)
所有权系统是Rust语言最基础,最独特,最重要的机制,它让Rust无需GC(Garbage Collection 垃圾回收机制)就可以保障内存安全和运行效率。
所有权系统包括所有权
(Ownership)、借用
(Borrowing)、生命周期
(Lifetimes)。
Rust编译器在编译代码时会根据所有权系统的一系列规则进行检查,所有分析与检查都在编译阶段完成。所以,所有权系统的运行时开销非常小。在运行时,所有权系统的任何功能都不会影响程序执行的效率。
一、通用概念
1.栈内存与堆内存
栈和堆都是代码在运行时可供使用的内存,但他们存储数据的方式不同。
- 栈内存存储的数据大小在编译时已知且固定,Rust中所有的基本类型都可以存储在栈上,因为他们的大小都是固定的。
- 堆内存存储的数据大小在编译时未知或可能发生变化,只有在程序运行时才能确定具体的占用内存的大小。当向堆上放入数据时,操作系统会先找到一块足够大的内存空间,将其标记为已使用,并返回一个指向该空间地址的指针,这个过程被称为在堆上分配内存。
数据入栈比在堆上分配内存的速度要快。入栈时,操作系统直接将栈顶作为要分配的内存空间即可,不需要有其他额外的操作。但是在堆上分配内存要做很多的工作,需要由操作系统决定分配哪一块内存,分配多大的内存。所以访问栈上数据比访问堆上数据的速度要快,访问栈上的数据只需要从栈顶获取,但是访问堆上的数据要首先访问这块内存的地址,拿到地址信息后再去访问那块内存。
所有权系统要做的就是追踪代码正在使用的堆上的数据,尽最大限度的减少堆上重复的数据,及时清理掉堆上不再使用的数据,确保垃圾数据不会耗尽内存空间等。了解所有权的存在有助于管理堆内存中的数据,理解所有权机制的工作方式。
2.值语义与移动语义
值语义
(Value Semantic)是指按位复制后与原值无关,值的独立性得到了保障(其实就是真正的拷贝了一份一摸一样的)。基本数据类型都是值语义,要修改值语义的变量,只能通过修改这个值本身实现,同时修改了它的值的话,也不会影响到刚才复制的值。按位复制
就是栈复制
,也成为浅复制
(Shallow Copy),C/C++里好像更常称为浅拷贝?浅复制
只复制栈上的数据,与之对应的是深复制
(Deep Copy),是对栈上和堆上的数据一起复制。
引用语义
(Reference Semantic)是指数据存储在堆内存
中,通过存储在栈内存的指针来管理堆内存的数据,并且禁止按位复制
。因为按位复制只能复制栈上的指针,如果有两个或多个指针指向堆上的同一块内存地址,那么就很容易造成堆上的数据二次释放。动态数组、字符串等都属于引用语义
(因为他们占用内存大小可变,而且数据结构复杂)。
3.复制语义与移动语义
在所有权机制下,Rust又引入了复制语义
(Copy Semantic)和移动语义
(Move Semantic)。
复制语义
对应的就是上面提到的值语义
,具有值语义特征的变量可以在栈上进行按位复制,以便于内存管理。移动语义
对应的是引用语义
,在堆上存储的数据只能进行深复制
,但是深复制
又会带来极大的性能开销,如果我们并不是为了让数据变成两份,而是拷贝一份“变量名”,让新的“变量名”可以访问这块堆内存的话,只需要在栈上移动指向堆上数据的指针地址即可,这样可以同时保证内存安全和按位复制的高性能。
二、所有权机制
Rust使用所有权系统管理程序运行时使用内存的方式,所有权机制的核心如下:
- 每个
值
都有一个被称为其所有者
的变量
,也就是说每块内存都有其所有者(存储在栈上的指向这块堆内存地址的指针变量),所有者具有这块内存空间的释放和读写权限。 - 每个值在任意时刻有且仅有一个所有者。
- 当所有者(变量)离开作用域时,这个变量将被丢弃(堆内存是否丢弃要看代码怎么写,一般情况下好像就直接drop掉了)
1.变量绑定
Rust使用let
关键字来声明变量,但是这里的变量不是传统意义上的变量,本质上其实是一种绑定语义
。let
将一个变量与一个值绑定在一起,这个变量就拥有了这个值的所有权。其实就是将这个变量和存储这个值的内存空间绑定,从而让变量对这块内存空间拥有访问权(我感觉这里,所有权机制就非常类似于C/C++里面的各种智能指针,感觉像是对“指针->内存地址”系统的一个高级抽象)。Rust确保对于每块内存空间有且仅有一个绑定变量与之对应,不允许多个变量同时指向同一块内存空间。
变量绑定
具有空间
和时间
的双重属性。空间属性
是指变量与内存空间进行了绑定,时间属性
是指绑定的时效性,也就是它的生命周期
(一个变量从创建到销毁的整个过程)。每个let
关键字声明的变量都会创建一个默认的词法作用域
,变量在作用域内有效。当变量离开作用域时,它所绑定的资源就会被释放,变量也会随之无效并销毁。
例如:
fn foo(){
let s=String::from("hello");//程序在运行时向操作系统请求一块内存用来存储这个String实例,并绑定到s变量上,此时s有效
//s有效
}//s无效,Rust自动调用drop函数释放s所绑定的内存
这种内存处理方式对Rust代码有着非常重要的影响,这里虽然看起来很简单,但是在复杂场景下,比如有多个变量在多个作用域中使用用一个堆上数据,代码的行为就不好预测了。
2.所有权转移
与绑定
概念相辅相成的另外一个机制是所有权转移
,所有权转移
对应于移动语义
。一个属于移动语义类型的值,其绑定变量的所有权转移给另外一个变量的过程叫做所有权转移
。
所有权转移之后,原变量不能再继续使用。Rust中会发生所有权转移的场景主要有变量赋值(所有权转移给变量)、向函数传递值(所有权传递给函数的形参)、从函数返回值(所有权通过函数返回值返回回来)
(1)变量赋值
所有权机制只针对在堆上分配的数据(也就是对应移动语义
的那些数据),基本类型没有所有权的概念(因为都是存储在栈上的)。对于移动语义的复杂类型来说,把一个变量赋值给另外一个变量可以在内存上重新开辟一个空间来存储复制的数据,再与新的变量绑定。
字符串变量赋值给另一个变量时转移所有权:
fn main(){
let x=5;
let y=x;
println!("x:{} y:{}",x,y);
let s1=String::from("hello");
let s2=s1;
println!("s1:{} s2:{}",s1,s2);
}
可以看到,这种非基本类型不能直接用=
move之后仍然两个都可以使用,这也印证了上面的说法,s1对那块String实例的堆内存已经没有了所有权,访问s1就会发生错误。
字符串“hello”与s1绑定的内存表现如图:
如上图所示,左侧部分是存储在栈上的一组数据,其中包括指向存放在堆上的字符串内容的指针、长度和容量;右侧部分则是存储在堆上的内容。左侧的len
表示字符串内容使用了多少字节的内存(有效存储字节占用),容量是字符串内容从操作系统总共获取了多少字节的内存(可容纳的最大字节)。
如果以值语义(按位复制
,即深复制)的处理逻辑生成一个s1的复制并绑定到s2,内存表现如图:
以上其实就是对栈上和堆上的数据全部进行了复制,也就是深复制
。Rust如果深复制了,当堆上数据所占内存空间比较大时,会对运行性能造成严重影响。
实际上,把s1赋值给s2只会从栈上复制它的指针、长度和容量,并没有复制指针指向的堆上的数据。
仅复制栈上数据的内存表现如图:
但是,这种处理方式带来了一个新的问题,如果s1和s2两个数据指针都指向了堆上的同一位置,当s1和s2离开各自的作用域时,都会尝试调用drop函数释放指向的堆内存空间,这种二次释放就会导致潜在的安全漏洞。
为了确保内存安全(所有权的第二条规则:每个值在任意时刻有且仅有一个所有者。
),Rust在完成s1栈上数据复制的同时会将s1置为无效,因此在s1离开作用域后不需要清理任何资源,s1已经把所有权转让给s2了,它不再有权限对这块堆内存调用drop了。
也就是说,s2创建之后不能再用s1了,Rust禁止使用无效的变量,否则会抛出value borrowed here after move
错误提示。值“hello”的所有权由s1转移到s2,并让s1无效的这一过程叫做所有权转移
。当前只有s2有效,就完美避免了内存二次释放的问题。
s1无效后的内存表现:
(2)向函数传递值
将值传递给函数在语义上与给变量赋值相似,下面的代码展示了所有权转移的全过程:
fn main(){
let s=String::from("hello"); //s有效
take_ownership(s); //s的所有权转移给了take_ownership函数
//s无效,不能继续使用,失去了所有权
let x=5; //x有效
make_copy(x); //x绑定值按位复制后传递给函数参数
//x有效,可以继续使用
}//作用域结束,x和s都无效了,s所有权已转移,它不需要特殊操作,但是x还需要调用drop清理栈内存
fn take_ownership(str:String){ //str有效
println!("{}",str);
}//作用域结束,str无效,调用drop释放str占用的内存
fn make_copy(int:i32){ //int有效
println!("{}",int);
}//作用域结束,int无效,调用drop释放int占用的内存
有一个问题,s所有权转移之后,s这个变量本身干嘛去了呢,就是上图的那个被无效化的s1的栈内存。我问了问AI,说s这个变量在丢失所有权后,它的栈内存不会发生什么,也就是说,尽管s这个变量不可用了,它所占的栈内存也不会被清除。而是在作用域结束后,由Rust来进行drop等处理。
为什么不及时drop掉呢?原来,Rust这样做是为了确保安全和性能,虽然看似这个无用的变量没有及时释放掉,白白占用着栈内存,但是如果Rust直接释放的话,往往会带来无法预料的问题,如果Rust及时drop掉了变量,那么万一,我们不小心又调用了这个变量,由于它已经被drop掉了,我们调用就会发生未定义行为,这就和Rust的安全性不着调了。况且,这些占用栈内存的变量,本身对性能影响也不大,也不会一直不被drop,Rust早晚会自动处理掉它,而且处理得很好,那么我们就不用顾虑这么多,对Rust的编译器放100个心吧!
下面对比两个程序
fn main(){
let key="Favorite color";
let value="Red";
let mut map=HashMap::new();
map.insert(key,value);
println!("{}",map[key]);
}
输出如下:
fn main(){
let key=String::from("Favorite color");
let value=String::from("Red");
let mut map=HashMap::new();
map.insert(key,value);
println!("{}",map[key]);
}
输出如下:
可以看到,key
作为参数在执行了insert语句后,key变量失去了对String::from("Favorite color");
的所有权,所以后续访问会报错。
解决方案是将&key
作为实参
传递给insert
方法,程序就可以正常运行。
fn main(){
let key=String::from("Favorite color");
let value=String::from("Red");
let mut map=HashMap::new();
map.insert(&key,&value);
println!("{}",map[key]);
}
(3)从函数返回值
函数的形参所获得的所有权会在函数执行完成时失效,失效之后就再也无法被访问。为了解决这个问题,我们可以通过函数返回值把所有权再次传递回调用者。
fn main(){
let s1=give_ownership(); //give_ownership()函数返回值所有权转移给s1
let s2=take_and_give_back(s1); //s1所有权转移给take_and_give_back,take_and_give_back将所有权转移给s2
}//作用域无效,s2无效,调用drop释放s2占用的内存
//s1无效,s1所有权已转移,无需特殊操作
fn give_ownership()->String{
let str=String::from("ownership"); //str有效
str //str将所有权传递给函数外界
}
fn take_and_give_back(name:String)->String{ //name有效
let hello=String::from("hello"); //hello有效
hello+" "+&name; //创建了一个新的String实例并把所有权传递到函数外界
}//hello无效,name无效,调用drop释放他们占用的堆内存
3.浅复制与深复制
通过上面的学习我们已经知道,浅复制是只复制栈上的数据,深复制是复制栈上和堆上的数据。基本数据类型默认支持浅复制,String类型不支持浅复制。
那么,为什么基本数据类型默认支持浅复制?元组,结构体,枚举等复合数据类型是否支持浅复制?如果确实需要深复制String该怎么办?
没事的时候看书看完了Rust里面的基本类型系统,里面提到了一个Copy trait
,由Copy trait
可以区分值语义
和引用语义
,就是说:实现了Copy trait
的都是值语义
,凡是值语义
类型数据都支持浅复制
。
整数类型、浮点数类型、布尔类型、字符类型等基本类型数据都默认实现了Copy trait
,因此基本数据类型默认支持浅复制。
- 对于元组类型,如果内部的每个元素的类型都实现了
Copy trait
,那么该元组类型数据支持浅复制。比如,元组(i32,bool)支持浅复制,但元组(i32,String)不支持浅复制。 - 结构体和枚举有点特殊,即使所有字段都实现了
Copy trait
,也不支持浅复制。比如下面的代码:
#[derive(Debug)]
struct Foo{
x:i32,
y:bool,
}
fn main(){
let foo=Foo{x:8,y:true};
let other=foo;
println!("foo:{:?},other:{:?}",foo,other);
}
代码报错,这是因为,虽然所有字段都是基本类型,但是struct本身不会自动实现Copy trait,也就不支持浅复制,所以第九行代码会发生所有权转移,自然就会报错了。
要解决这个问题,必须在Foo定义上标记#[derive(Copy,Clone)]
,让Foo实现Copy trait,代码就可以正常编译了!
#[derive(Debug,Copy,Clone)]
struct Foo{
x:i32,
y:bool,
}
fn main(){
let foo=Foo{x:8,y:true};
let other=foo;
println!("foo:{:?},other:{:?}",foo,other);
}
可以看到,这次就正常运行了。
但是需要注意的是,如果结构体里面包含引用语义
的字段,那么即使添加了上面的属性,也不会支持浅复制,因为里面的字段不支持Copy trait。
也就是说这类似于一个与门电路,只有里面的全部满足条件,外部这个整体才可以满足条件。
在某些场景中,如果确实需要深复制堆上的数据,可以使用clone方法
fn main(){
let s1=String::from("hello");
let s2=s1.clone();
println!("s1={},s2={}",s1,s2);
}
深复制数据的内存表现:
三、引用和借用
引用
(Reference)是一种语法(本质上是Rust提供的一种指针语义),借用
是对引用行为的描述。
引用
分为可变引用
和不可变引用
,对应着可变借用
和不可变借用
。使用&
操作符实现不可变引用
,使用&mut
实现可变引用。
&x
也可以成为对x的借用。通过&
完成对所有权的借用,不会造成所有权的转移。
1.引用与可变引用
向函数传递实参时转移所有权:
fn main(){
let vec1=vec![1,2,3];
let vec2=vec![4,5,6];
let answer=sum_vec(vec1,vec2);
println!("vec1:{:?},vec2:{:?},answer:{}",vec1,vec2,answer);
}
fn sum_vec(v1:Vec<i32>,v2:Vec<i32>)->i32{
let sum1:i32=v1.iter().sum();
let sum2:i32=v2.iter().sum();
sum1+sum2
}
执行代码会报错:
其实,问题现在也很明显,很好理解了,就是vec1和vec2在传入函数形参时丧失了对堆内存空间的所有权。
用上一部分的从函数返回值
的所有权处理方式可以这样做:
fn main(){
let vec1=vec![1,2,3];
let vec2=vec![4,5,6];
let (vec1,vec2,answer)=sum_vec(vec1,vec2);
println!("vec1:{:?},vec2:{:?},answer:{}",vec1,vec2,answer);
}
fn sum_vec(v1:Vec<i32>,v2:Vec<i32>)->(Vec<i32>,Vec<i32>,i32){
let sum1:i32=v1.iter().sum();
let sum2:i32=v2.iter().sum();
(v1,v2,sum1+sum2)
}
这样,让函数归还所有权的方案,确实解决了这一个问题,不过这很麻烦,还要专门处理那么多的与函数内部业务逻辑无关的变量。
为了更好的实现这样的操作,Rust支持所有权的借用
:通过引用给函数传递实参,就是把所有权借用给函数的参数,当函数的参数离开作用域时会自动归还所有权。这个过程需要将函数的参数通过&操作符
定义为引用,同时传递实参时也应该传递变量的引用。
这样修改刚才的代码就是一种很好的实现:
fn main(){
let vec1=vec![1,2,3];
let vec2=vec![4,5,6];
let answer=sum_vec(&vec1,&vec2);
println!("vec1:{:?},vec2:{:?},answer:{}",vec1,vec2,answer);
}
fn sum_vec(v1:&Vec<i32>,v2:&Vec<i32>)->i32{
let sum1:i32=v1.iter().sum();
let sum2:i32=v2.iter().sum();
sum1+sum2
}
要注意,如果想要使用这种方式,确保函数定义时,里面的形参要设置为引用类型
,也就是要加上&操作符
,同样的,调用函数时,传入给函数的实参也应该是引用类型,也要加上&操作符
。
引用默认是只读的,如果想要修改引用的值,应该使用可变引用&mut
。在定义与调用可变引用参数的函数时,必须同时满足以下三个要求:
- 变量本身必须是可变的,因为可变引用只能操作可变变量,不能获取不可变变量的可变引用。变量声明中必须有
mut
。 - 函数的形参必须是可变的,函数的形参定义中必须使用
&mut
。 - 调用函数时传递的实参必须是可变的,传递给函数的实参必须使用
&mut
。
示例,以可变引用修改动态数组的值:
fn main(){
let mut vec=Vec::new();
push_vec(&mut vec,1);
push_vec(&mut vec,2);
push_vec(&mut vec,2);
push_vec(&mut vec,5);
println!("vec:{:?}",vec);
}
fn push_vec(v:&mut Vec<i32>,value:i32){
//这行代码的作用是确保插入数据时是单调递增的
if v.is_empty()||v.get(v.len()-1).unwrap()<&value{
v.push(value);
}
//v.get(v.len()-1).unwrap() 这段代码,取当前Vec的最后一个下标的数据,得到Option<T>,然后调用unwarp(),把数据提取出来,和待插入数据做比较
}
示例,以可变引用修改基本类型
的值:
fn main(){
let mut x=6;
let y=&mut x;
*y+=1;
println!("x:{}",x);
}
可以看到,程序运行的结果是x=7
,变量y是变量x的可变引用,可以使用解引用操作符*
,追踪引用的值,*y
代表y所引用的值,即x的值。
2.借用规则
为了确保内存安全,借用必须遵循以下规则:
- 对于同一个资源的借用,在同一个作用域只能由一个可变引用(&mut T),或有n个不可变引用(&T),但不能同时存在可变引用和不可变引用。
- 在可变引用释放前不能访问资源所有者。
- 任何引用的作用域都必须小于资源所有者的作用域,并在离开作用域后自动释放。
借用规则类似于读写锁
,即同一时刻只能拥有一个写锁
,或者多个读锁
,不允许写锁
和读锁
同时出现。这是为了避免数据竞争
,保证数据的一致性。Rust在编译时完成借用规则的检查,这样可以有效避免运行时出现死锁等问题。
示例,在可变借用的同时进行不可变借用:
fn main(){
let mut x=6;
let y=&mut x;
*y+=1;
let z=&x;
println!("y:{},z:{}",*y,*z);
}
这体现了借用规则的第一条规则。想想也是,你也不希望自己借来的东西莫名其妙被别人乱动吧。
示例:在可变引用释放前访问资源所有者
fn main(){
let mut x=6;
let y=&mut x;
*y+=1;
let z=x;
println!("y:{},z:{}",*y,z);
}
报错如下:
以上的错误就是,在变量x的可变引用y未释放前再次访问变量x,这违反了借用规则的第二条——在可变引用释放前不能访问资源所有者。
还有一点就是,最多只能有一个可变引用,想一下也是,如果你硬要用x搞第二个可变引用,那不就相当于在变量x的可变引用y未释放前再次访问变量x了吗,所以,这个操作就无法实现。
上面的两个例子就是违反借用规则第一条和第二条时会出现的问题,而违反借用规则第三条会导致悬垂引用
。
3.借用示例1:切片
切片本身是没有所有权的,他是通过引用语法实现对集合中一段连续的元素序列的借用。切片可以和常见的能够在内存中开辟一段连续内存块的数据结构一起使用,比如数组、动态数组、字符串等。字符串切片就是指向字符串中的一段连续的字符。
(1)切片定义
切片本质上就是指向一段内存空间的指针,用于访问一段连续内存块中的数据。它的数据结构存储了切片的起始位置和长度,切片定义的语法如下所示:
let slice=&data[start_index..end_index];
其中start_index..end_index
表示一个范围类型
,starting_index
是切片的第一个位置,ending_index
是切片最后一个位置的后一个值,即生成的是从start_index
开始到end_index
结束的元素序列,但end_index
索引指向的字符不包含在内。start_index
和end_index
都可以省略。
省略start_index
表示从0开始,且start_index
的最小取值也是0;省略end_index
表示取最大长度,且end_index
的最大取值也就是最大长度。
比如start_index..
和..end_index
。
示例,字符串切片与动态数组切片:
fn main(){
let s=String::from("Hello Rust!");
println!("{}",&s[0..5]);
println!("{}",&s[..5]);
println!("{}",&s[7..s.len()]);
println!("{}",&s[7..]);
println!("{}",&s[0..s.len()]);
println!("{}",&s[..]);
println!();
let vec=vec![1,2,3,4,5];
println!("{:?}",&vec[0..2]);
println!("{:?}",&vec[..2]);
println!("{:?}",&vec[2..vec.len()]);
println!("{:?}",&vec[2..]);
println!("{:?}",&vec[0..vec.len()]);
println!("{:?}",&vec[..]);
}
切片图示:
代码运行结果:
(2)切片作为函数参数
切片可以作为函数的参数,将数组、动态数组、字符串中一段连续的元素序列通过引用的方式传递给函数。
示例,切片作为函数参数:
fn main(){
let s=String::from("Hello Rust!");
let str="Hello";
let vec=vec![1,2,3,4,5];
print_str(&s[0..5]);
print_str(&str);
print_vec(&vec[2..]);
}
fn print_str(s:&str){
println!("slice:{:?},length:{}",s,s.len());
}
fn print_vec(vec:&[i32]){
println!("slice:{:?},length:{}",vec,vec.len());
}
(3)可变切片
默认情况下,切片不能改变所引用的数组、动态数组、字符串中的元素,也就是说不能通过更改切片的元素来影响源数据。什么情况下可以通过更改切片数据进而更改源数据呢?
当声明源数据是可变的,同时声明切片也是可变的,就可以通过更改切片的元素来更改源数据。
示例,更改可变切片的元素会更改源数据:
fn main(){
let mut vec=vec![1,2,3,4,5];
let vec_slice=&mut vec[3..];
vec_slice[0]=7;
println!("{:?}",vec);
}
4.借用示例2:迭代器
Rust提供的迭代器IntoIter
,Iter
,IterMut
和所有权借用的对应关系如下:
迭代器 | 所有权借用 | 创建方法 | 迭代器元素类型 |
---|---|---|---|
IntoIter | 转移所有权 | into_iter | T |
Iter | 不可变借用 | iter | &T |
IterMut | 可变借用 | iter_mut | &mut T |
(1)转移所有权IntoIter
迭代器IntoIter由into_iter方法创建,会把容器中元素的所有权转移给迭代器,之后原容器不能再使用。
示例,into_iter方法转移所有权
fn main(){
let vec=vec!["Java","Rust","Python"];
for str in vec.into_iter(){
match str{
"Rust"=>println!("Niubility"),
_=>println!("{}",str);
}
}
}
如果我们在迭代器迭代完vec.into_iter()
后,尝试使用vec
这个容器,那么就会发生错误:
fn main(){
let vec=vec!["Java","Rust","Python"];
for str in vec.into_iter(){
match str{
"Rust"=>println!("Niubility"),
_=>println!("{}",str),
}
}
println!("{:?}",vec);
}
报错提示如下:
(2)不可变借用Iter
迭代器Iter由iter方法创建,能把容器中元素的引用传递给迭代器,而不发生所有权的转移。引用传递后,原容器依然可以继续使用。
注意,由于迭代器中的元素是对原容器数据的引用,所以在编写match语句的时候要注意“匹配的左值”应该加上&操作符
。
示例,iter方法获得所有权的不可变借用:
fn main(){
let vec=vec!["Java","Rust","Python"];
for str in vec.iter(){
match str{
&"Rust"=>println!("Niubility"),//注意这个地方要加上“&运算符”,取数据的引用
_=>println!("{}",str),
}
}
println!("{:?}",vec);
}
(3)可变借用IterMut
迭代器IterMut由iter_mut方法创建,会把容器中元素的可变引用
传递给迭代器,不发生所有权转移。引用传递之后,原容器还可以继续使用。
iter_mut方法与iter方法的不同之处在于,iter方法创建的是只读迭代器
,不能在迭代器中改变原容器的元素。但iter_mut方法创建的是可变迭代器,在迭代器中可以改变原容器的元素。
注意,因为迭代器中的元素是对原容器中元素的可变引用
,所以在match模式匹配时,需要使用&mut
,并且通过解引用符*
可以更改原容器中的元素。迭代器遍历元素后,原动态数组还可以继续使用。
示例,iter_mut方法获得所有权的可变引用:
fn main(){
let mut vec=vec!["Java","Rust","Python"];
for str in vec.iter_mut(){
match str{
&mut "Rust"=>{
*str="Niubility";
println!("{}",str);
},
_=>println!("{}",str),
}
}
println!("{:?}",vec);
}