当前位置: 首页 > news >正文

rust-异步学习

rust获取future中的结果

两种主要的方法使用 async: async fn 和 async 块
async 体以及其他 future 类型是惰性的:除非它们运行起来,否则它们什么都不做。
运行 Future 最常见的方法是 .await 它。
当 .await 在 Future 上调用时,它会尝试把 future 跑到完成状态。
如果 Future 被阻塞了,它会让出当前线程的控制权。
能取得进展时,执行器就会捡起这个 Future 并继续执行,让 .await 求解。

use futures::executor::block_on;
use futures::Future;// `foo()` returns a type that implements `Future<Output = u8>`.
// `foo().await` will result in a value of type `u8`.
async fn foo() -> u8 { 5 }fn bar() -> impl Future<Output = u8> {// This `async` block results in a type that implements// `Future<Output = u8>`.async {// 和block_on不同,.await不会阻塞当前线程,而是异步地等待 future完成let x: u8 = foo().await;x + 5}
}fn main() {let future = bar();let result = block_on(future);println!("{}", result);
}

async 生命周期

async fn foo(x: &u8) -> u8 { *x }// 等价于:
// impl Future<Output = u8> + 'a 是 Rust 中的 trait bounds 格式
// 定义了一个 "future" 对象,用于异步计算并产生类型为 u8 的结果
// + 'a:这是一个 trait bounds 中的 "lifetime" 限定符,用于确定对象的生命周期。
// 它表示实现 Future trait 的对象被绑定在了一个叫做 'a 的生命周期上
// 具体使用,这个 'a 生命周期可以是当前函数的生命周期、闭包中的生命周期、也可以是调用者传入的生命周期等
// 总结:当前 "future" 对象实现了 Future<Output = u8> trait 的对象,并且它的生命周期被限制在 'a 中
fn foo_expanded<'a>(x: &'a u8) -> impl Future<Output = u8> + 'a {// move 关键字告诉编译器引用的变量需要被捕获// 如下闭包会 "移动"(Move)它所捕获的变量到闭包内部,在闭包的生命周期内都可以使用这些变量// 即闭包会取得变量的所有权,即使在闭包外部变量的所有权已被转移也可以在闭包内部使用//  { *x } 将传入的 x 变量解引用并返回// async move { *x } 的含义是异步闭包,在它的生命周期内获取变量 x 的所有权,、// 将它解引用并作为 u8 类型的返回值async move { *x }
}

如上意味着:这些 future 被 async fn 函数返回后必须要在它的非 'static 参数仍然有效时 .await
通常场景,future 在函数调用后马上 .await(例如 foo(&x).await)没问题
如果储存了这些 future 或者把它发送到其他的任务或者线程,那就有问题了

带有引用参数的 async fn 转化成 'static future

常用方法:把这些参数和对 async fn 的函数调用封装到async 块中

use futures::executor::block_on;
use futures::Future;async fn borrow_x(x: &u8) -> u8 {*x
}fn good() -> impl Future<Output = u8> {async {let x = 5;// 参数 &x,表示传入一个 x 变量的不可变借用(borrow)// 异步函数 borrow_x:返回值类型是 impl Future,即它会返回一个 Future 对象// 在函数的返回值类型上调用 .await 表示暂停当前异步任务并等待结果的完成//  .await 关键字,可以使整个异步计算更高效,避免了阻塞等待异步任务完成的情况// 通过移动参数到 async 块中,把它的生命周期扩展到了匹配调用 good 函数返回的 Future 的生命周期borrow_x(&x).await}
}fn main() {let ft = good();let result = block_on(ft);println!("{}", result);
}
use futures::executor::block_on;
use futures::Future;fn bad() -> impl Future<Output = u8> {let x = 5;borrow_x(&x) // ERROR: `x` does not live long enough
}fn main() {let ft = bad();let result = block_on(ft);println!("{}", result);
}

不使用move

在同一变量的作用域内,在多个不同的异步块(async block)中可以访问同一局部变量(local variable),而不会出现冲突。

use futures::Future;
use futures::executor::block_on;async fn blocks() {let my_string = "foo".to_string();let future_one = async {println!("{}", my_string);};let future_two = async {println!("{}", my_string);};// Run both futures to completion, printing "foo" twice:let ((), ()) = futures::join!(future_one, future_two);
}fn main() {let fut = blocks();block_on(fut);
}

使用move

一个 async move 块会获取 所指向变量的所有权,允许它的生命周期超过当前作用域(outlive)
但是放弃了与其他代码共享这些变量的能力

use futures::Future;
use futures::executor::block_on;fn move_block() -> impl Future<Output = ()> {let my_string = "foo".to_string();async move {println!("{}", my_string);}
}fn main() {let fut = move_block();block_on(fut);
}

在多线程执行器中 .await

线程间移动

在使用多线程的 Future 执行器时,一个 Future 可能在线程间移动
任何在 async 体中使用的变量必须能够穿过线程
任何 .await 都有可能导致线程切换

使用 Rc,&RefCell 或者其他没有实现 Send trait 的类型不安全
包括那些指向 没有 Sync trait 类型的引用。
(注意:使用这些类型是允许的,只要他们不是在调用 .await 的作用域内)

锁的使用

横跨 .await 持有一个非 future 感知的锁很不好
它能导致整个线程池 锁上
一个任务可能获得了锁,.await 然后让出到执行器,允许其他任务尝试获取所并导致死锁

解决方式:使用 futures::lock里的 Mutex 类型比起 std::sync 里面的更好

固定

固定的背景

// 该结构体的定义包含了一个名为 buf 的可变引用
struct ReadIntoBuf<'a> {// buf 的生命周期不能超过它被声明的拥有者(结构体)的生命周期 'a// 意味着 AsyncFuture 结构体必须声明一个具有足够长生命周期 &'a mut [u8]buf: &'a mut [u8], // 指向 AsyncFuture 的成员x
}// 该结构体包含一个名为 x 的 128 字节数组和一个 ReadIntoBuf 类型的字段
struct AsyncFuture {x: [u8; 128],// 在同一作用域内,x 的引用被传递给 read_into_buf_fut,使得异步方法中读取到 x 数组的数据// 在 AsyncFuture 中,需要使用与 x 数组相同的生命周期注解 'what_lifetime?// 确保 &mut 引用在结构体的生命周期内仍然有效read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}

假设 x 数组定义在当前作用域内

fn main() {let mut x: [u8; 128] = [0; 128];let read_into_buf_fut = ReadIntoBuf { buf: &mut x[..] };// 在这种情况下,what_lifetime 就是当前作用域 main() 的生命周期,定义为 'staticlet async_future = AsyncFuture { x, read_into_buf_fut };
}

因此,AsyncFuture 的定义应该是:

struct AsyncFuture<'a> {x: [u8; 128],// 使用单引号(')表示生命周期变量称为泛型参数,它是 ReadIntoBuf 中的泛型变量 'a 的值// 泛型参数允许代码处理具有不同生命周期的值或类型,这种方式可以消除代码重复read_into_buf_fut: ReadIntoBuf<'a>,
}

上述说了背景,但是最开始的例子的问题是什么?
ReadIntoBuf future 持有了一个指向其他字段 x 的引用。
如果 AsyncFuture 被移动,x 的位置(location)也会被移走
使得存储在 read_into_buf_fut.buf 的指针失效

固定的反面示例

固定future 到内存特定位置则阻止了上述问题,让创建指向 async 块的引用变得安全

//  Rust 中的属性宏,用于自动生成结构体 Test 的 Debug trait 的实现代码以便后续调试使用
#[derive(Debug)]
struct Test {a: String,b: *const String,
}// 定义Test结构体的方法集合
impl Test {fn new(txt: &str) -> Self {Test {a: String::from(txt),b: std::ptr::null(),}}// 初始化 b 字段,赋予 a 字段的内存地址fn init(&mut self) {// *const String 指向 String 类型的不可变指针let self_ref: *const String = &self.a;//  b 现在包含了 a 字段的内存地址值//  b 字段使用了一个原始指针// b 指向 a 的引用,但由于 Rust 的借用规则,不能定义它的生命周期(lifetime)// 所以把它存成指针。一个自引用结构体self.b = self_ref;}fn a(&self) -> &str {&self.a}// 返回 b 字段内存地址中所存储的值fn b(&self) -> &String {// 使用原始指针可能存在安全问题,因此通过 assert 语句增加了对 b 是否已经初始化的检查// condition 的值为 false,则 assert! 宏会抛出一个 panic 异常并中断程序运行,同时输出一条错误信息assert!(!self.b.is_null(), "Test::b called without Test::init being called first");// unsafe:涉及到解引用一个原始指针,这个操作可能破坏类型系统的安全性质,所以用unsafe// 首先对 self.b 进行解引用得到一个 &String 类型的指针// 然后使用 unsafe 代码块获取这个指针所指向的 String 类型的值unsafe { &*(self.b) }}
}fn main() {let mut test1 = Test::new("test1");test1.init();let mut test2 = Test::new("test2");test2.init();println!("a: {}, b: {}", test1.a(), test1.b()); // a: test1, b: test1println!("a: {}, b: {}", test2.a(), test2.b()); // a: test2, b: test2}

如果上述列子改为如下:

//  Rust 中的属性宏,用于自动生成结构体 Test 的 Debug trait 的实现代码以便后续调试使用
#[derive(Debug)]
struct Test {a: String,b: *const String,
}// 定义Test结构体的方法集合
impl Test {fn new(txt: &str) -> Self {Test {a: String::from(txt),b: std::ptr::null(),}}// 初始化 b 字段,赋予 a 字段的内存地址fn init(&mut self) {// *const String 是指一个指向 String 类型的不可变指针let self_ref: *const String = &self.a;//  b 现在包含了 a 字段的内存地址值//  b 字段使用了一个原始指针// b 指向 a 的引用,但由于 Rust 的借用规则,不能定义它的生命周期(lifetime)// 所以把它存成指针。一个自引用结构体self.b = self_ref;}fn a(&self) -> &str {&self.a}// 返回 b 字段内存地址中所存储的值fn b(&self) -> &String {// 使用原始指针可能存在安全问题,因此通过 assert 语句增加了对 b 是否已经初始化的检查assert!(!self.b.is_null(), "Test::b called without Test::init being called first");// unsafe:涉及到解引用一个原始指针,这个操作可能破坏类型系统的安全性质,所以用unsafe// 首先对 self.b 进行解引用得到一个 &String 类型的指针// 然后使用 unsafe 代码块获取这个指针所指向的 String 类型的值unsafe { &*(self.b) }}
}fn main() {let mut test1 = Test::new("test1");test1.init();let mut test2 = Test::new("test2");test2.init();println!("a: {}, b: {}", test1.a(), test1.b()); // a: test1, b: test1println!("a: {}, b: {}", test2.a(), test2.b()); // a: test2, b: test2std::mem::swap(&mut test1, &mut test2);println!("a: {}, b: {}", test1.a(), test1.b()); // a: test2, b: test1println!("a: {}, b: {}", test2.a(), test2.b()); // a: test1, b: test2
}

结构体不再是自引用,它持有一个指向不同对象的字段的指针,导致未定义的行为
在这里插入图片描述

固定的实践

Pin 类型包装了指针类型, 保证没有实现 Unpin 指针指向的值不会被移动
例如, Pin<&mut T>, Pin<&T>, Pin<Box> 都保证了 T 不会被移动,即使 T: !Unpin
多数类型被移走也不会有问题。这些类型实现了 Unpin trait

指向 Unpin 类型的指针能够自由地放进 Pin,或取走。
例如,u8 是 Unpin 的,所以 Pin<&mut T> 的行为就像普通的 &mut T,就像普通的 &mut u8

固定后不能再移动的类型有一个标记 trait !Unpin
比如:async/await 创建的 Future

固定到栈上

Test类型实现了 !Unpin,那么固定这个类型的对象到栈上总是 unsafe 的行为

use std::pin::Pin;
use std::marker::PhantomPinned;#[derive(Debug)]
// 使用 Pin 类型来创建自引用结构体
// 防止实例在被移动时其自引用指针变为无效
struct Test {a: String,b: *const String,// 表示实例被初始化后,不能再被移动以保证自引用的合法性_marker: PhantomPinned,
}impl Test {fn new(txt: &str) -> Self {Test {a: String::from(txt),b: std::ptr::null(),// 表示实例被初始化后,不能再被移动以保证自引用的合法性_marker: PhantomPinned, // This makes our type `!Unpin`}}fn init(self: Pin<&mut Self>) {let self_ptr: *const String = &self.a;// get_unchecked_mut :// allows to obtain a mutable reference (&mut) to a descendant object// without performing Rust's safety checks(不需要安全检查)// Rust通过unsafe代码块来实现上述目的// get_unchecked_mut 用在开发者自信地用其他方式保证安全性的环境中// 在 Pin 的上下文之外对 Pin 实例进行借用时,必须使用 unsafe 代码//// self的类型:core::pin::Pin<&mut rust_demo1::Test>// this的类型:&mut rust_demo1::Test// 所以如下一行可以理解为:从Pin中取它包裹的类型let this = unsafe { self.get_unchecked_mut() };this.b = self_ptr;}// self的类型是Pin<&Self>,这个引用指向了一个暂时不可移动的 Test 实例// 由于访问 get_ref() 值会把 Pin 引用变为常规引用,因此不需要 unsafe 代码块// 在 Pin 上下文中,这种转换是有保证的// 因为 Pin 分别对它的 T: Unpin 泛型 struct 和 Pin<&T> 应用了沙盒限制(sandboxing restrictions)// 以防止 T 的 move 和 drop 操作fn a(self: Pin<&Self>) -> &str {// self.get_ref()的类型:&rust_demo1::Test// &self.get_ref().a 的类型 &alloc::string::String,它被转换为 &str&self.get_ref().a}fn b(self: Pin<&Self>) -> &String {assert!(!self.b.is_null(), "Test::b called without Test::init being called first");unsafe { &*(self.b) }}
}pub fn main() {// test1 此时可移动let mut test1 = Test::new("test1");// 隐藏test1防止它被再次访问(重新赋值)// 用 Pin::new_unchecked() 将 &mut Test 转换为 Pin<&mut Test> 引用let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };Test::init(test1.as_mut());let mut test2 = Test::new("test2");let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };Test::init(test2.as_mut());println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));// 	尝试移动,则报错// std::mem::swap(test1.get_mut(), test2.get_mut());
}

固定到栈的注意事项

  • 固定到栈总是依赖你在写 unsafe 代码时提供的保证
    例如知道了 &'a mut T 的 被指向对象(pointee) 在生命周期 'a 期间固定,不知道被 &'a mut T 指向数据是否在 'a 结束后仍然不被移动。如果移动了,将会违反固定的协约。

  • 忘记遮蔽(shadow)原本的变量
    因为可以释放 Pin 然后移动数据到 &'a mut T,像下面这样(这违反了固定的协约):

fn main() {let mut test1 = Test::new("test1");// 没有隐藏test1let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };Test::init(test1_pin.as_mut());// 释放pindrop(test1_pin);// 交换前// test1.b points to "test1": 0x7ff7bd6fcf50// test1.a="test1", test1.b=0x7ff7bd6fcf50println!(r#"test1.b points to "test1": {:?}"#, test1.b);let mut test2 = Test::new("test2");mem::swap(&mut test1, &mut test2);// 交换后// and now it points nowhere: 0x0println!("and now it points nowhere: {:?}", test1.b);// test1.a="test2", test1.b=0x0 违反了固定协约// test2.a="test1", test2.b=0x7ff7bd6fcf50
}

固定到堆上

固定保证了实现了 !Unpin trait 的对象不会被移动
固定 !Unpin 类型到堆上,指向的数据不会在被固定之后被移动走。
和在栈上固定相反,整个对象的生命周期期间数据都会被固定在一处。

use std::pin::Pin;
use std::marker::PhantomPinned;#[derive(Debug)]
struct Test {a: String,b: *const String,_marker: PhantomPinned,
}impl Test {fn new(txt: &str) -> Pin<Box<Self>> {let t = Test {a: String::from(txt),b: std::ptr::null(),_marker: PhantomPinned,};let mut boxed = Box::pin(t);let self_ptr: *const String = &boxed.a;unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };boxed}fn a(self: Pin<&Self>) -> &str {&self.get_ref().a}fn b(self: Pin<&Self>) -> &String {unsafe { &*(self.b) }}
}pub fn main() {let test1 = Test::new("test1");let test2 = Test::new("test2");println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
}

一些函数需要的参数是 Unpin 的 future。
为了让这些函数使用不是 Unpin 的 Future/Stream,需要将future Pin住
Pin<Box> 和 Pin<&mut Fut> 都能用作 future,并且都实现了 Unpin

  • Box::pin 创建 Pin<Box>
  • pin_utils::pin_mut! 创建 Pin<&mut T>
use pin_utils::pin_mut; // handy crate on crates.io// 该函数需要Unpin的future
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }// let fut = async { /* ... */ };
// Error: `fut` does not implement `Unpin` trait
// execute_unpin_future(fut); // Pinning with `Box`:
let fut = async { /* ... */ };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK// Pinning with `pin_mut!`:
let fut = async { /* ... */ };
pin_mut!(fut);
execute_unpin_future(fut); // OK

总结

(1)如果 T: Unpin(默认会实现),那么 Pin<'a, T> 完全等价于 &'a mut T。
换言之: Unpin 意味着这个类型被移走也没关系,就算已经被固定了,所以 Pin 对这样的类型毫无影响。

(2)如果 T: !Unpin, 获取已经被固定的 T 类型示例的 &mut T需要 unsafe。

(3)标准库中的大部分类型实现 Unpin,在 Rust 中遇到的多数“平常”的类型也是一样。但是, async/await 生成的 Future 是个例外。

(4)你可以在 nightly 通过特性标记来给类型添加 !Unpin 约束,或者在 stable 给你的类型加 std::marker::PhatomPinned 字段。

(5)你可以将数据固定到栈上或堆上

(6)固定 !Unpin 对象到栈上需要 unsafe

(7)固定 !Unpin 对象到堆上不需要 unsafe。Box::pin可以快速完成这种固定。

(8)对于 T: !Unpin 的被固定数据,你必须维护好数据内存不会无效的约定,或者叫 固定时起直到释放。这是 固定协约 中的重要部分。

附录

swap

// Rust 标准库中的函数,用于交换两个变量的值
std::mem::swap(&mut test1, &mut test2) 

假设两个结构体变量 test1 和 test2:

struct Test {val: i32,
}
let mut test1 = Test { val: 1 };
let mut test2 = Test { val: 2 };

swap之后,变量 test1 的值会被更新为 Test { val: 2 },变量 test2 的值会被更新为 Test { val: 1 }。
由于该函数会修改传入的变量的值,因此需要使用可变引用 &mut,并且只能在当前变量作用域内进行操作。
需要特别注意函数 std::mem::swap() 可能会导致“悬垂指针”等问题,因此在使用时需要仔细检查和评估潜在的风险。

get_unchecked_mut

允许通过可变引用(&mut)的方式,获取一个可变引用的后代对象(allows to obtain a mutable reference (&mut) to a descendant object),并且不能执行 Rust 的安全检查。
Rust 对于这类情况实现了飞腾支持,只要使用得当,就能安全地保证程序的执行安全性。

注意:
只有在开发者确信代码已经以其他方式保证安全性,才应该使用 get_unchecked_mut

比如:
(1)在 FFI(Foreign Function Interface)中调用 C 函数,必须使用 unsafe 代码来对其进行转换。如果要使用 rustc(Rust 编译器)检查 FFI 的安全性,则可能会出现假警告。
(2)在访问某个对象时,如果某种保证在代码中已经得到满足,那么就可以使用未经检查的代码。

这种函数典型的用法可以在使用 Pin 的例子中看到,它允许在 Pin 上下文中引用自身的某个字段。因为 Pin 类型本质上是一种安全约束,它的存在使得 Rust 可以保证在 Pin 的上下文之外不会发生安全问题。而由于代码库编写者不知道该类型的使用者在它的上下文中会做什么,在 Pin 的上下文之外对 Pin 实例进行借用时,必须使用 unsafe 代码。

new_unchecked

Pin::new_unchecked 方法允许将原始的不可变引用(&T)或可变引用(&mut T)转换为对应的 Pin 引用(即,Pin<&T> 或 Pin<&mut T>。转换后,Rust 不会再对被引用的对象执行安全性检查,因此开发者需要确保在遵循 Rust 的一般编码规范的情况下正确使用这个方法。

Pin 类型是一种用于强制不能在内存空间被移动的类型。在使用 Pin 时,必须遵循 Rust 的安全性规则,从而避免对 Pin 引用环境的破坏。
在 Pin 引用外部使用被引用的类型或类型上的方法时,Rust 编译器会自动执行安全性检查,但是,为 Pin 引用执行相同的检查会浪费宝贵的系统资源。

如何使用 Pin::new_unchecked 方法来创建 Pin 引用:

let my_ref = &mut my_struct;
let my_pin_ref = unsafe { Pin::new_unchecked(my_ref) };

注意:
使用 unsafe 代码块时,必须自行确保被引用的对象始终可用并符合 Pin 引用的要求,否则可能会引发安全性问题。

as_mut

用于将 Pin<&T> 引用转换为 Pin<&mut T> 引用,从而允许对元素进行安全的可变引用。该方法返回一个 Pin<&mut T> 引用。
注意:
由于 as_mut 方法本身不执行安全性检查,因此在使用时,需要确保 Pin 引用所包含的元素已经通过 Pin 约束得到保护。

假设有一个已经被pin的 Test 结构体,与其对应的 Pin 引用传递给了函数。
如果需要在函数中以可变方式修改 Test 中的某些元素,则可以使用 as_mut 方法将 Pin<&T> 引用转换为 Pin<&mut T> 引用:

fn do_something(test: Pin<&mut Test>) {let a = test.as_mut().a_mut();// Now we can mutate 'a' safelya.push_str(", and then some");
}

在上面的代码中,as_mut 方法被用来将一个可变的引用返回给调用者,以便对结构体 Test 中的元素进行修改。
注意:
在调用 as_mut 之前,需要将 &mut Test 引用转换为 Pin<&mut Test> 引用,这在上面的函数中通过传递它进行实现。然后,在调用 as_mut 方法时,使用它来获取对最新可变的元素进行引用的引用,以便修改它。

说明:
因为 as_mut 方法是从不可变引用转换为可变引用,所以在使用引用链构建时,无法将其用于堆栈中即时生成的可变引用。

在这种情况下,在使用引用之前,必须先生成一个 Pin<&mut T> 引用,例如,通过使用 Pin::new_unchecked 方法。

个人理解:
貌似不是这么回事,待后续分析

 47     // test1 is safe to move before we initialize it48     let mut test1 = Test::new("test1");49     // Notice how we shadow `test1` to prevent it from being accessed again50     let mut test3 = unsafe{Pin::new_unchecked(&mut test1)};5152     print_type_of(&test3);53     let tmp =  unsafe{test3.as_mut()};54     print_type_of(&tmp);

&String转&str

&String 类型可以转换为 &str 类型,因为 Rust 中的字符串(String)是 UTF-8 编码的。 &str 表示内存中的一块 UTF-8 编码的字符串。

可以通过添加引用符(&)并调用 as_str() 方法来将 &String 转换为 &str

let my_string = String::from("Hello, world!");
let my_str: &str = my_string.as_str();

也可以直接使用 & 符号引用String它并转换成 &str:

let my_string = String::from("Hello, world!");
let my_str: &str = &my_string;

这种方式天然地不存在内存分配问题,因此也是非常高效的。
请注意,在这里无需调用 as_str() 方法。

快速理解!Unpin和 Unpin

在 Rust 中,有一些类型是可以移动或重分配的,这些类型被标记为实现了 Unpin trait。
如果一个类型实现了 Unpin,它就可以在不限制语义和安全性的情况下,从一个位置移动到另一个位置。

如果一个类型没有实现 Unpin trait,则表明这个类型值在内存中不稳定,并且如果尝试在其上进行移动、重分配或者其他可能破坏内存稳定性的操作,就会导致编译时错误。
所以使用前先Pin住。

当使用 Pin 类型时,需要确保对这些约束和限制满足了正确地语义。在 Rust 中,通过使用 PhantomPinned 来标记类型为 !Unpin,表明这个类型的值可能不稳定,并且需要使用 Pin 类型在特定的上下文中使用。

handy crate

一个 Rust 社区中的第三方 crate,旨在为 Rust 开发人员提供实用的工具和辅助功能。

一些常见的功能及其描述:

  • try_trait
    一个 trait 定义,可以用于实现方法 try,这个方法类似于 unwrap,但允许更好的错误处理和调试。

  • iter_ext
    一个辅助模块,提供了一些常见且常用的迭代器操作,例如 dedup_by_key、intersperse、min_by_key 等。

  • clean
    一个函数,可以安全地从字符串中删除 ASCII 控制字符,因为这些字符在终端输出和其他上下文中可能会引起错误。

  • extend_slice
    一个扩展函数,可用于将一个 &[T] 切片扩展到具有给定长度的新切片。该函数可以在对于具有固定长度的数组的情况下特别有用。

  • resize_default
    一个扩展函数,可用于将具有默认值的元素的向量扩展到指定的长度。

  • Loggable
    一个 trait,可用于将任何类型转换为字符串,并将其用作日志。
    除此之外,handy crate 还提供了一些其他的小工具和实用程序,如处理字符串和字节的函数、时间日期函数等。

handy crate 中的功能可以显著简化 Rust 开发人员的代码,并加速常见任务的开发。由于 handy 是一个第三方 crate,因此需要安装并将其导入到项目中才能使用它的功能。

// handy = "0.5.3"
use handy::iter_ext::IntersectExt;fn main() {let a = vec![1, 2, 3, 4];let b = vec![1, 3, 5, 7, 9];// 它扩展了标准库中的迭代器并更加方便易用// 取交集let intersect = a.intersect(b);println!("{:?}", intersect);
}

相关文章:

rust-异步学习

rust获取future中的结果 两种主要的方法使用 async: async fn 和 async 块 async 体以及其他 future 类型是惰性的&#xff1a;除非它们运行起来&#xff0c;否则它们什么都不做。 运行 Future 最常见的方法是 .await 它。 当 .await 在 Future 上调用时&#xff0c;它会尝试把…...

【Azure】office365邮箱测试的邮箱账号因频繁连接邮箱服务器而被限制连接 引起邮箱显示异常

azure微软office365邮箱会对频繁连接自身邮箱服务器的IP地址进行&#xff0c;连接邮箱服务器IP限制&#xff0c;也就是黑名单&#xff0c;释放时间不确定&#xff0c;但至少一天及以上。 解决办法&#xff0c;换一个IP&#xff0c;或者新注册一个office365邮箱再重试。 以下是…...

重新登录成功和登录失败处理器

<template><div class="login"><el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form"><h3 class="title">Java1234 Vue3 后台管理系统</h3><el…...

【Spring】(三)Spring 使用注解存储和读取 Bean对象

文章目录 前言一、使用注解储存 Bean 对象1.1 配置扫描路径1.2 类注解储存 Bean 对象1.2.1 Controller&#xff08;控制器存储&#xff09;1.2.2 Service&#xff08;服务储存&#xff09;1.2.3 Repository&#xff08;仓库存储&#xff09;1.2.4 Component&#xff08;组件储存…...

ParallelCollectionRDD [0] isEmpty at KyuubiSparkUtil.scala:48问题解决

ParallelCollectionRDD [0] isEmpty at KyuubiSparkUtil.scala:48问题解决 这个问题出现在使用Kyubi Spark Util处理ParallelCollectionRDD的过程中&#xff0c;具体是在KyubiSparkUtil.scala文件的第48行调用isEmpty方法时出现的。该问题可能是由以下几个原因引起的&#xff1…...

---------------- 部署 Zookeeper 集群 ----------------

部署 Zookeeper 集群 1.安装前准备2.安装 Zookeeper修改配置文件在每个节点上创建数据目录和日志目录在每个节点的dataDir指定的目录下创建一个 myid 的文件配置 Zookeeper 启动脚本 //准备 3 台服务器做 Zookeeper 集群 192.168.109.1 192.168.109.2 192.168.109.3 1.安装前准…...

SpringBoot 依赖管理和自动配置---带你了解什么是版本仲裁

&#x1f600;前言 本篇博文是关于SpringBoot 依赖管理和自动配置&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您…...

c语言每日一练(2)

前言&#xff1a; 每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…...

代码随想录第三十七天

代码随想录第三十七天 Leetcode 738. 单调递增的数字 Leetcode 738. 单调递增的数字 题目链接: 单调递增的数字 自己的思路:完全想不到&#xff01;&#xff01; 正确思路:大致思路是从后向前遍历&#xff0c;不可以从前向后&#xff0c;如果从前向后没有保证单调递增的顺序&…...

Linux进程间通信--ftok

在C语言中&#xff0c;ftok函数用于生成一个唯一的键值&#xff0c;该键值通常用于创建共享内存&#xff0c;消息队列和信号量等系统资源的标识符。 ftok函数原型入下&#xff1a; key_t ftok(const char *pathname, int proj_id); 参数说明&#xff1a; pathname&#xff1a…...

Spring Boot集成Mybatis-Plus

Spring Boot集成Mybatis-Plus 1. pom.xml导包 <!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--mysql驱动--><dependency><groupId>mysql<…...

梳理日常开发涉及的负载均衡

负载均衡是当前分布式微服务时代最能提及的词之一&#xff0c;出于对分层、解耦、弱依赖、可配置、可靠性等概念的解读&#xff0c;一对一的模式变得不再可信赖&#xff0c;千变万化的网络环境中&#xff0c;冗余和备份显得格外重要&#xff0c;稍大型的系统就会存在大量微服务…...

IEEE 754 浮点数运算

​​​​​​...

阿里巴巴Java开发手册学习记录

阿里巴巴Java开发手册学习记录 一、编程规约 1.命名风格 严禁使用英文 拼音混合使用 类名应所有单词的首字母大写&#xff0c;除了&#xff08;UserDO&#xff0c;XxxDTO, XxxPo等&#xff09; 常量的命名应该是大写 单词间用下划线连接 抽象类的应以Abstract/Base开头 …...

论文阅读---《Unsupervised T ransformer-Based Anomaly Detection in ECG Signals》

题目&#xff1a;基于Transformer的无监督心电图&#xff08;ECG&#xff09;信号异常检测 摘要 异常检测是数据处理中的一个基本问题&#xff0c;它涉及到医疗感知数据中的不同问题。技术的进步使得收集大规模和高度变异的时间序列数据变得更加容易&#xff0c;然而&#xff…...

收藏这8个好用的原型设计工具,轻松制作原型图

在设计工作中&#xff0c;原型设计是非常关键的一步&#xff0c;而原型设计工具又能帮助设计师更轻松地完成设计工作。今天本文将与大家分享8个好用的原型设计工具&#xff0c;一起来看看吧&#xff01; 1、即时设计 即时设计是一个能在线协作的原型工具&#xff0c;也就是说…...

王道计网 第四章笔记

4.1 生活在网络层的“工人”是路由器,他负责各种异构网络的连接,但是因为他只生活在前三层所以从网络层之上的东西他不能管理,所以网路层之上的数据对于路由器来说必须是相同的、透明的。 常见的网络层协议有IP 和 ICMPTCP IP传输层协议FTP应用层协议一句话区分IP和MAC地址…...

C# Blazor 学习笔记(9):动态css/class绑定

文章目录 前言相关资料css和class绑定直接绑定间接绑定 前言 之前我们说到&#xff0c;我们组件化有三个目的。 不用写CSS不用写html不用写交互逻辑 为了解决第一个目的&#xff0c;我们需要动态css 相关资料 Blazor入手教程&#xff08;二&#xff09;css和class绑定 cs…...

vue3学习-Pinia状态管理

Pinia 定义一个Store import { defineStore } from pinia export const useStore defineStore(main, {})这个 name&#xff0c;也称为 id&#xff0c;是必要的&#xff0c;Pinia 使用它来将 store 连接到 devtools。 将返回的函数命名为 use… 是跨可组合项的约定&#xff0…...

TextBrewer:融合并改进了NLP和CV中的多种知识蒸馏技术、提供便捷快速的知识蒸馏框架、提升模型的推理速度,减少内存占用

TextBrewer:融合并改进了NLP和CV中的多种知识蒸馏技术、提供便捷快速的知识蒸馏框架、提升模型的推理速度&#xff0c;减少内存占用 TextBrewer是一个基于PyTorch的、为实现NLP中的知识蒸馏任务而设计的工具包&#xff0c; 融合并改进了NLP和CV中的多种知识蒸馏技术&#xff0…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...