由于生命周期,共享状态不起作用

Gil*_*iwu 4 rust rust-axum

我使用 Axum 框架构建一个简单的休息服务器。我想要一种“应用程序状态”,其中包含一些可重用的组件,供我的一些处理程序重用/使用。

#![allow(incomplete_features)]
#![feature(async_fn_in_trait)]

use axum::{routing::post, Router};
use std::{net::SocketAddr, sync::Arc};

mod modules;
use modules::auth::handlers::login;

pub struct AppState<'a> {
    user_credentials_repository: UserCredentialsRepository<'a>,
}

pub struct UserCredentialsRepository<'a> {
    shared_name: &'a mut String,
}

impl<'a> UserCredentialsRepository<'a> {
    pub fn new(shared_name: &'a mut String) -> UserCredentialsRepository<'a> {
        UserCredentialsRepository { shared_name }
    }
}

#[tokio::main]
async fn main() {
    let mut name = String::from("Tom");
    let mut user_credentials_repository = UserCredentialsRepository::new(&mut name);

    let shared_state = Arc::new(AppState {
        user_credentials_repository,
    });

    let app = Router::new()
        .route("/login", post(login))
        .with_state(shared_state);

    let addr = SocketAddr::from(([127, 0, 0, 1], 7777));

    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}
Run Code Online (Sandbox Code Playgroud)

基本上我想要实现的是重用数据库会话/连接实例。我首先尝试共享一个字符串来测试它,但它似乎不起作用。我收到以下错误:

`name` dropped here while still borrowed
Run Code Online (Sandbox Code Playgroud)

argument requires that `name` is borrowed for `'static`
Run Code Online (Sandbox Code Playgroud)

我对生命周期的工作方式有一定的了解,但我猜测“.with_state(...)”内部的某些内容取得了我传递的值的所有权并试图放弃它?

dre*_*ato 6

有两个问题:生命周期和共享可变性。

  1. 因为shared_state可能会被发送到另一个线程(由 tokio 运行时),所以 rust 不知道shared_state当 main 函数删除时是否仍然存在name&即使它不是&mut.
  2. 路由器状态需要Clone,但&mut类型未实现。这是因为,如果您收到多个请求,则可能有多个处理程序尝试同时访问同一状态。&mut对于同一变量同时存在多个变量是未定义的行为,并且在安全代码中通过&mut不允许存在来强制执行这一行为Clone

您尝试通过放入状态来解决#1 问题Arc在这里不起作用,因为它仍然包含引用。您需要将引用替换为Arc.

#2 的解决方案是使用共享可变性构造,例如Mutexor RwLock

首先,您需要删除引用:


pub struct UserCredentialsRepository {
    shared_name: String,
}

impl UserCredentialsRepository {
    pub fn new(shared_name: String) -> UserCredentialsRepository {
        UserCredentialsRepository { shared_name }
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然您可以直接替换&mutMutex并使其正常工作,但我会从更简单的事情开始。

让我们保留Arcmain用 包裹user_credentials_repository起来Mutex

pub struct AppState {
    user_credentials_repository: Mutex<UserCredentialsRepository>,
}
Run Code Online (Sandbox Code Playgroud)

然后在你的login函数中的某个地方,你需要lock读取Mutex和写入它:

let lock = state.user_credentials_repository.lock().unwrap();
lock.shared_name = "New name".to_string();
Run Code Online (Sandbox Code Playgroud)

这应该可以按预期编译和工作。

表现

如果您有许多可以单独访问的单独项目,您可能需要将 放在Mutex每个项目上(类似于您的原始结构):

pub struct AppState {
    user_credentials_repository: UserCredentialsRepository,
}

pub struct UserCredentialsRepository {
    shared_name: Mutex<String>,
    other_state: Mutex<String>,
}
Run Code Online (Sandbox Code Playgroud)

然后,单独的线程可以锁定单独的项目,而不会阻塞其中之一。

如果您期望频繁读取数据而不频繁写入数据,则可以使用RwLock,它允许任意数量的读锁,只要没有写锁即可:

pub struct AppState {
    user_credentials_repository: RwLock<UserCredentialsRepository>,
}
Run Code Online (Sandbox Code Playgroud)

用法与 几乎相同Mutex,但不是lock方法,而是readwrite