闭包与迭代器
讲真又是比较变态的一章,算了算了我还可以接受,在Python学装饰函数的时候学过一点闭包,在学基础可迭代数据类型如字典,List学过一点迭代器
闭包
闭包:可以捕获其所在环境的匿名函数:
- 匿名函数,可以保存为变量,作为参数,返回值
- 在某一个地方创建闭包,然后再另一个上下文中调用闭包完成运算
- 可从其定义的作用域捕获值
额,好的,又涉及到了我的只是盲区,什么是匿名函数?好吧,我上网没有没查到具体的定义。不过再Rust中我感觉可以理解为可以赋值给变量且没有名字的函数。比如下面这个例子:
fn main() {
// 闭包不强制要求声明函参数与返回值类型!!!!,淡然我们可以手动标注
let noname:fuc = |arg1:i32, arg2|{
println!("{},{}",arg1,arg2);
};
// closure:fn(i32,i32)
let closure = this_is_a_fuction;
closure(10,15);
noname(2,3);
}
fn this_is_a_fuction(arg1:i32,arg2:i32){
println!("I'm a fuction");
}
I'm a fuction
2,3
上面的noname就是一个匿名函数,而我们的closure相当一一个传递函数?怎么说呢,就是指向函数的指针这么理解吧。我们再这两个函数赋值的时候均没有运行函数体里面的命令,这也是闭包的一个重要特性。
我们在使用闭包的时候一般使用结构体存取闭包的匿名函数与结果 。在结构体里面所有闭包都需要注明类型。Rust语言使得所有闭包都至少实现了Fn FnMut FnOnce中至少一个trait.
#[derive(Debug)]
struct Closure{
// 这里之所以没有泛形,是我觉得这应该是Rust语言更新后的结果,主讲课程没涉及到
// 如果想要参数中设计不确定因素可以泛形,没关系的,但是泛形也要知名类型(Trait约束,所以我个人人为不如写明了)
fuction_name:fn(i32,i32)->i32,
fuction_result:Option<i32>,
}
impl Closure{
fn new() -> Closure {
Closure{
fuction_name:|a1,a2|{return a1 + a2;},
fuction_result: None,
}
}
fn arg_convert(&mut self, a:i32,b:i32){
self.fuction_result = Option::from((self.fuction_name)(a, b));
}
}
fn main() {
let mut example = Closure::new();
example.arg_convert(5,7);
if example.fuction_result == Option::None {
example.arg_convert(1, 2);
}n!("{:?}",example);
}
Closure { fuction_name: 0x401ae0, fuction_result: Some(12) }
闭包还可以捕获到同级作用域下的变量,也就是不需要我们传参了,也会产生内存开销,所以也比较少见.不过在多线程需要将闭包传递给新线程的时候,可以用Move关键字获得所有权传递
迭代器
迭代器:对一系列项执行某些任务。迭代器负责:遍历每个项,确认序列何时完成。
Rust中的迭代器是惰性的(不如说很多迭代器都是惰性的),除非调用迭代器的方法,否则迭代器本身没有任何效果 。Rust中可以使用迭代器的:至少[], (), vec都可以。
我们来看看iter这个方法的源码:
// 返回了一个Iter类型
pub fn iter(&self) -> Iter<'_, T> {
Iter::new(self)
}
// Iter本身的是一个结构体并实现了好多种Trait
pub struct Iter<'a, T: 'a> {
ptr: NonNull<T>,
end: *const T, // If T is a ZST, this is actually ptr+len. This encoding is picked so that
// ptr == end is a quick test for the Iterator being empty, that works
// for both ZST and non-ZST.
_marker: PhantomData<&'a T>,
}
// 看看人家自带的一堆Trait
#[stable(feature = "core_impl_debug", since = "1.9.0")]
impl<T: fmt::Debug> fmt::Debug for Iter<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Iter").field(&self.as_slice()).finish()
}
}
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: Sync> Sync for Iter<'_, T> {}
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: Sync> Send for Iter<'_, T> {}
impl<'a, T> Iter<'a, T> {
#[inline]
pub(super) fn new(slice: &'a [T]) -> Self {
let ptr = slice.as_ptr();
// SAFETY: Similar to `IterMut::new`.
unsafe {
assume(!ptr.is_null());
let end = if mem::size_of::<T>() == 0 {
(ptr as *const u8).wrapping_add(slice.len()) as *const T
} else {
ptr.add(slice.len())
};
Self { ptr: NonNull::new_unchecked(ptr as *mut T), end, _marker: PhantomData }
}
}
/// Views the underlying data as a subslice of the original data.
///
/// This has the same lifetime as the original slice, and so the
/// iterator can continue to be used while this exists.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// // First, we declare a type which has the `iter` method to get the `Iter`
/// // struct (`&[usize]` here):
/// let slice = &[1, 2, 3];
///
/// // Then, we get the iterator:
/// let mut iter = slice.iter();
/// // So if we print what `as_slice` method returns here, we have "[1, 2, 3]":
/// println!("{:?}", iter.as_slice());
///
/// // Next, we move to the second element of the slice:
/// iter.next();
/// // Now `as_slice` returns "[2, 3]":
/// println!("{:?}", iter.as_slice());
/// ```
#[must_use]
#[stable(feature = "iter_to_slice", since = "1.4.0")]
pub fn as_slice(&self) -> &'a [T] {
self.make_slice()
}
}
// 这一段是迭代器的核心,这个宏的源码超级长我就不放了,反正也看不懂
iterator! {struct Iter -> *const T, &'a T, const, {/* no mut */}, {
fn is_sorted_by<F>(self, mut compare: F) -> bool
where
Self: Sized,
F: FnMut(&Self::Item, &Self::Item) -> Option<Ordering>,
{
self.as_slice().windows(2).all(|w| {
compare(&&w[0], &&w[1]).map(|o| o != Ordering::Greater).unwrap_or(false)
})
}
}}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> Clone for Iter<'_, T> {
fn clone(&self) -> Self {
Iter { ptr: self.ptr, end: self.end, _marker: self._marker }
}
}
#[stable(feature = "slice_iter_as_ref", since = "1.13.0")]
impl<T> AsRef<[T]> for Iter<'_, T> {
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}
反正就是有一个本身被强转的类型,一个结尾(长度),一个标记位marker
~~这是源码,我尽力了。~~实际上我们定义一个迭代器,只需要定义其trait, 并实现Iterator trait的next方法即可。next方法每次返回迭代器中的一项,并将返回结果包裹在Some中。迭代方法有三种:
- iter 不可变引用迭代器
- into_iter,创建的迭代器会获得元素的所有权
- iter_mut: 迭代元素的可变引用
我们同时可以使用map对雕刻带起的所有元素进行闭包转换。同时也可以使用filter方法使用闭包(要求返回BOOL进行过滤)
fn main(){
let mut t = vec![2,3,4,3,3,5];
let mut v2:Vec<i32> = t.iter().map(|x| *x+2).collect();
let v3:Vec<&i32> = v2.iter().filter(|a| **a>=5).collect();
println!("{:?}",v2);
println!("{:?}",v3);
}
[4, 5, 6, 5, 5, 7]
[5, 6, 5, 5, 7]
我需要简单解释一下上面的代码,真的非常难。首先我们v2 使用t.iter(), 返回一个Vec 类型的iter迭代器。这里我们写闭包函数的时候因为是不可变引用的迭代器,迭代器,迭代器。这里的x已经是引用类型,,也就是&i32. (换句话说如果我们治理使用的是into_iter这种拿到所有全而不是引用的类型,这里的x就是i32类型)这里其实我甚至不知道给v2什么类型好。但是我想了一下,我们重组成Vec(collect)的时候应该回会解引用,所以这里的V2应该是Vec类型。然后,我们的v3 使用filiter.filiter和map完全不同,甚至连传参都不同。filiter我至今想不明白他的参数是二次引用,同时由于filiter会引用两次,collect()只会解一次引用,所以我们应该返回Vec<&i32>类型。整个过程最离谱的是filter的两次引用,千万别被误导了。
// 创建自定义迭代器(实现next),典型斐波那契
use std::cmp::Ordering;
use std::iter::*;
// use std::ops::{Residual, Try};
struct add_num{
num1:i32,
num2:i32,
count:i32
}
impl Iterator for add_num {
// 这个语法糖挺好玩,定义迭代器的返回类型,自动取Option中的值看来
type Item = i32;
fn next(&mut self) -> Option<i32> {
let p = self.num1.clone();
self.num1 = self.num1 + self.num2;
self.num2 = p;
if self.count >0{
self.count -= 1;
return Some(self.num2);
}else{
// 使用None就终止了
return None;
}
}
}
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
每日一题824. 山羊拉丁文
impl Solution {
pub fn to_goat_latin(sentence: String) -> String {
// sentence不可变就没有办法使用sentence
let sentence = sentence + " ";
// 这里不用传参,闭包特性获取
let sentence_split = ||-> Vec<String>{
let mut start = 0;
let mut end = 0;
let mut return_:Vec<String> = Vec::new();
for letter in sentence.chars(){
if letter == ' '{
return_.push(sentence[start..end].to_string());
start = end + 1;
}
end = end + 1;
}
return_
};
let judge_yuanyi = |str_:String| -> bool{
if str_ == "a".to_string()|| str_ == "A".to_string(){ return true;}
if str_ == "e".to_string()|| str_ == "E".to_string(){ return true;}
if str_ == "i".to_string()|| str_ == "I".to_string(){ return true;}
if str_ == "o".to_string()|| str_ == "O".to_string(){ return true;}
if str_ == "u".to_string()|| str_ == "U".to_string(){ return true;}
false
};
let mut count = 0;
let mut wor = sentence_split();
let words = wor.iter_mut();
let mut return__:String = String::from("");
for mut word in words{
count += 1;
if (judge_yuanyi)(word[0..1].to_string()){
*word = word.to_string() + "ma";
}else{
*word = word[1..].to_string() + &word[0..1];
*word = word.to_string() + "ma";
}
let mut count_ = 0;
while count_ < count{
*word = word.to_string() + "a";
count_ += 1;
}
}
for word in wor{
return__ = return__ + &word;
return__ = return__ + " ";
}
return__[..return__.len()-1].to_string()
}
}
执行用时:0 ms, 在所有 Rust 提交中击败了100.00% 的用户
内存消耗:2 MB, 在所有 Rust 提交中击败了75.00% 的用户
class Solution:
def toGoatLatin(self, sentence: str) -> str:
words:list = sentence.split(" ")
yuanyin = 'aeiouAEIOU'
for word_index in range(len(words)):
word = words[word_index]
if word[0] in yuanyin:
word += 'ma'
else:
word = word + word[0] + 'ma'
word = word[1:]
for i in range(word_index + 1):
word += 'a'
words[word_index] = word
sentence = ''
for word in words:
sentence = sentence + word + ' '
# 去掉最后的空格
return sentence[:-1]
执行用时:36 ms, 在所有 Python3 提交中击败了66.40% 的用户
内存消耗:15 MB, 在所有 Python3 提交中击败了43.47% 的用户
智能指针
指针:一个变量在内存中包含的是一个地址(指向其他数据)
Rust最常见的指针就是引用,没错无论是在什么时候引用都是最狗的。引用:& 变量保存指向的值的地址 ,没有其余开销, 最常见的指针类型。
而我们本章所介绍的智能指针是一些数据结构。他们包含指针本身行为的能力,同时有额外的元数据与功能 , 比如 :
-
referenc count:通过记录所有者的数量,使一份数据被多个所有者同时持有,并在没有所有者的时候自动清理数据。
-
String, Vec 都拥有一篇内存区域,且允许用户对其操作 并拥有元数据(容量等)
智能指针通常使用strut实现,并且实现了 Deref 和 Drop的trqit:
- Deref trait:允许智能指针struct的实例像引用一样使用
- Drop trait :允许自定义当智能指针实例走出作用域的代码清理堆与栈上的数据
Box <T>
在Heap内存上分配值。一种比较简单的智能指针: 允许你在Heap(而不是stack)上存储数据,stack 上指向Heap数据的指针。没有性能开销,没有额外功能.结构如图(本身的元数据struct存在stack上,但是stack里的指针指向Heap数据).所以说走出作用域的时候Box会释放栈上的元数据+指针以及 堆上的数据
使用场景:
- 在编译时,某类型的大小无法确定,但是使用该类型时,上下文需要知道它确切的大小
- 有大量数据,只想移交所有权,不允许克隆
- 使用某个值,关心它是否实现了某个trait, 而不关心具体类型,
fn main() {
let p = Box::new('😀'); // 现在我们把一个char类型存到了堆上
println!("{}",p);
}
Box 可以赋能递归类型,比如我们之前每日一题我写字典树时候树struct是递归类型 。这种类型本来是确定不了大小的,因为编译器会无限循环寻找 对于Rust这种安全性要求极高的语言,我当时甚至没有注意会不会编译失败~~。 好吧,哪个时候我用了Vec这种本来就是Box声明的动态空间类型,怪不得能成功~~定递归大小的方法叫Cons List
Cons List 是来自 Lisp语言的一种数据结构,如图。Cons List里面最后一个成员只包含Nil(终止标记)值, 没有下一个元素。
enum trya{
try_next(trya),
Nil(Option<i32>),
}
// 无限空间报错
3 | enum trya{
| ^^^^^^^^^ recursive type has infinite size
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `trya` representable
先说一下Rust是如何存储枚举类型的:Rust会使用自定义的枚举类型中最大的空间变体作为每个变体的存储空间分配大小。f所以应对上面这种递归型,Rust无法分配空间,就会报错, 这个时候help有给出解决办法: 将存储类型换为指针,这样就会变为指定的大小,可以使用Box, RC , 引用等方法。
// 尝试引用方法,需标注生命周期
enum trya<'a>{
try_next(&'a trya),
Nil(Option<i32>),
}
fn main() {
let tryb =try_next(&try_next(&try_next(&Nil(None))));
}
// 报错,引用还需要生命周期声明,而且递归的生命周期声明好像是Rust编译器捕捉不到。
4 | try_next(&'a trya),
| ^^^^ expected named lifetime parameter
use crate::trya::*;
#[derive(Debug)]
enum trya{
try_next(Box<trya>),
Nil(Option<i32>),
}
fn main() {
let tryb =try_next(Box::from(try_next(Box::from(try_next(Box::from(Nil(None)))))));
println!("{:?}",tryb);
}
try_next(try_next(try_next(Nil(None))))
Deref Trait
Deref Trait的作用(温习) 是对象为变换为引用类型指针。通过实现Deref Trait使我们可以自定义解引用运算符 * 的行为, 也能让我们像处理引用一样处理智能指针。
标准库中Deref trait要求我们实现一个deref方法
pub trait Deref {
/// The resulting type after dereferencing.
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "deref_target"]
#[lang = "deref_target"]
// 所以我们写trait的时候要定义Target关联类型,在这里其实相当于返回引用的时候保证知道在这个地址所占的空间大小
type Target: ?Sized;
/// Dereferences the value.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "deref_method"]
fn deref(&self) -> &Self::Target;
}
se std::ops::Deref;
struct tryc<T>{
num:T,
}
impl <T> Deref for tryc<T>{
type Target = T;
fn deref(&self) -> &T{
return &self.num;
}
}
fn main() {
let tryd = tryc{num:69};
let tryd_reference = tryd.deref();
println!("{:?}",*tryd_reference == 69 );
println!("{:?}",*(tryd) == 69 ); // 两个println内的内容完全一样,上面的是显示,下面的是隐式,但是编译器会把他解析为显示
}
true
true
Deref trait还有一个重要的特性就是Deref Corecion: 隐式解引用转化, 一种为函数和方法提供的便捷特性:
假设T类型实现了Deref trait:
- Deref coercion 可以把T的引用转化为T经过deref corecion操作后生成的引用
- 当引用传递给函数和方法,但是参数类型不匹配,会自动发生deref corecion
- 在编译时完成,不带来额外开销
- 这也是String引用可以强转&str的原因,真的是妙啊
这么一看deref真的是很方便的功能。
fn main() {
let tryd = tryc{num:69};
show_i32(&tryd);
}
fn show_i32(p:& i32){
println!("{}",p);
}
69
Drop Trait
实现Drop Trait, 就相当于我们自己写GC(gabrage collection),真是妙蛙种子吃着妙脆角进了米奇妙妙屋:妙到家了 在Rust类型中任何类型都可以实现Drop trait 实现的方法是定义一个drop方法,上源码:
pub trait Drop {
#[stable(feature = "rust1", since = "1.0.0")]
fn drop(&mut self);
}
其实啥也不用写,把自己的引用传给他它就自动全删了,,怎么感觉有点略微简单,,
impl <T> Drop for tryc<T> {
fn drop(&mut self) {
println!("Now I.m a garbage and will be collected , Byebye master, I'll miss you forever");
}
}
fn main() {
let tryd = tryc{num:69};
std::mem::drop(tryd);
let d = let d = tryd.num;
}
24 | std::mem::drop(tryd);
| ---- value moved here
25 | let d = tryd.num;
| ^^^^ value borrowed here after move
// 重粘了一边是因为上一次T没限定display,直接打印有BUG
struct tryc<T:Display>{
num:T,
}
impl <T:Display> Deref for tryc<T>{
type Target = T;
fn deref(&self) -> &T{
return &self.num;
}
}
impl <T:Display> Drop for tryc<T> {
fn drop(&mut self) {
println!("Now I.m a garbage and will be collected , Byebye master, I {}, will miss you forever",self.num);
}
}
fn main() {
let tryd = tryc{num:69};
let tryd = tryc{num:13};
// std::mem::drop(tryd);
// println!("{:?}",tryd);
}
// 这个结果说明了什么,在覆盖命名的时候69还放在内存中没有删掉,但是无法调用了。所以我们最好向上面一样清理数据。
Now I.m a garbage and will be collected , Byebye master, I 13, will miss you forever
Now I.m a garbage and will be collected , Byebye master, I 69, will miss you forever
Rc <T>
引用计数的智能指针:保证所有权不混乱(因为引用的存在,一个值可能会有多个所有者利用引用)。RC Reference counting: 引用计数,判断值是否被使用。如果不再使用(计数器为0) 岁然不会直接清理,但会标记可以被清理掉.
use std::rc::Rc;
// 浅拷贝,直接把地址给你,然后计数加一。
RC::clone()
RC<T>不在,不在,不在 预导入模块中,需要我们手动导入。
RefCell <T>
内存不可变性是Rust的设计模式之一, 允许只持有不可变引用的前提下对数据修改,这也是Rust不能完全安全的原因之一。在数据结构中使用unsafe代码绕过Rust正常的可变性与借用规则。
RefCell<T> 代表了持有数据的唯一所有权,且不会在编译时强制遵守引用规则 ,出错了直接panic.这种方法面向特定内存安全场景不可变环境中修改自身数据非常有效。注意RefCell<T>只能用于单线程场景
Box<T> | RC<T> | RefCell<T> | |
---|---|---|---|
同一数据的所有者 | 一个 | 多个 | 一个 |
可变性,借用检查 | 可变,不可变借用(编译时检查) | 不可变借用(编译时检查) | 可变,不可变借用(运行时检查) |
// 一个example
fn main(){
let p = 10;
let q = &mut p;
// 报错, p不可变, q借用了p的可变引用
let q = RefCell::new()
}
// Refcell 如何改变,加上RC多个所有者简直逆天
pub trait _try{
fn change(&self);
}
#[derive(Debug)]
struct test{
need_change:RefCell<String>,
}
impl test{
pub fn new() -> test{
test{need_change:RefCell::new(String::new())}
}
}
impl _try for test{
// 注意,这里的self是不可变引用,但我们还是成功Push了
fn change(&self) {
// 获得內部值得可变引用,成功改变String,牛皮
(*self.need_change.borrow_mut()). = "Hello".to_string();
}
}
fn main() {
let p = test::new();
p.change();
println!("{:?}",p);
}
test { need_change: RefCell { value: "Hello" } }