Что такое web::Data<T> в Actix-web и как разделить состояние приложения?
web::Data<T> — это Arc<T>, зарегистрированный в App через .app_data() и автоматически инжектируемый в обработчики. Позволяет разделять пул соединений с БД, HTTP-клиент или конфигурацию между всеми рабочими потоками без глобальных переменных.
Что такое web::Data<T>
web::Data<T> — это обёртка над Arc<T>, которая регистрирует значение в контейнере приложения и автоматически инжектируется в обработчики как экстрактор. Это основной способ передавать разделяемое состояние (пул соединений с БД, HTTP-клиент, кэш) во все обработчики без глобальных переменных.
Как это работает
При вызове App::new().app_data(web::Data::new(value)) значение оборачивается в Arc и сохраняется в реестре приложения. Когда обработчик объявляет параметр data: web::Data<T>, Actix ищет зарегистрированное значение нужного типа и клонирует Arc (дёшево — только счётчик ссылок).
Пример: пул соединений SQLx
use actix_web::{get, web, App, HttpResponse, HttpServer};
use sqlx::PgPool;
// Состояние приложения
struct AppState {
db: PgPool,
app_name: String,
}
#[get("/users/{id}")]
async fn get_user(
path: web::Path<i64>,
data: web::Data<AppState>,
) -> HttpResponse {
let id = path.into_inner();
match sqlx::query!("SELECT name FROM users WHERE id = $1", id)
.fetch_one(&data.db)
.await
{
Ok(row) => HttpResponse::Ok().json(row.name),
Err(_) => HttpResponse::NotFound().finish(),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let pool = PgPool::connect("postgres://user:pass@localhost/mydb")
.await
.expect("DB connection failed");
let state = web::Data::new(AppState {
db: pool,
app_name: "MyApp".to_string(),
});
HttpServer::new(move || {
App::new()
.app_data(state.clone()) // clone Arc, не данные!
.service(get_user)
})
.bind("0.0.0.0:8080")?
.run()
.await
}
Изменяемое состояние
Если состояние нужно изменять из обработчиков, оберните его в Mutex или RwLock внутри Data:
use std::sync::Mutex;
struct Counter {
value: Mutex<u64>,
}
#[get("/count")]
async fn increment(data: web::Data<Counter>) -> HttpResponse {
let mut val = data.value.lock().unwrap();
*val += 1;
HttpResponse::Ok().body(val.to_string())
}
Несколько независимых состояний
Можно зарегистрировать несколько разных типов — Actix различает их по типу T:
App::new()
.app_data(web::Data::new(db_pool))
.app_data(web::Data::new(redis_client))
.app_data(web::Data::new(config))
Подводные камни
- Замыкание
HttpServer::new(|| App::new()...)вызывается для каждого потока — данные нужно захватить черезmoveи клонироватьArc(state.clone()), а не перемещать - Если забыть
app_data(), Actix вернёт 500 с сообщением «App data is not configured» — ошибка не компиляционная, а runtime Mutex::lock().unwrap()паникует при отравленном мьютексе — лучше использоватьparking_lot::Mutexбез отравления- Использование
std::sync::Mutexблокирует async-поток; для долгих операций нуженtokio::sync::Mutex - Тип
Tдолжен бытьSend + Sync + 'static— сырые указатели и RefCell не пройдут - Данные живут всё время работы сервера — утечки состояния (например, кэш без TTL) накапливаются и растут в памяти
- Не передавайте секреты (API-ключи) через
web::Dataв виде plainString— оберните в newtype или используйтеsecrecy::Secret
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.