8

I get a rust compiler error:

src/main.rs:33:31: 33:35 error: can't capture dynamic environment in a fn item; use the || { ... } closure form instead

The error occurs because I have a function in which I declare a variable using let, and then have an inner function in which I try to use this variable that is closed over.

I am fairly certain that this is a beginner question, so sorry in advance if this has a very straight forward answer!

Note that I am using this inner function as a callback somewhere, and thus using a closure, like

let closure_fn = | args | -> () { do stuff };

... is not going to be an appropriate solution for me.


extern crate nickel;

use std::io::net::ip::Ipv4Addr;
use nickel::{ Nickel, Request, Response };

fn stub_3rd_party_function() -> String {
    "hello world".to_string()
}

fn main() {

    let mut server = Nickel::new();

    // assume that the variable **must** be instantiated like this
    let hello_text : String = stub_3rd_party_function();

    fn hello_handler (_request: &Request, response: &mut Response) -> () {
        response.send(hello_text.as_slice());
    }
    server.get("/hello", hello_handler);

    server.listen(Ipv4Addr(0,0,0,0), 6767);
}

Results in the following error:

src/test.rs:12:23: 12:33 error: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
src/test.rs:12         response.send(hello_text);
                                     ^~~~~~~~~~
src/test.rs:12:23: 12:33 error: unresolved name `hello_text`.
src/test.rs:12         response.send(hello_text);
                                     ^~~~~~~~~~
error: aborting due to 2 previous errors

Now, I switch from a standard function to a closure function instead:

extern crate nickel;

use std::io::net::ip::Ipv4Addr;
use nickel::{ Nickel, Request, Response };

fn stub_3rd_party_function() -> String {
    "hello world".to_string()
}

fn main() {

    let mut server = Nickel::new();

    // assume that the variable **must** be instantiated like this
    let hello_text : String = stub_3rd_party_function();

    let hello_handler = |_request: &Request, response: &mut Response| -> () {
        response.send(hello_text.as_slice());
    };
    server.get("/hello", hello_handler);

    server.listen(Ipv4Addr(0,0,0,0), 6767);
}

Results in a different error:

src/test.rs:21:30: 21:43 error: mismatched types: expected `fn(&nickel::request::Request<'_>, &mut nickel::response::Response<'_,'_>)` but found `|&nickel::request::Request<'_>, &mut nickel::response::Response<'_,'_>|` (expected extern fn but found fn)
src/test.rs:21         server.get("/hello", hello_handler);
                                            ^~~~~~~~~~~~~
error: aborting due to previous error

Is there perhaps a way to "wrap" the closed over function with a normal one?

Since the library that I am using expects a standard function instead of a closure, I cannot use a closure. But if I do not use a closure, I cannot close over variables that are defined within the outer function, fn main () { ... }... and thus getting stuck here.

Note that above, I am using a string, hello_text, for the purposes of providing a concise code example. In this case using a static variable would suffice. However, static variables will not fix it for me, as I need to be able to assign a variable from within a fn main() the result of a function call, and then use that within my inner handler function.

bojangle
  • 888
  • 1
  • 8
  • 14

1 Answers1

3

It says that because it’s the simple truth: a function cannot capture variables; if you put a function inside another function, that the function is inside the function rather than outside is purely a matter of namespacing and making it absolutely private and inaccessible to anything else. If you want such variable capturing, you must use a closure.

In your specific case, functions are the only way. You should consider your code to be this (I would write it this way, too, to reduce indentation if nothing else):

fn hello_handler(_request: &Request, response: &mut Response) {
    response.send(hello_text);
}

fn main() {
    let mut server = Nickel::new();
    let hello_text = "hello world";
    server.get("/hello", hello_handler);
    server.listen(Ipv4Addr(0, 0, 0, 0), 6767);
}

As you can see with this way of expressing it, hello_text is evidently inaccessible from hello_handler. There are sound technical reasons why it must be so, also—each request is handled in its own task. In this particular case, a static is the solution:

static HELLO_TEXT: &'static str = "hello world";
Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • ... and now another update, to show a different compiler error that I get when I switch to a closure function. – bojangle Jul 31 '14 at 23:46
  • @ chrismorgan - Yes, I did indeed consider using static.. but that felt like cheating. Additionally, I used a string here to reduce the complexity of an example. The item that I actually need to close over is returned by a call to a function from another `extern` library; hence static would not work here. I need a way to assign something to a variable within `fn main()`, and then use that variable within my handler function, `fn hello_handler()`. – bojangle Aug 01 '14 at 01:20
  • 1
    … cheating? It’s the only way to do it. Because of the task model, you may not have any shared mutable state between requests—your requests must be stateless. Of course, this isn’t *quite* true, there are things like `Arc`, `RWLock`, `Mutex` et al, but forget them for the moment. What *actually* are you wanting to do? – Chris Morgan Aug 01 '14 at 04:55
  • Ok got it. I would like to be able to instantiate a database connection object, and use that within handlers. – bojangle Aug 01 '14 at 05:05
  • Ah—now that is quite a different question. For starters, you won’t want *a* database connection object, you’ll want a pool, such as [`postgres::pool::PostgresConnectionPool`](http://www.rust-ci.org/sfackler/rust-postgres/doc/postgres/pool/struct.PostgresConnectionPool.html). As for how you’d hook that up with Nickel, you might need to use some form of middleware, and you will probably need to have some form of global (Nickel might have a way or you might want something like https://github.com/Kimundi/lazy-static.rs). I suggest asking in #rust-webdev on irc.mozilla.org. – Chris Morgan Aug 01 '14 at 07:08