Closure et temps de vie

Le problème exposé dans ce sujet a été résolu.

Coucou,

J’essaye de me mettre au Rust. Je tente des petits trucs de temps en temps.

Là, j’essaye de faire un serveur HTTP léger et rapide. J’essaye d’utiliser "hyper" et un système de template "handlebars".

J’ai un problème que je ne comprend pas trop. Pour l’instant j’ai ce code là :

extern crate futures;
extern crate hyper;
extern crate handlebars;

use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
use handlebars::Handlebars;
use futures::Future;
use hyper::{Body, Response, Server};
use hyper::service::service_fn_ok;


fn main() {
    let addr = "127.0.0.1:2337".parse().unwrap();

    let service = || {
        service_fn_ok(|_| {
            let mut handlebars = Handlebars::new();
            let mut file = File::open("test.html").ok().expect("");
            let mut contents = String::new();
            file.read_to_string(&mut contents);
            assert!(handlebars.register_template_string("main", contents).is_ok());
            let handlebars = handlebars;

            let mut data = BTreeMap::new();
            data.insert("title".to_string(), "Super micro service !");

            Response::new(Body::from(handlebars.render("main", &data).unwrap()))
        })
    };


    let server = Server::bind(&addr)
        .serve(service)
        .map_err(|e| eprintln!("Error: {}", e));

    hyper::rt::run(server);
}

Mais là, je crée un objet handelbars à chaque requête alors que le fichier est toujours le même (les données varient parfois, mais là même pas).

Du coup, j’ai essayé de sortir l’allocation de handlebars hors de la closure…

extern crate futures;
extern crate hyper;
extern crate handlebars;

use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
use handlebars::Handlebars;
use futures::Future;
use hyper::{Body, Response, Server};
use hyper::service::service_fn_ok;


fn main() {
    let addr = "127.0.0.1:2337".parse().unwrap();

    let mut handlebars = Handlebars::new();
    let mut file = File::open("test.html").ok().expect("");
    let mut contents = String::new();
    file.read_to_string(&mut contents);
    assert!(handlebars.register_template_string("main", contents).is_ok());
    let handlebars = handlebars;

    let service = || {
        service_fn_ok(|_| {
            let mut data = BTreeMap::new();
            data.insert("title".to_string(), "Super micro service !");

            Response::new(Body::from(handlebars.render("main", &data).unwrap()))
        })
    };


    let server = Server::bind(&addr)
        .serve(service)
        .map_err(|e| eprintln!("Error: {}", e));

    hyper::rt::run(server);
}

Mais j’ai cette erreur :

error[E0597]: `handlebars` does not live long enough
  --> src/main.rs:29:38
   |
24 |       let service = || {
   |                     -- value captured here
25 | /         service_fn_ok(|_| {
26 | |             let mut data = BTreeMap::new();
27 | |             data.insert("title".to_string(), "Super micro service !");
28 | |
29 | |             Response::new(Body::from(handlebars.render("main", &data).unwrap()))
   | |                                      ^^^^^^^^^^ borrowed value does not live long enough
30 | |         })
   | |__________- returning this value requires that `handlebars` is borrowed for `'static`
...
39 |   }
   |   - `handlebars` dropped here while still borrowed

Ce que je ne comprend pas c’est que justement handlebars a déjà la plus grosse portée possible, elle est dans la fonction main ! Plus grosses que ça, c’est globale hors de toute fonction (j’ai jamais essayé mais ce n’est pas ce que je souhaite ici).

Bref, il y a quelque chose qui m’échappe ici. La première closure « emprunte » la variable handlerbars et donc la seconde closure n’est pas capable de l’emprunter à nouveau ?

J’ai bien cet exemple pour m’aider. Mais Registrery (le type de handlebars) n’implémente pas le traie Copy.

Bref, je suis un peu perdu, si quelqu’un à une idée de comment mettre en cache handlebars pour toutes les connections. (Sachant que si je place l’allocation dans la première closure ça compile mais par-contre, l’allocation se fait à chaque connexion. Ce qui concrètement m’est très peu utile1).

Merci d’avance !


  1. Ça me permet de faire curl localhost:2337 localhost:2337, mais concrètement, je vais souvent fermer puis réouvrir la connexion.

+0 -0

Hello, du coup c’est pas un problème de portée mais de lifetime.

   |   - `handlebars` dropped here while still borrowed

Ça dit «on a tué handlebars alors qu’il était encore emprunté par quelqu’un», quelqu’un qui ici était la lambda. Si tu veux résoudre ce problème, tu dois donner l’ownership à la lambda, en la préfixant par move : https://doc.rust-lang.org/rust-by-example/fn/closures/capture.html

Maintenant, dans ton code, tu as deux closures qui sont imbriquées, donc mettre move sur les deux va te donner:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src/main.rs:24:19
   |
24 |     let service = move || {
   |                   ^^^^^^^ this closure implements `FnOnce`, not `Fn`
25 |         service_fn_ok(move |_| {
   |                       -------- closure is `FnOnce` because it moves the variable `handlebars` out of its environment
...
35 |         .serve(service)
   |          ----- the requirement to implement `Fn` derives from here

error: aborting due to previous error

Pour comprendre pourquoi on est dans cette situation, on peut tricher en imbriquant handlebars dans un std::rc::Rc:

error[E0277]: `std::rc::Rc<handlebars::Handlebars>` cannot be shared between threads safely
  --> src/main.rs:36:10
   |
36 |         .serve(service)
   |          ^^^^^ `std::rc::Rc<handlebars::Handlebars>` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `std::rc::Rc<handlebars::Handlebars>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `&std::rc::Rc<handlebars::Handlebars>`
   = note: required because it appears within the type `[closure@src/main.rs:26:23: 31:10 handlebars:&std::rc::Rc<handlebars::Handlebars>]`

Et on voit ici qu’en fait ce que tu as dans ta lambda doit être thread-safe.

En fait, il va falloir faire la même chose que pour cet exemple finalement : https://doc.rust-lang.org/std/sync/struct.Arc.html#examples

Merci, je comprend bien la distinction entre portée et durée de vie. Je fais un peu le mélange alors que pourtant la distinction est bien nette.

J’avais bien essayer de rajouter move aux deux lambdas mais clairement, je comprenais pas plus le message d’erreur. L’astuce avec RS donne une explication bien plus claire.

J’ai essayé ça :

    let handlebars = handlebars;
    let handlebars = Arc::new(handlebars);

    let service = || {
        let handlebars = Arc::clone(&handlebars);
        service_fn_ok( |_| {
            let mut data = BTreeMap::new();
            data.insert("title".to_string(), "Super micro service !");

            Response::new(Body::from(handlebars.render("main", &data).unwrap()))
        })
    };

J’abuse un peu du masquage, mais j’adore le principe. Le concept, c’est que cloner Arc, c’est pas grand chose. Ça ne coûte quasiment rien ? Où alors je l’utilise mal ?

Merci en tout cas ^^

+0 -0

Merci beaucoup !

J’ai depuis pas mal étudié le sujet de Reference Counter. Je connaissais le concepts pour les systèmes de fichier mais je n’avais que vaguement entendu parler de la chose en C++.

C’était effectivement ce qu’il me manquait.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte