英文:
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};
// 一个演示Web服务器:从命令行接收一条消息,然后将其返回给传入的请求。
#[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]: 无法从`message`中移出一个在`FnMut`闭包中捕获的变量
--> src/main.rs:22:68
|
17 | pub async fn serve_message(message: String) {
| ------- 外部捕获的变量
...
22 | Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
| ____________________________________________-----------------------^
| | |
| | 被此`FnMut`闭包捕获
23 | | Ok::<_, Infallible>(
24 | | Response::new(Body::from(message))
| | -------
| | |
| | 变量由于在生成器中使用而被移出
| | 移动发生因为`message`具有不实现`Copy`特性的`String`类型
25 | | )
26 | | }))
| |_____________^ 移出`message`发生在这里
error[E0507]: 无法从`message`中移出一个在`FnMut`闭包中捕获的变量
--> src/main.rs:21:9
|
17 | pub async fn serve_message(message: String) {
| ------- 外部捕获的变量
...
20 | let make_svc = make_service_fn(|_conn| {
| ------- 被此`FnMut`闭包捕获
21 | / async move {
22 | | Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
23 | | Ok::<_, Infallible>(
24 | | Response::new(Body::from(message))
| | -------
| | |
| | 变量由于在生成器中使用而被移出
| | 移动发生因为`message`具有不实现`Copy`特性的`String`类型
25 | | )
26 | | }))
27 | | }
| |_________^ 移出`message`发生在这里
我尝试了许多更复杂的修改,包括克隆、ARCs、将状态放入具有句柄impl的结构体以及许多其他方法,但我一直在挣扎,每一种方法似乎都让我回到了上面的同一个根本问题。显然,我在异步、闭包和所有权交互方面漏掉了一些基本的东西,以及管理它们的工具。我看过 https://stackoverflow.com/questions/67860385/how-to-re-use-a-value-from-the-outer-scope-inside-a-closure-in-rust ,它类似,但唯一的答案示例是一个更简单的演示,不明显适用于更大的问题 - 就像随处建议的添加 .clone()
对于这种情况似乎不足够。
我发现最令我困惑的部分是,这与Hyper自己的一个示例非常相似:https://docs.rs/hyper/latest/hyper/service/fn.make_service_fn.html#example。但这个示例似乎没有遇到任何问题,而这个示例却遇到了问题。
正确和惯用的方法是什么,为什么它起作用,以及这与Hyper示例案例有何不同?非常感谢初学者级别的解释。
英文:
I'm trying to learn Rust, and trying to write some extremely simple web server code to do so.
I thought I had a good idea of the basics of lifetimes & borrowing in simple code, but I'm finding that either I'm missing a basic technique somewhere, or what I thought was a simple case is actually much more complicated for some reason.
What I'm essentially trying to do is this:
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);
}
}
This fails to compile with:
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
I've tried a wide variety of more complex modifications of this, with cloning, ARCs, state into a struct with a handle impl, and lots of other approaches, but I'm struggling and each of them seems to bring me back to the same fundamental problem above. I'm clearly missing something essential about how async, closures & ownership interact, and the tools to manage that. I have seen https://stackoverflow.com/questions/67860385/how-to-re-use-a-value-from-the-outer-scope-inside-a-closure-in-rust which is similar, but the only answer's example is a simpler demo that doesn't clearly translate to the larger problem - just adding .clone()
as suggested everywhere doesn't seem sufficient for this case.
The part I find most confusing is that this is extremely similar to one of Hyper's own examples: https://docs.rs/hyper/latest/hyper/service/fn.make_service_fn.html#example. But that example doesn't seem to hit any issues, while this does.
What's the correct & idiomatic way to do this, why does it work, and what's the difference between this and that Hyper example case? Beginner-level explanations much appreciated.
答案1
得分: 1
以下是代码部分的翻译:
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);
}
}
希望这对你有所帮助。如果需要更多信息或有其他问题,请随时提出。
英文:
The reason that you are getting this error is that the message
string is being moved into the closure that you pass to service_fn
. In Rust, each value has a unique owner, and moving a value transfers its ownership. Once a value has been moved, it can no longer be used from the original location. See "Ownership and moves"
However, in your case, you want to use the message
string in multiple responses, which means you need to share it between multiple closures. This is where Arc
(Atomic Reference Counting) comes in handy.
An Arc<T>
is a thread-safe reference-counted pointer that allows shared read access to a value of type T
. It can be cloned to create a new pointer to the same value, increasing the reference count.
You can update your serve_message
function to wrap the message
in an Arc
and then clone it for each request like this (playground):
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);
}
}
The way it works is:
Arc::new(message)
creates a newArc
that owns themessage
. This is the onlyArc
that directly owns themessage
.- Each time
make_service_fn
is called, it clones theArc
(not themessage
itself), which increases the reference count but does not move themessage
. - Inside
service_fn
, we again clone theArc
for each request. This allows each request to have a reference to themessage
without taking ownership. - Finally,
Response::new(Body::from((*Arc::clone(&message)).clone()))
clones theArc
one more time to use themessage
in the response. This does not move themessage
and allows it to be used in subsequent responses.
Regarding the last point:
The code Response::new(Body::from(Arc::clone(&message).to_string()))
would work as well, but there is a key difference to keep in mind.
When you use Arc::clone(&message).to_string()
, you are creating a new String
for every request. This can be inefficient if the message
is large or if there are a lot of requests, because it involves allocating memory for a new String
each time.
Instead, let message = Bytes::copy_from_slice(&*Arc::clone(&message))
is of type Bytes
.
In this code, we're using Bytes::copy_from_slice(&*Arc::clone(&message))
to create a new Bytes
instance from the shared Bytes
for each request.
This is less efficient than sharing the Bytes
directly (as we would ideally like to do), but it avoids the borrowing errors you were encountering earlier.
The breakdown is:
Arc::clone(&message)
creates a newArc
that points to the sameBytes
value thatmessage
points to. The type ofArc::clone(&message)
isArc<Bytes>
.&*Arc::clone(&message)
dereferences theArc<Bytes>
to get a reference to theBytes
value it points to. The type of&*Arc::clone(&message)
is&Bytes
.Bytes::copy_from_slice(&*Arc::clone(&message))
creates a newBytes
value that contains the same byte sequence as theBytes
valuemessage
points to. The type ofBytes::copy_from_slice(&*Arc::clone(&message))
isBytes
.
The Bytes::copy_from_slice
function expects a reference to a byte slice (&[u8]
), and &Bytes
can be used as &[u8]
because Bytes
implements Deref<Target=[u8]>
.
So the overall effect of Bytes::copy_from_slice(&*Arc::clone(&message))
is to create a new Bytes
value that contains a copy of the bytes from the original Bytes
value, allowing it to be used independently in the response body.
Dereferencing Arc<Bytes>
to get a Bytes
reference (&Bytes
) doesn't work is because Body::from
doesn't accept a &Bytes
reference as a parameter.
While Rust's From
trait often does work with references, in this case, the From
trait is only implemented for the owned Bytes
type and not for a reference to Bytes
.
Body::from
consumes its argument. It takes ownership of the provided value.- When you dereference
Arc<Bytes>
, you get a&Bytes
(a reference toBytes
), not an ownedBytes
. - A
&Bytes
is not the same thing asBytes
. They are different types. The former is a reference to aBytes
value, the latter is an ownedBytes
value. - Because
Body::from
is not implemented for&Bytes
, you can't pass a&Bytes
toBody::from
.
The Hyper example you linked creates an HTTP response with a body of type hyper::Body::empty()
, which does not involve any borrowing from the outer scope.
In your code, you are trying to use a String
(message
) from the outer scope inside your service function, which leads to the borrowing issues you are encountering.
Your use case is different from the Hyper example because you want to share an owned String
between multiple closures, which requires the use of Arc
as explained above. The Arc
allows multiple closures to have a read-only reference to the same String
without taking ownership of it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论