Search code examples
rustrust-rocket

How to return a State value from a Rocket endpoint?


I'm storing data from an external service in a local cache and I want to create an endpoint to return data currently in the cache.

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

use rocket::{Route, State};
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct Post {
    pub title: String,
    pub body: String,
}

impl Post {
    pub fn new(title: String, body: String) -> Post {
        Post { title, body }
    }
}

pub struct Cache {
    pub posts: Vec<Post>,
}

impl Cache {
    pub fn new() -> Cache {
        let mut posts = vec![
            Post::new(String::from("Article1"), String::from("Blah")),
            Post::new(String::from("Article2"), String::from("Blah")),
            Post::new(String::from("Article3"), String::from("Blah")),
            Post::new(String::from("Article4"), String::from("Blah")),
            Post::new(String::from("Article5"), String::from("Blah")),
        ];

        Cache { posts }
    }
}

#[derive(Responder)]
pub enum IndexResponder {
    #[response(status = 200)]
    Found(Json<Vec<Post>>),
    #[response(status = 404)]
    NotFound(String),
}

#[get("/")]
pub fn index(cache: State<Cache>) -> IndexResponder {
    return IndexResponder::Found(Json(cache.posts));
}

fn main() {
    rocket::ignite()
        .mount("/", routes![index])
        .manage(Cache::new())
        .launch();
}

The compiler complains:

error[E0507]: cannot move out of dereference of `rocket::State<'_, Cache>`
  --> src/main.rs:50:39
   |
50 |     return IndexResponder::Found(Json(cache.posts));
   |                                       ^^^^^^^^^^^ move occurs because value has type `std::vec::Vec<Post>`, which does not implement the `Copy` trait

(cargo build --verbose output)

My Cargo.toml file:

[package]
name = "debug-project"
version = "0.1.0"
authors = ["Varkal <[email protected]>"]
edition = "2018"

[dependencies]
rocket = "0.4.0"
rocket_contrib = "0.4.0"
serde = { version = "1.0.90", features = ["derive"] }
serde_json = "1.0.39"

A very simple repository to reproduce the error.


Solution

  • Your endpoint does not own the state and thus it cannot return an owned Vec<Post>. Conceptually, this makes sense because if you did take ownership of it, then what value would be present the next time the endpoint is called?

    The simplest thing you can do is clone the data:

    #[get("/")]
    pub fn index(cache: State<Cache>) -> IndexResponder {
        IndexResponder::Found(Json(cache.posts.clone()))
    }
    

    This will require that you implement Clone for Post, or perhaps change your state to hold something like an Arc.

    A slightly more performant solution is to return a slice that refers to the state. This requires no cloning, but does require using the State::inner method:

    #[derive(Responder)]
    pub enum IndexResponder<'a> {
        #[response(status = 200)]
        Found(Json<&'a [Post]>),
        #[response(status = 404)]
        NotFound(String),
    }
    
    #[get("/")]
    pub fn index<'a>(cache: State<'a, Cache>) -> IndexResponder<'a> {
        IndexResponder::Found(Json(&cache.inner().posts))
    }
    

    See also: