5.0 KiB
State
Many web applications have a need to maintain state. This can be as simple as maintaining a counter for the number of visits or as complex as needing to access job queues and multiple databases. Rocket provides the tools to enable these kinds of interactions in a safe and simple manner.
Managed State
The enabling feature for maintaining state is managed state. Managed state, as the name implies, is state that Rocket manages for your application. The state is managed on a per-type basis: Rocket will manage at most one value of a given type.
The process for using managed state is simple:
- Call
manage
on theRocket
instance corresponding to your application with the initial value of the state. - Add a
State<T>
type to any request handler, whereT
is the type of the value passed intomanage
.
Adding State
To instruct Rocket to manage state for your application, call the
manage method
on a Rocket
instance. For example, to ask Rocket to manage a HitCount
structure with an internal AtomicUsize
with an initial value of 0
, we can
write the following:
struct HitCount(AtomicUsize);
rocket::ignite().manage(HitCount(AtomicUsize::new(0)));
The manage
method can be called any number of times as long as each call
refers to a value of a different type. For instance, to have Rocket manage both
a HitCount
value and a Config
value, we can write:
rocket::ignite()
.manage(HitCount(AtomicUsize::new(0)))
.manage(Config::from(user_input));
Retrieving State
State that is being managed by Rocket can be retrieved via the
State type: a request
guard for managed state. To use the request
guard, add a State<T>
type to any request handler, where T
is the
type of the managed state. For example, we can retrieve and respond with the
current HitCount
in a count
route as follows:
#[get("/count")]
fn count(hit_count: State<HitCount>) -> String {
let current_count = hit_count.0.load(Ordering::Relaxed);
format!("Number of visits: {}", current_count)
}
You can retrieve more than one State
type in a single route as well:
#[get("/state")]
fn state(hit_count: State<HitCount>, config: State<Config>) -> T { ... }
It can also be useful to retrieve managed state from a FromRequest
implementation. To do so, invoke the from_request
method of a State<T>
type
directly, passing in the req
parameter of from_request
:
fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> {
let count = match <State<HitCount> as FromRequest>::from_request(req) {
Outcome::Success(count) => count,
...
};
...
}
Unmanaged State
If you request a State<T>
for a T
that is not managed
, Rocket won't call
the offending route. Instead, Rocket will log an error message and return a
500 error to the client.
While this behavior is 100% safe, it isn't fun to return 500 errors to
clients, especially when the issue can be easily avoided. Because of this,
Rocket tries to prevent an application with unmanaged state from ever running
via the unmanaged_state
lint. The lint reads through your code at compile-time
and emits a warning when a State<T>
request guard is being used in a mounted
route for a type T
that isn't being managed.
As an example, consider the following short application using our HitCount
type from previous examples:
#[get("/count")]
fn count(hit_count: State<HitCount>) -> String {
let current_count = hit_count.0.load(Ordering::Relaxed);
format!("Number of visits: {}", current_count)
}
fn main() {
rocket::ignite()
.manage(Config::from(user_input))
.launch()
}
The application is buggy: a value for HitCount
isn't being managed
, but a
State<HitCount>
type is being requested in the count
route. When we compile
this application, Rocket emits the following warning:
warning: HitCount is not currently being managed by Rocket
--> src/main.rs:2:17
|
2 | fn count(hit_count: State<HitCount>) -> String {
| ^^^^^^^^^^^^^^^
|
= note: this State request guard will always fail
help: maybe add a call to 'manage' here?
--> src/main.rs:8:5
|
8 | rocket::ignite()
| ^^^^^^^^^^^^^^^^
The unmanaged_state
lint isn't perfect. In particular, it cannot track calls
to manage
across function boundaries. You can disable the lint on a per-route
basis by adding #[allow(unmanaged_state)]
to a route handler. If you wish to
disable the lint globally, add #![allow(unmanaged_state)]
to your crate
attributes.
You can find a complete example using the HitCounter
structure in the state
example on
GitHub and
learn more about the manage
method and
State type in the API docs.