diff --git a/site/news/2018-12-08-version-0.4.md b/site/news/2018-12-08-version-0.4.md new file mode 100644 index 00000000..49112c47 --- /dev/null +++ b/site/news/2018-12-08-version-0.4.md @@ -0,0 +1,574 @@ +# Rocket v0.4: Typed URIs, Database Support, Revamed Queries, Stateful Handlers, & More! + +

+ Posted by Sergio Benitez on December 08, 2018 +

+ +I am elated to announce that the next major release of Rocket is now available! +Rocket 0.4 is a step forward in every direction: it is **packed** with features +and improvements that increase developer productivity, improve application +security and robustness, provide new opportunities for extensibility, and +deliver a renewed degree of toolchain stability. + +Rocket 0.4 is the culmination of more than a year of development. During this +time, more than 600 changes were committed, almost 400 issues were closed, and +over 165 pull requests were submitted. The Rocket community has proved steadfast +in its support: a sincere thank you to everyone involved! + +## About Rocket + +Rocket is a web framework for Rust with a focus on usability, security, and +performance. Rocket makes it simple to write fast, secure web applications +without sacrificing flexibility or type safety. + +Not already using Rocket? Join the tens of thousands of users and hundreds of +companies happily using Rocket today! Rocket's extensive documentation makes it +easy. Get started now by [reading through the guide](../../guide) or learning +more from [the overview](../../overview). + +## What's New? + +Rocket 0.4 is the largest release to date by a _wide_ margin. It is packed with +hundreds of changes. We highlight the largest of them here. For a complete +description of everything new and different in 0.4, please see the [CHANGELOG]. + +[CHANGELOG]: https://github.com/SergioBenitez/Rocket/blob/v0.4.0/CHANGELOG.md#version-040-dec-06-2018 + +### Maintainers += 1 + +An open source project is as much about the people as it is about the code. This +is why I am delighted to welcome [@jebrosen] as Rocket's first co-maintainer! +Jeb is directly responsible for several of the new features in 0.4, has +painstakingly code reviewed many other changes, and actively answers questions +and resolves issues on GitHub, IRC, and offline. + +Needless to say, Rocket is a better project thanks to you, Jeb. Welcome! + +[@jebrosen]: https://github.com/jebrosen + +### Codegen Rewrite + +In 0.4, the [`rocket_codegen`] crate has been entirely rewritten to use +to-be-stable procedural macro APIs where it previously used private, unstable +`rustc` APIs. While this is largely an internal change, it has big, positive +implications for all Rocket users. + +First and foremost, the path to Rocket on stable is now clearly in sight. While +there are still hurdles to overcome, we are actively working with the Rust team +to make Rocket on stable a reality as soon as possible. We expect the next major +Rocket release to support the stable channel. + +Second, but equally important, we expect breakages due to nightly changes to +drop dramatically, likely to zero. This means that Rocket is largely already +_de-facto_ toolchain stable. + +The new prelude import for Rocket applications is: + +```diff +- #![feature(plugin)] +- #![plugin(rocket_codegen)] ++ #![feature(proc_macro_hygiene, decl_macro)] + +- extern crate rocket; ++ #[macro_use] extern crate rocket; +``` + +[`rocket_codegen`] should **_not_** be a direct dependency. Remove it from your +`Cargo.toml`: + +```diff +[dependencies] +- rocket = "0.3" ++ rocket = "0.4" +- rocket_codegen = "0.3" +``` + +[`rocket_codegen`]: https://api.rocket.rs/v0.4/rocket_codegen/index.html + +### Typed URIs + +Rocket 0.4 introduces the [`uri!`] macro, allowing you to construct URIs to +routes in a robust, type-safe, and URI-safe manner. Type or route parameter +mismatches are caught at compile-time, and changes to route URIs are +automatically reflected in the generated URIs. + +To illustrate, consider the following route: + +```rust +#[get("/person/?")] +fn person(name: String, age: Option) +``` + +URIs to this `person` route can be created as follows: + +```rust +// with unnamed parameters, in route URI declaration order +let uri = uri!(person: "Mike Smith", 28); +assert_eq!(uri.to_string(), "/person/Mike%20Smith?age=28"); + +// with named parameters, order irrelevant +let uri = uri!(person: name = "Mike", age = 28); +let uri = uri!(person: age = 28, name = "Mike"); +assert_eq!(uri.to_string(), "/person/Mike?age=28"); + +// with a specific mount-point +let uri = uri!("/api", person: name = "Mike", age = 28); +assert_eq!(uri.to_string(), "/api/person/Mike?age=28"); + +// with optional query parameters ignored +let uri = uri!(person: "Mike", _); +let uri = uri!(person: name = "Mike", age = _); +assert_eq!(uri.to_string(), "/person/Mike"); +``` + +Should your route's URI change in an incompatible manner, or should you mistype +parameters, Rocket informs you of the error at compile-time with a helpful +message: + +```rust +error: person route uri expects 2 parameters but 1 was supplied + --> examples/uri/src/main.rs:9:29 + | +9 | uri!(person: "Mike Smith"); + | ^^^^^^^^^^^^ + | + = note: expected parameters: name: String, age: Option +``` + +The same applies to type errors: Rocket informs you of any type errors at +compile-time as well: + +```rust +error: the trait bound u8: FromUriParam is not satisfied + --> examples/uri/src/main.rs:9:35 + | +9 | uri!(person: age = "10", name = "Mike"); + | ^^^^ FromUriParam is not implemented for u8 + | +``` + +We recommend that `uri!` is exclusively used when constructing route URIs. For +more information on typed URIs, see the new [Typed URIs] guide section and the +[`uri!`] macro documentation. + +[`uri!`]: @api/rocket_codegen/macro.uri.html +[Typed URIs]: ../../guide/responses/#typed-uris + +### Database Support + +Rocket now includes built-in, ORM-agnostic support for database connection +pooling. More specifically, Rocket allows you to easily configure and connect +your Rocket application to databases through connection pools in three simple, +largely automated steps: + + 1. Configure databases in `Rocket.toml`. + 2. Associate a request guard type and fairing with each database. + 3. Use the request guard to retrieve a connection in a handler. + +As an example, for a Diesel-based SQLite database named `sqlite_logs`, your +`Rocket.toml` would record the URL to the database in the `databases` table: + +```toml +[global.databases] +sqlite_logs = { url = "/path/to/database.sqlite" } +``` + +In the application, a unit-like `struct` with one internal type (the database +connection) is decorated with the `#[database]` attribute and the name of the +configured database. This generates a fairing which must then be attached: + +```rust +#[database("sqlite_logs")] +struct LogsDbConn(diesel::SqliteConnection); + +rocket::ignite().attach(LogsDbConn::fairing()) +``` + +That's it! Whenever a connection to the database is needed, the type can be used +as a request guard: + +```rust +#[get("/logs/")] +fn get_logs(conn: LogsDbConn, id: usize) -> Result { + logs::filter(id.eq(log_id)).load(&conn) +} +``` + +For more information on Rocket's database support, see the new [Database] guide +section and the [`rocket_contrib::databases`] module documentation. + +[Database]: ../../guide/state/#databases +[`rocket_contrib::databases`]: @api/rocket_contrib/databases/index.html + +### Revamed Queries + +In Rocket 0.4, query string handling has been completely overhauled, resolving +some of the most called for requests in Rocket's history ([#608]). The new query +handling route syntax and semantics were designed with the following goals in +mind: + + * Enable matching of static query components. + * No special-casing of any kind, preferring type-driven flows. + * Ad-hoc matching of specific query key/value pairs. + * Lenient parsing by default, allowing missing parameters. + * Order-independent matching of query parameters. + +To illustrate the new system in action, consider the following route: + +```rust +#[derive(FromForm)] +struct DogDetails { + color: Color, + height: Inches, + weight: Pounds +} + +#[get("/animal?dog&&&")] +fn dog(name: String, nickname: Option, rest: Form) +``` + +This route matches any `GET` request with a path of `/animal`, a static query +component of `dog`, and key/value parameters of `color`, `height`, and `weight` +that validate as `Color`, `Inches`, and `Pounds`, respectively. Furthermore, it +optionally accepts a key/value parameter of `nickname`. If the value is present, +`nickname` will be `Some`; if it is not, `nickname` will be `None`. + +Single parameters (``) like `name` and `nickname` are validated using the +existing [`FromFormValue`] trait while trailing parameters (``) are +validated using the new [`FromQuery`] trait. Both traits are user implementable, +and [`FromFormValue`] is deriveable. + +For more details on handling query strings, see the new [Query Strings] guide +section and the updated [`route` attribute] documentation. + +[`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html +[`FromQuery`]: @api/rocket/request/trait.FromQuery.html +[`route` attribute]: @api/rocket_codegen/attr.get.html +[Query Strings]: ../../guide/requests/#query-strings +[#608]: https://github.com/SergioBenitez/Rocket/issues/608 + +### Stateful Handlers + +The type of a handler has been generalized in 0.4 to any type that implements +the new [`Handler`] trait. Among other things, this allows handlers to refer to +internal state during request handling. + +The new [`StaticFiles`] `contrib` type uses this functionality to provide +easier-than-ever static file serving. For example, to make local files from a +`/static` directory accessible at `/public`, you need simply write: + + +```rust +fn main() { + rocket::ignite() + .mount("/public", StaticFiles::from("/static")) + .launch(); +} +``` + +We encourage users to explore the new `Handler` API and contribute libraries +with pluggable handlers! For more details, see the [`Handler`] documentation. + +[`Handler`]: @api/rocket/trait.Handler.html +[`StaticFiles`]: @api/rocket_contrib/serve/struct.StaticFiles.html + +### Responder Derive + +In Rocket 0.4, the [`Responder`] trait can be derived for `enum`s and `struct`s +with named fields. This greatly simplifies returning multiple types of responses +from a single handler. + +To illustrate, consider a route that returns either a `Json` structure for +401 (unauthorized) errors or a `NamedFile` with a dynamic Content-Type for 404 +(not found) errors. To accomplish this previously, `Result` values could be +arbitrarily nested, an unappealing and semantically incorrect approach. +Alternatively, an `enum` could be declared with the appropriate variants, and +`Responder` could be manually implemented for the `enum`. As of 0.4, that +implementation can be automatically derived: + +```rust +#[derive(Responder)] +enum Error { + #[response(status = 400)] + Unauthorized(Json), + #[response(status = 404)] + NotFound(NamedFile, ContentType), +} +``` + +A value of this type can then be returned from a hander or used as part of +wrapping responders: + +```rust +#[get("/")] +fn handler(user: Option, item: Option) -> Result { + if user.is_none() { + Err(Error::Unauthorized(..)) + } else if item.is_none() { + Err(Error::NotFound(..)) + } else { + Ok(..) + } +} +``` + +The status for each variant will be automatically set to the value of the +`status` variant attribute, and fields beyond the first will be added as +headers to the response (here, `ContentType`). + +For more on using the `Responder` derive, see the new [Custom Responders] guide +section and the [`Responder` derive] documentation. + +[Custom Responders]: ../../guide/responses/#custom-responders +[`Responder` derive]: @api/rocket_codegen/derive.Responder.html +[`Responder`]: @api/rocket/response/trait.Responder.html + +### Live Template Reloading + +Rocket 0.4 automatically reloads changed templates at runtime without requiring +recompilation. This works on all major platforms. For security and performance +reasons, live template reloading is only enabled when the application is +compiled in debug mode. + +There is no configuration necessary: this _just works_ out of the box! + +### And Plenty More! + +In addition to the features highlighted above, Rocket 0.4 also contains with the +following new features: + + * Introduced [Request-Local State]. + * Introduced [transforming] data guards via [`FromData::transform()`]. + * Introduced the [`SpaceHelmet`] security and privacy headers fairing. + * Private cookies are gated behind a `private-cookies` default feature. + * Added [derive for `FromFormValue`]. + * Added [`Template::custom()`] for customizing templating engines. + * Cookies are automatically tracked and propagated by [`Client`]. + * Private cookies can be added to local requests with + [`LocalRequest::private_cookie()`]. + * Release builds default to the `production` environment. + * Keep-alive can be configured via the `keep_alive` configuration parameter. + * Allow CLI colors and emoji to be disabled with `ROCKET_CLI_COLORS=off`. + * Route `format` accepts [shorthands] such as `json` and `html`. + * Implemented [`Responder` for `Status`]. + * Added [`Response::cookies()`] for retrieving response cookies. + * All logging is disabled when `log` is set to `off`. + * Added [`Metadata`] guard for retrieving templating information. + * The [`Uri`] type parses according to RFC 7230 into one of [`Origin`], + [`Absolute`], or [`Authority`]. + * Added [`Outcome::and_then()`], [`Outcome::failure_then()`], and + [`Outcome::forward_then()`]. + * Implemented `Responder` for `&[u8]`. + * Any `T: Into>` can be [`mount()`]ed. + * Added [`Request::get_query_value()`] for retrieving a query value by key. + * Applications can launch without a working directory. + * Added [`State::from()`] for constructing `State` values. + +[`SpaceHelmet`]: https://api.rocket.rs/v0.4/rocket_contrib/helmet/index.html +[`State::from()`]: https://api.rocket.rs/v0.4/rocket/struct.State.html#method.from +[Typed URIs]: https://rocket.rs/v0.4/guide/responses/#typed-uris +[ORM agnostic database support]: https://rocket.rs/v0.4/guide/state/#databases +[`Template::custom()`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Template.html#method.custom +[`LocalRequest::private_cookie()`]: https://api.rocket.rs/v0.4/rocket/local/struct.LocalRequest.html#method.private_cookie +[`LocalRequest`]: https://api.rocket.rs/v0.4/rocket/local/struct.LocalRequest.html +[shorthands]: https://api.rocket.rs/v0.4/rocket/http/struct.ContentType.html#method.parse_flexible +[derive for `FromFormValue`]: https://api.rocket.rs/v0.4/rocket_codegen/derive.FromFormValue.html +[derive for `Responder`]: https://api.rocket.rs/v0.4/rocket_codegen/derive.Responder.html +[`Response::cookies()`]: https://api.rocket.rs/v0.4/rocket/struct.Response.html#method.cookies +[`Client`]: https://api.rocket.rs/v0.4/rocket/local/struct.Client.html +[Request-Local State]: https://rocket.rs/v0.4/guide/state/#request-local-state +[`Metadata`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Metadata.html +[`Uri`]: https://api.rocket.rs/v0.4/rocket/http/uri/enum.Uri.html +[`Origin`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Origin.html +[`Absolute`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Absolute.html +[`Authority`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Authority.html +[`Outcome::and_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.and_then +[`Outcome::forward_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.forward_then +[`Outcome::failure_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.failure_then +[`StaticFiles`]: https://api.rocket.rs/v0.4/rocket_contrib/serve/struct.StaticFiles.html +[live template reloading]: https://rocket.rs/v0.4/guide/responses/#live-reloading +[`Handler`]: https://api.rocket.rs/v0.4/rocket/trait.Handler.html +[`mount()`]: https://api.rocket.rs/v0.4/rocket/struct.Rocket.html#method.mount +[`FromData::transform()`]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#tymethod.transform +[transforming]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#transforming +[query string handling]: https://rocket.rs/v0.4/guide/requests/#query-strings +[Default rankings]: https://rocket.rs/v0.4/guide/requests/#default-ranking +[`Request::get_query_value()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.get_query_value +[`Responder` for `Status`]: https://rocket.rs/v0.4/guide/responses/#status + +## Breaking Changes + +This release includes many breaking changes. Please see the +[CHANGELOG](https://github.com/SergioBenitez/Rocket/blob/v0.3.0/CHANGELOG.md#breaking-changes) +for a complete list of breaking changes along with details on handling the +breaking change in existing applications. + +Rocket 0.3 will continue as a security maintance release _only_. All users are +encouraged to migrate their applications to 0.4. + +## General Improvements + +In addition to new features, Rocket saw the following improvements: + + * Log messages now refer to routes by name. + * Collision errors on launch name the colliding routes. + * Launch fairing failures refer to the failing fairing by name. + * The default `403` catcher now references authorization, not authentication. + * Private cookies are set to `HttpOnly` and are given an expiration date of 1 + week by default. + * A [Tera templates example] was added. + * All macros, derives, and attributes are individually documented in + [`rocket_codegen`]. + * Invalid client requests receive a response of `400` instead of `500`. + * Response bodies are reliably stripped on `HEAD` requests. + * Added a default catcher for `504: Gateway Timeout`. + * Configuration information is logged in all environments. + * Use of `unsafe` was reduced from 9 to 2 in core library. + * [`FormItems`] now parses empty keys and values as well as keys without + values. + * Added [`Config::active()`] as a shorthand for + `Config::new(Environment::active()?)`. + * Address/port binding errors at launch are detected and explicitly emitted. + * [`Flash`] cookies are cleared only after they are inspected. + * `Sync` bound on [`AdHoc::on_attach()`], [`AdHoc::on_launch()`] was removed. + * [`AdHoc::on_attach()`], [`AdHoc::on_launch()`] accept an `FnOnce`. + * Added [`Config::root_relative()`] for retrieving paths relative to the + configuration file. + * Added [`Config::tls_enabled()`] for determining whether TLS is actively + enabled. + * ASCII color codes are not emitted on versions of Windows that do not support + them. + * Added FLAC (`audio/flac`), Icon (`image/x-icon`), WEBA (`audio/webm`), TIFF + (`image/tiff`), AAC (`audio/aac`), Calendar (`text/calendar`), MPEG + (`video/mpeg`), TAR (`application/x-tar`), GZIP (`application/gzip`), MOV + (`video/quicktime`), MP4 (`video/mp4`), ZIP (`application/zip`) as known + media types. + * Added `.weba` (`WEBA`), `.ogv` (`OGG`), `.mp4` (`MP4`), `.mpeg4` (`MP4`), + `.aac` (`AAC`), `.ics` (`Calendar`), `.bin` (`Binary`), `.mpg` (`MPEG`), + `.mpeg` (`MPEG`), `.tar` (`TAR`), `.gz` (`GZIP`), `.tif` (`TIFF`), `.tiff` + (`TIFF`), `.mov` (`MOV`) as known extensions. + * Interaction between route attributes and declarative macros has been + improved. + * Generated code now logs through logging infrastructures as opposed to using + `println!`. + * Routing has been optimized by caching routing metadata. + * [`Form`] and [`LenientForm`] can be publicly constructed. + * Console coloring uses default terminal colors instead of white. + * Console coloring is consistent across all messages. + * `i128` and `u128` now implement [`FromParam`], [`FromFormValue`]. + * The `base64` dependency was updated to `0.10`. + * The `log` dependency was updated to `0.4`. + * The `handlebars` dependency was updated to `1.0`. + * The `tera` dependency was updated to `0.11`. + * The `uuid` dependency was updated to `0.7`. + * The `rustls` dependency was updated to `0.14`. + * The `cookie` dependency was updated to `0.11`. + +[Tera templates example]: @github/examples/tera_templates +[`FormItems`]: @api/rocket/request/enum.FormItems.html +[`Config::active()`]: @api/rocket/config/struct.Config.html#method.active +[`Flash`]: @api/rocket/response/struct.Flash.html +[`AdHoc::on_attach()`]: @api/rocket/fairing/struct.AdHoc.html#method.on_attach +[`AdHoc::on_launch()`]: @api/rocket/fairing/struct.AdHoc.html#method.on_launch +[`Config::root_relative()`]: @api/rocket/struct.Config.html#method.root_relative +[`Config::tls_enabled()`]: @api/rocket/struct.Config.html#method.tls_enabled +[`rocket_codegen`]: @api/rocket_codegen/index.html +[`FromParam`]: @api/rocket/request/trait.FromParam.html +[`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html +[`Data`]: @api/rocket/struct.Data.html +[`Form`]: https://api.rocket.rs/v0.4/rocket/request/struct.Form.html +[`LenientForm`]: https://api.rocket.rs/v0.4/rocket/request/struct.LenientForm.html + +## What's Next? + +Rocket v0.5 is scheduled to be _at least_ as exciting as 0.4! As always, the +focus continues to be usability, stability, security, and performance. With this +in mind, the roadmap for 0.5 includes: + + 1. **Support for Rust Stable** ([#19]) + + Finally! Rocket 0.5 will compile and run on stable versions of the Rust + compiler. + + 2. **Aynchronous Request Handling** ([#17]) + + In 0.5, Rocket will migrate to the latest asynchronous version of `hyper` and + `futures` with compatibility for `async`/`await` syntax. Of utmost importance + is preserving Rocket's usability. As such, these changes will be largely + internal, with asynchronous I/O peeking over the covers _only_ when + explicitly desired or required. As a side effect, we expect a substantial + performance boost from the migration as well as resolution to long-standing + issues. + + 3. **Multipart Form Support** ([#106]) + + The lack of built-in multipart form support makes handling file uploads and + other submissions much more cumbersome than necessary. Rocket 0.5 will + generalize its existing forms infrastructure to handle multipart forms. + + 4. **Stronger CSRF and XSS Protection** ([#14]) + + Since 0.3, Rocket uses `SameSite: Strict` private cookies to prevent CSRF + attacks. This technique is only tenable in newer browsers. In 0.5, Rocket + will protect against CSRF using more robust techniques. Rocket will also add + support for automatic, browser-based XSS protection. + +[#17]: https://github.com/SergioBenitez/Rocket/issues/17 +[#19]: https://github.com/SergioBenitez/Rocket/issues/19 +[#106]: https://github.com/SergioBenitez/Rocket/issues/106 +[#14]: https://github.com/SergioBenitez/Rocket/issues/14 + +## Rocket v0.4 Contributors + +The following wonderful people helped make Rocket 0.4 happen: + +
    +
  • Alexander Mielczarek
  • +
  • Alex Bowers
  • +
  • Alfie John
  • +
  • Alva Snædís
  • +
  • Ashley Williams
  • +
  • Beatriz Rizental
  • +
  • bohov
  • +
  • Christophe Courtaut
  • +
  • David Darrell
  • +
  • Desmond
  • +
  • Divyahans Gupta
  • +
  • Donald Robertson
  • +
  • EloD10
  • +
  • Eric Dattore
  • +
  • Henning Kowalk
  • +
  • Imron Alston
  • +
  • Jeb Rosen
  • +
  • kryptan
  • +
  • Kyle Clemens
  • +
  • lerina
  • +
  • Linus Unnebäck
  • +
  • Lukas Abfalterer
  • +
  • Marc Mettke
  • +
  • Max Furman
  • +
  • messense
  • +
  • Ning Sun
  • +
  • Philip Jenvey
  • +
  • Pyry Kontio
  • +
  • Richo Healey
  • +
  • Riley Trautman
  • +
  • Rolf Schmidt
  • +
  • Rukai
  • +
  • Sean Stangl
  • +
  • Sébastien Santoro
  • +
  • Sergio Benitez
  • +
  • Stanislav Ivanov
  • +
  • Tal Garfinkel
  • +
  • Tobias Stolzmann
  • +
  • Ville Hakulinen
  • +
  • Vishal Sodani
  • +
  • Zack Chandler
  • +
  • Zac Pullar-Strecker
  • +
+ +Thank you all! Your contributions are **greatly** appreciated! + +Looking to help with Rocket's development? Head over to [Rocket's +GitHub](https://github.com/SergioBenitez/Rocket#contributing) and start +contributing! diff --git a/site/news/index.toml b/site/news/index.toml index 1384ecd4..39b43347 100644 --- a/site/news/index.toml +++ b/site/news/index.toml @@ -1,3 +1,25 @@ +[[articles]] +title = """ + Rocket v0.4: Typed URIs, Database Support, Revamed Queries, Stateful Handlers, + & More! +""" +slug = "2018-12-08-version-0.4" +author = "Sergio Benitez" +author_url = "https://sergio.bz" +date = "December 08, 2018" +snippet = """ +I am elated to announce that the next major release of Rocket is now available! +Rocket 0.4 is a step forward in every direction: it is **packed** with features +and improvements that increase developer productivity, improve application +security and robustness, provide new opportunities for extensibility, and +deliver a renewed degree of toolchain stability. + +Rocket 0.4 is the culmination of more than a year of development. During this +time, more than 600 changes were committed, almost 400 issues were closed, and +over 165 pull requests were submitted. The Rocket community has proved steadfast +in its support: a sincere thank you to everyone involved! +""" + [[articles]] title = "Rocket's 2nd v0.4 Release Candidate" slug = "2018-11-30-version-0.4-rc-2"