3

I want to pass a pre-compiled json schema to actix web, but the compiler complains that the borrowed Value used to create the JSONSchema does not live long enough. Is there a way to workaround this?

Example:

use jsonschema::JSONSchema;
use serde_json::from_str;
use actix_web::{web, get, App, HttpServer, HttpResponse, Responder};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        let schema_str = include_str!("schema.json");
        let schema_value = from_str(schema_str).unwrap();
        let schema_compiled = JSONSchema::compile(&schema_value).unwrap();
        App::new()
            .data(schema_compiled) // fixme: compiles if commented out
            .service(index)
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

#[get("/")]
async fn index<'a>(_schema: web::Data<JSONSchema<'a>>) -> impl Responder {
    HttpResponse::Ok().finish() // todo: use schema for something
}

Error from rustc:

error[E0597]: `schema_value` does not live long enough
  --> src/main.rs:10:51
   |
10 |         let schema_compiled = JSONSchema::compile(&schema_value).unwrap();
   |                               --------------------^^^^^^^^^^^^^-
   |                               |                   |
   |                               |                   borrowed value does not live long enough
   |                               argument requires that `schema_value` is borrowed for `'static`
...
14 |     })
   |     - `schema_value` dropped here while still borrowed

I'm new to rust, so apologise if this is a generic rust question in disguise (and will happily modify the question with a smaller reproducible once my understanding is improved).

Matt Roberts
  • 1,107
  • 10
  • 15
  • I think you might be confusing `&' static T` with `T: 'static` [which are not the same thing](https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md#2-if-t-static-then-t-must-be-valid-for-the-entire-program). – pretzelhammer Feb 16 '21 at 20:29
  • See also [The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want](https://stackoverflow.com/q/40053550/155423) – Shepmaster Feb 16 '21 at 20:43
  • I recommend you add `#![deny(rust_2018_idioms)]` — does that' help clarify the problem? – Shepmaster Feb 16 '21 at 20:43
  • Thank you all for the clarifications and suggestions. I will keep reading / tweaking and report back. – Matt Roberts Feb 17 '21 at 07:30
  • Reporting back... I solved on my lunch break and hope to update this question / answer tonight. Turned out to be more involved than I expected, but a great learning experience. Thank you again. – Matt Roberts Feb 18 '21 at 13:56
  • Could you share your answer @MattRoberts – Njuguna Mureithi Feb 20 '21 at 09:07
  • 1
    Sorry for the delay @NjugunaMureithi. I've just added my answer. I hope it is useful to you. Cheers, Matt. – Matt Roberts Feb 21 '21 at 15:28

1 Answers1

3

The root cause of the problem is that JSONSchema does not own Value, but we can work around that. First we place the Value on the stack using Box::new. Then we leak a reference (which will last for the lifetime of the application) using Box::leak. Finally we use Arc::new so that we can call clone() on the schema in the inner scope (this last step allows you to move the schema code elsewhere, which is nice).

use jsonschema::JSONSchema;
use serde_json::{from_str, Value};
use actix_web::{web, get, App, HttpServer, HttpResponse, Responder};
use std::sync::Arc;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let schema_str = include_str!("schema.json");
    let schema_value:  &'static Value = Box::leak(Box::new(from_str(schema_str).unwrap()));
    let schema_compiled: JSONSchema<'static> = JSONSchema::compile(schema_value).unwrap();
    let schema_arc = Arc::new(schema_compiled);
    HttpServer::new(move || {
        App::new()
            .data(schema_arc.clone())
            .service(index)
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

#[get("/")]
async fn index<'a>(_schema: web::Data<Arc<JSONSchema<'a>>>) -> impl Responder {
    HttpResponse::Ok().finish() // todo: use schema for something
}
Matt Roberts
  • 1,107
  • 10
  • 15