提问人:Tim Perry 提问时间:5/20/2023 更新时间:5/24/2023 访问量:169
如何在 Rust 中 Hyper 的异步闭包内从外部范围正确读取字符串值
How to correctly read a string value from an outer scope within an async closure for Hyper in Rust
问:
我正在尝试学习 Rust,并尝试编写一些非常简单的 Web 服务器代码来做到这一点。
我以为我对生命周期和简单代码借用的基础知识有一个很好的了解,但我发现要么我在某个地方缺少一个基本的技术,要么我认为是一个简单的案例实际上由于某种原因要复杂得多。
我本质上想做的是:
use std::env;
use std::convert::Infallible;
use std::net::SocketAddr;
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
// A demo web server: takes a message on the command-line, then
// serves it back to incoming requests.
#[tokio::main]
pub async fn main() {
let args: Vec<String> = env::args().collect();
let message = format!("Arguments were: {:?}", &args[1..]);
serve_message(message).await;
}
pub async fn serve_message(message: String) {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let make_svc = make_service_fn(|_conn| {
async move {
Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
Ok::<_, Infallible>(
Response::new(Body::from(message))
)
}))
}
});
let server = Server::bind(&addr).serve(make_svc);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}
这无法编译:
error[E0507]: cannot move out of `message`, a captured variable in an `FnMut` closure
--> src/main.rs:22:68
|
17 | pub async fn serve_message(message: String) {
| ------- captured outer variable
...
22 | Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
| ____________________________________________-----------------------_^
| | |
| | captured by this `FnMut` closure
23 | | Ok::<_, Infallible>(
24 | | Response::new(Body::from(message))
| | -------
| | |
| | variable moved due to use in generator
| | move occurs because `message` has type `String`, which does not implement the `Copy` trait
25 | | )
26 | | }))
| |_____________^ move out of `message` occurs here
error[E0507]: cannot move out of `message`, a captured variable in an `FnMut` closure
--> src/main.rs:21:9
|
17 | pub async fn serve_message(message: String) {
| ------- captured outer variable
...
20 | let make_svc = make_service_fn(|_conn| {
| ------- captured by this `FnMut` closure
21 | / async move {
22 | | Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
23 | | Ok::<_, Infallible>(
24 | | Response::new(Body::from(message))
| | -------
| | |
| | variable moved due to use in generator
| | move occurs because `message` has type `String`, which does not implement the `Copy` trait
25 | | )
26 | | }))
27 | | }
| |_________^ move out of `message` occurs here
我已经尝试了各种更复杂的修改,包括克隆、ARC、状态到带有句柄 impl 的结构体以及许多其他方法,但我很挣扎,每一种似乎都让我回到了上面相同的基本问题。我显然错过了一些关于异步、闭包和所有权如何交互以及管理它的工具的重要信息。我已经看过如何在 Rust 的闭包内重用外部作用域的值? 这是相似的,但唯一的答案的例子是一个更简单的演示,并没有清楚地转化为更大的问题 - 只是按照建议添加到处似乎不足以应对这种情况。.clone()
我发现最令人困惑的部分是,这与 Hyper 自己的一个示例非常相似:https://docs.rs/hyper/latest/hyper/service/fn.make_service_fn.html#example。但这个例子似乎没有遇到任何问题,而这确实如此。
这样做的正确和惯用方法是什么,为什么它有效,这个和那个 Hyper 示例案例有什么区别?非常感谢初学者级别的解释。
答:
出现此错误的原因是字符串被移动到您传递给 的闭包中。在 Rust 中,每个值都有一个唯一的所有者,移动一个值会转移其所有权。一旦值被移动,就不能再从原始位置使用它。请参阅“所有权和移动message
service_fn
"
但是,在您的情况下,您希望在多个响应中使用该字符串,这意味着您需要在多个闭包之间共享它。这就是Arc
(原子参考计数)派上用场的地方。
An 是一个线程安全的引用计数指针,它允许对 类型的值进行共享读取访问。可以克隆它以创建指向相同值的新指针,从而增加引用计数。message
Arc<T>
T
您可以更新函数以将 包装在 an 中,然后为每个请求克隆它,如下所示(playground):serve_message
message
Arc
use bytes::Bytes;
use hyper::{
service::{make_service_fn, service_fn},
Body, Error as HyperError, Request, Response, Server,
};
use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;
pub async fn serve_message(message: String) {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let message = Arc::new(Bytes::from(message));
let make_svc = make_service_fn(move |_conn| {
let message = Arc::clone(&message);
async {
Ok::<_, Infallible>(service_fn(move |_: Request<Body>| {
let message = Bytes::copy_from_slice(&*Arc::clone(&message));
async move { Ok::<_, HyperError>(Response::new(Body::from(message))) }
}))
}
});
let server = Server::bind(&addr).serve(make_svc);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}
它的工作方式是:
Arc::new(message)
创建一个拥有 .这是唯一直接拥有 .Arc
message
Arc
message
- 每次调用时,它都会克隆 (而不是 本身),这会增加引用计数,但不会移动 .
make_service_fn
Arc
message
message
- 在里面,我们再次克隆每个请求。这允许每个请求在不获取所有权的情况下引用 。
service_fn
Arc
message
- 最后,再克隆一次以在响应中使用。这不会移动 ,并允许在后续响应中使用。
Response::new(Body::from((*Arc::clone(&message)).clone()))
Arc
message
message
关于最后一点:
代码也可以工作,但要记住一个关键的区别。Response::new(Body::from(Arc::clone(&message).to_string()))
使用 时,您将为每个请求创建一个新的请求。如果请求很大或请求很多,这可能效率低下,因为它涉及每次为新内存分配内存。Arc::clone(&message).to_string()
String
message
String
相反,是 类型 。let message = Bytes::copy_from_slice(&*Arc::clone(&message))
Bytes
在此代码中,我们用于从每个请求的共享创建一个新实例。
这比直接共享效率低(正如我们理想情况下希望的那样),但它避免了您之前遇到的借用错误。Bytes::copy_from_slice(&*Arc::clone(&message))
Bytes
Bytes
Bytes
细分如下:
Arc::clone(&message)
创建一个指向与指向的相同值的新值。的类型是 。Arc
Bytes
message
Arc::clone(&message)
Arc<Bytes>
&*Arc::clone(&message)
取消引用以获取对它所指向的值的引用。的类型是 。Arc<Bytes>
Bytes
&*Arc::clone(&message)
&Bytes
Bytes::copy_from_slice(&*Arc::clone(&message))
创建一个新值,该值包含与该值指向的字节序列相同的字节序列。的类型是 。Bytes
Bytes
message
Bytes::copy_from_slice(&*Arc::clone(&message))
Bytes
该函数需要对字节切片 () 的引用,并且可以用作 because 实现 .Bytes::copy_from_slice
&[u8]
&Bytes
&[u8]
Bytes
Deref<Target=[u8]>
因此,总体效果是创建一个新值,其中包含原始值的字节副本,从而允许它在响应正文中独立使用。Bytes::copy_from_slice(&*Arc::clone(&message))
Bytes
Bytes
取消引用以获取引用 () 不起作用,因为 Body::from
不接受引用作为参数。
虽然 Rust 的特征通常适用于引用,但在这种情况下,该特征仅针对拥有的类型实现,而不是针对 .Arc<Bytes>
Bytes
&Bytes
&Bytes
From
From
Bytes
Bytes
Body::from
消耗其参数。它获取所提供值的所有权。- 当你取消引用时,你会得到一个(对 的引用),而不是一个拥有的。
Arc<Bytes>
&Bytes
Bytes
Bytes
- A 与 不一样。它们是不同的类型。前者是对价值的引用,后者是对自有价值的引用。
&Bytes
Bytes
Bytes
Bytes
- 因为没有实现 ,所以不能将 传递给 。
Body::from
&Bytes
&Bytes
Body::from
您链接的 Hyper 示例创建了一个 HTTP 响应,其类型为 ,该响应不涉及从外部作用域的任何借用。hyper::Body::empty()
在代码中,您尝试在服务函数中使用外部作用域的 (),这会导致您遇到的借用问题。String
message
您的用例与 Hyper 示例不同,因为您希望在多个闭包之间共享一个拥有的,这需要如上所述使用 。允许多个闭包对相同的闭包进行只读引用,而无需拥有它的所有权。String
Arc
Arc
String
评论
Arc::clone(&message)
.to_string()
Body::from
(&[u8])
Arc<String>
AsRef<[u8]>
Body::from(Arc::clone(&message))
Arc<String>
the trait From<Arc<String>> is not implemented for Body
Arc::clone(&message).to_string()
String
the trait From<Arc<bytes::Bytes>> is not implemented for Body
评论
Arc