bounty还有16小时到期。回答此问题可获得+100声望奖励。Tim Perry希望引起更多注意这个问题:
我真的很感激一个“正确”的方式来做到这一点的例子,为什么。
我正在尝试学习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的结构体中,以及许多其他方法,但我很挣扎,每一种方法似乎都让我回到了上面同样的基本问题。我显然错过了一些关于异步、闭包和所有权如何交互的重要内容,以及管理这些内容的工具。我见过类似的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那个例子似乎没有遇到任何问题,而这个例子却遇到了。
什么是正确的和惯用的方法来做到这一点,为什么它的工作,什么是这个和那个超级例子的情况下的区别?初学者水平的解释非常赞赏。
1条答案
按热度按时间tf7tbtn21#
出现此错误的原因是
message
字符串被移动到传递给service_fn
的闭包中。在Rust中,每个值都有一个唯一的所有者,移动一个值会转移其所有权。一旦移动了值,就不能再从原始位置使用它。参见“Ownership and moves”但是,在您的例子中,您希望在多个响应中使用
message
字符串,这意味着您需要在多个闭包之间共享它。这就是Arc
(原子引用计数)派上用场的地方。Arc<T>
是一个线程安全的引用计数指针,允许对T
类型的值进行共享读取访问。它可以被克隆以创建指向相同值的新指针,从而增加引用计数。您可以更新
serve_message
函数,将message
Package 在Arc
中,然后为每个请求like this (playground)克隆它:它的工作方式是:
Arc::new(message)
创建一个拥有message
的新Arc
。这是唯一直接拥有message
的Arc
。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
,它指向与message
相同的Bytes
值。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
。Bytes::copy_from_slice
函数需要一个字节片(&[u8]
)的引用,而&Bytes
可以用作&[u8]
,因为Bytes
实现了Deref<Target=[u8]>
。因此,
Bytes::copy_from_slice(&*Arc::clone(&message))
的总体效果是创建一个新的Bytes
值,该值包含原始Bytes
值的字节副本,允许它在响应体中独立使用。解引用
Arc<Bytes>
以获得Bytes
引用(&Bytes
)不起作用,因为Body::from
不接受&Bytes
引用作为参数。虽然Rust的
From
trait通常与引用一起工作,但在这种情况下,From
trait仅针对拥有的Bytes
类型实现,而不是针对Bytes
的引用。Body::from
消耗其参数。它获取所提供值的所有权。Arc<Bytes>
时,您将得到一个&Bytes
(对Bytes
的引用),而不是一个拥有的Bytes
。&Bytes
与Bytes
不是一回事。他们是不同的类型。前者是对Bytes
值的引用,后者是 ownedBytes
值。Body::from
不是为&Bytes
实现的,所以不能将&Bytes
传递给Body::from
。您所链接的Hyper示例创建了一个HTTP响应,其主体类型为
hyper::Body::empty()
,这并不涉及从外部作用域的任何借用。在您的代码中,您试图使用服务函数外部作用域中的
String
(message
),这会导致您遇到的借用问题。您的用例与Hyper示例不同,因为您希望在多个闭包之间共享拥有的
String
,这需要使用Arc
,如上文所述。Arc
允许多个闭包对同一个String
有一个只读引用,而不需要获得它的所有权。