1

I'm currently building a HTTP service exposing actions on a unique object. I already created the central object, with several methods taking immutable &self references, and using internally various efficient synchronization structures to access the data inside (all the code is unsafe-free). My thought was that this would be enough to make it safe to use concurrently.

And then comes the hard part of actually connecting it to a HTTP server. I'm currently trying to use Iron, but I could switch to Nickel.rs or any other if it makes things easier.

Most HTTP server examples I saw used stateless handlers, without any access to local variables. I now understand why: it's near-impossible to do.

Here is an example of what I'd like to do, using Nickel.rs: https://gist.github.com/Gyscos/42510a335098ce935848

Here is a similar failed attempt using Iron: https://gist.github.com/Gyscos/92e56e95baee0ebce78f

The basic idea being that obj only lives for the duration of the scope, but so does server, so it shouldn't be a big deal... right?

Unfortunately each of my attempts failed. When trying to give the server a closure that accesses self.object, I get an error saying that the closure might outlive the reference.

I saw Iron provided a shared memory module with a Read structure. Not only does it look overly complicated for my needs, I also would like to avoid paying the Arc price when I don't need it (I have a clear view of the lifecycle of the object and really don't need to count references).

The current solution I see would be to have a static Object and use that instead of one specific to MyServer, but I'd prefer to avoid this level of ugliness if possible.

I come from golang, where this is not a problem (I could use object-bound methods as handler for that). So my question is: how do you easily access a shared immutable reference from your HTTP handlers in Rust?

Gyscos
  • 1,772
  • 17
  • 22
  • I have an example that does what you want. https://github.com/tupshin/CastIron/blob/master/src/main.rs it creates a struct that wraps an local state that it needs to pass (note that Session is a FFI wrapper around a C++ thread-safe db session object), wraps it in an Arc, clones the Arc twice into explicitly named variables, and then uses closure move semantics to move each copy into the closure so it doesn't outlive the parent. That was the cleanest approach I could come up with. – Tupshin Harper May 02 '15 at 02:03
  • I wonder if [this answer](http://stackoverflow.com/a/29618235/155423) would be of any use, especially the paragraph beginning with "I'm no expert". – Shepmaster May 06 '15 at 14:31

1 Answers1

1

Note that I have no prior experience with Nickel or Iron (personally I'm using SCGI so far).

The error I got compiling your Nickel example is:

<nickel macros>:7:9: 7:70 error: captured variable `obj` does not outlive the enclosing closure

The error happens in the following snippet:

server.utilize(router! {
    get "/foo" => |_req, _res| {
        obj.foo();
    }
});

Now, router! is just a fancy macros to wrap your closure in a Middleware, with some additional checks built in. For our investigation we might want to get to the roots and use the Middleware directly.

Unfortunately Nickel explicitly requires the Middleware to have a 'static lifetime. That's a quirk of the Nickel API design and doesn't have much with Rust per se (except for the fact that Rust allows the library to require such things from the user).

I see two options then. First is to use our superior knowledge of the object lifetime (Nickel doesn't know that our object outlives the server but we do) and tell the compiler so. Compiler allows us to show our superiour knowledge with the helpful unsafe and transmute primitives.

Here it is, working: unsafe.rs.

In Rust unsafe means "this piece of code is safe because the programmer said so". Every unsafe piece must satisfy this safety contract between the programmer and the compiler. In this case we know that the object outlives the server so the safety guarantee is maintained.

The second option is to pay the price for tricks that would satisfy the requirements of the Nickel API. Here we use a scoped thread-local storage for this: thread_local.rs.

Bottom line: Nickel API has requirements that make you jump through some hoops to get you where you want. I haven't investigated the Iron API. I have a feeling that you might have a better luck with the lower-level Hyper API.

ArtemGr
  • 11,684
  • 3
  • 52
  • 85
  • Thank you very much! I now understand the issues coming from the 'static requirement on the middleware. Unfortunately, it seems hyper does that too ( http://hyperium.github.io/hyper/hyper/server/struct.Server.html#method.http ), so it seems logical that iron and nickel, both using hyper, apply the same restrictions... I didn't know about thread_local, it seems interesting, I will investigate. – Gyscos May 06 '15 at 16:40
  • For now, I'm working an a simple private project, so I think I'll go the unsafe route, but I'm curious - what is the price for thread_local? Is this a simple unsafe wrapper, or is there an actual performance impact? Oh, or this means multiple HTTP calls will end up in different threads, and access independent copies of the data? – Gyscos May 06 '15 at 17:37
  • You're welcome! I know that some thread-locals can be very fast (https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Thread-Local.html) but I don't know yet how they fare in Rust. The thread-local will only be available on the same thread that set it. If the web-server uses multiple threads then you'll have to adjust your implementation strategy. – ArtemGr May 06 '15 at 18:43