Announcing Gotham


#1

For the last eight months, we’ve been hard at work on a project that we’re thrilled to be able to share with the wider Rust community.

We know it as Gotham and today we’re releasing 0.1.

Gotham is a flexible web framework that does not sacrifice safety, security or speed. The Gotham core team loves many of the elegant concepts that are found in dynamically typed web application frameworks, such as Rails/Phoenix/Django and aspire to achieve them with the type and memory safety guarantees provided by Rust.

Gotham is stability focused. With our release of Gotham 0.1, we’re compatible with Rust stable and every future release of Gotham will maintain that contract. Naturally, we build on beta and nightly as well so if you’re on the bleeding edge Gotham is good to go.

Gotham leverages async extensively thanks to the Tokio project and is further enhanced by being built directly on top of async Hyper. Completing web requests in µs with almost non-existent memory footprints is still taking some getting used to.

We wanted to get Gotham in the hands of the Rust community early with regular smaller iterations to follow. The Gotham 0.1 release includes the following features:

  • Handlers and Controllers
  • Advanced Routing
  • Type Safe Extractors
  • Middleware and Pipelines
  • Request State
  • Sessions
  • Request and Response helpers
  • Test helpers
  • Thoroughly documented code and the beginnings of a Gotham book

There are some important features still to be built and we hope that the community will help us define even more. Right now our roadmap includes:

  • Enhancing our Router API with builders/macros to make this much more comfortable for folks used to defining routes in Rails or Phoenix
  • Compiled Templates
  • Form extraction
  • First class Diesel integration
  • Async static file serving
  • i18n
  • Hot reload during development
  • Structured logging

You can find out more about Gotham at https://gotham.rs. We look forward to welcoming you into the Gotham community.

Finally, we’d like to say a very sincere thank you to the developers and communities of every dependency we’ve built Gotham on top of, including of course, Rust itself. Your work is amazing and we could not have gotten here without it. We look forward to working with you all in the future.

@bradleybeddoes and @smangelsdorf


Seeking idea for Bachelor thesis
What's everyone working on this week (33/2017)?
Is anyone working on any web framework using the new Rust async features?
#2

First, congrats and great job! :+1:

Second, I’m sure lots of people will want to know the differences (or pros and cons) of Gotham vs Rocket. As a start, I suppose the fact Gotham can run on stable is a major difference. Another would be that it uses async Hyper whereas Rocket is (still) sync.

Any other things worth pointing out?


#3

Both projects have had a lot of hard work put into them and we wouldn’t want to make direct comparisons, there are simply different design decisions that have been made, both of which have their place in the ecosystem.

I can say that we’re really proud of our usage of stable Rust, our async types and the way we’ve implemented Pipelines and Middleware. We’re also really happy with how our Handler concept has worked out.

Another aspect of Gotham we really like is our Router. It is backed by a Tree data structure and can solve some very complex routing needs including delegating routing decisions to secondary Routers to allow for what we call “modular applications”. I think of this as being half way between microservices and monoliths, combining the best of both. The Router will be a big focus for 0.2, leveraging macros and/or builders to make route definitions much more along the lines of what you’d see in Rails or Phoenix.

We look forward to feedback from the community, I am sure there is a lot we could be doing better or haven’t thought of.


#4

On that topic, looks like Gotham is using boxed futures in the handler/middleware chain. This is an eye sore with Tokio/Rust as a whole at the moment, so not Gotham specific per se. I assume you intend to use impl trait and/or the async/await stuff once they’re stabilized?


#5

To say the least. While I’m excited about impl trait, it’s not going to be a silver bullet that solves all problems. It’s my understanding that you will need need a single return type from any function that returns an impl trait. That is, there can be only one type that is returned. There are many situations where a wrapper type will still be needed to resolve to a single type on return…


#6

Are there such bullets in general? :slight_smile:

Yes.

Many, maybe - depends on size and complexity of application. Most? Probably not I’d venture. And in the cases where you have different shape return types it may be ok to box them up because those places aren’t on the hot path. Or maybe code/design can be restructured. So practically speaking, you don’t need 100% coverage here. But, time/experience will tell.


#7

I’ve actually not (yet) read enough about this to make an informed comment.

I can say that we want Gotham to continue leverage best practices adopted by the Rust community, for example the HTTP crate which was announced recently makes a lot of sense to us and we want to add support for it in the very near future.


#8

As an alternative to boxes, can you use an enum with variants for possible return types? Avoids the vtable lookup.


#9

Yeah, an enum is just as good as a concrete struct if that makes sense. The issue isn’t really about that though. You really don’t want to expose these types in the API - all you really want to say is “I’m returning something concrete that implements Future<...> but I’m not telling you the concrete type name”. That’s what impl trait would help with. This is also particularly important when you start using combinators - each combinator layers another type onto the return type, and at some point it’s unwieldy.

As for the vtable lookup, I’d actually not be too concerned about that part in the context of a server. Unless the code is doing something utterly trivial, it likely won’t matter. The multiple heap allocations per request processing is what I’d consider problematic.


#10

Sorry I when I said “vtable” I kinda meant the boxing as well. Yeah I was thinking that impl trait might be a solution if you were trying to avoid leaking complicated types. I’ve seen this in diesel, you can get very complex type parameters if you want to store any diesel objects in your own structs etc.

EDIT: I saw somewhere something about trait aliases, because when I’ve wrote a function in diesel I’ve ended up doing

fn do_something_with_connection<C>(conn: &C) 
    where: // whole lotta stuff :P 
{

#11

As @bradleybeddoes mentioned, we want to continue to stick with best practices and idiomatic Rust code, and we’re certainly watching these language features as they evolve.

During early development, when creating the Middleware and Handler traits, we spoke about how we could deal with concrete types, and after exhausting our ideas we settled on boxed futures. The major issue with trying to use concrete future types is that we can’t name the return type of a function which uses future combinators. I’ve reproduced the experiment, and also attempted to use impl Trait (which we didn’t try at the time) here:

https://play.rust-lang.org/?gist=96a43142d6f821511feaf1493cfffd99&version=nightly

I’d appreciate any input on how we could move forward with this.


#12

Yeah, one approach is to push the type info into the middleware struct itself. So instead of:

struct LazyMiddleware;
impl<Chain, T> Middleware<Chain, T> for LazyMiddleware
where
    Chain: FnOnce(Request) -> T,
    T: IntoFuture<Item = Response, Error = Error>,
{
    type Output = Lazy<fn() -> FutureResult<Response, Error>, FutureResult<Response, Error>>;

    fn call(self, req: Request, chain: Chain) -> Self::Output {
        future::lazy(build_response)
    }
}

// Just to support the above code.
fn build_response() -> FutureResult<Response, Error> {
    future::ok(Response::new())
}

You’d have this:

struct LazyMiddleware<F: FnOnce() -> FutureResult<Response, Error>>(F);
impl<Chain, T, F> Middleware<Chain, T> for LazyMiddleware<F>
where
    Chain: FnOnce(Request) -> T,
    T: IntoFuture<Item = Response, Error = Error>,
    F: FnOnce() -> FutureResult<Response, Error>
{
    type Output = Lazy<F, FutureResult<Response, Error>>;

    fn call(self, req: Request, chain: Chain) -> Self::Output {
        future::lazy(self.0)
        //future::lazy(build_response)
    }
}

fn main() {
    let lazym = LazyMiddleware(|| future::ok(Response::new()));
}

#13

Sorry, my example was likely too contrived to be clear about why the closure types are a problem. The problem is that they’re part of what’s returned from the Middleware, and aren’t provided by the application. I’ll go with a more concrete example:

I’d imagine that most Middleware making use of asynchronous actions would need to use combinators in this way to do their job correctly. I’ve had a go at calculating the type we’d need to return if we were dealing entirely in concrete types here (please excuse any errors - this was done by hand and should be close enough to demonstrate my point):

The type is quite long, and it could be inferred if impl Trait could be used for an associated type. That isn’t the plan just yet, as far as I’m aware, so we’d need to specify the type by hand.

The reason we can’t specify that type by hand is the 3 different closure types within.

If I’m understanding you correctly (and please correct me if I’m not!), your solution would require that the closures be supplied by the application, which won’t work here.


#14

You did understand me right. I thought it was possible to supply the closure types within Gotham, but I see what you mean. Thanks for elaborating.

So, will need to think about this but I think you’re right in the whole. Have you brought these concerns up in a rust-lang github issue or anything of the sort? I’m curious if there’s been any discussion around supporting these scenarios without boxing. Namely, impl trait for associated types in this particular example.


#15

Good idea. Now that we’ve reached this point, it would be worth putting these thoughts together and talking about them on the relevant GitHub issue.


#16

So I think there’s some relevant discussion in https://github.com/rust-lang/rfcs/issues/1738 and https://github.com/rust-lang/rfcs/pull/2071, which is linked at the bottom of the first issue.