
How I Start: Rust - fanf2
https://christine.website/blog/how-i-start-rust-2020-03-15
======
anurag
In case it helps, Render ([https://render.com](https://render.com)) has first
class support for Rust webapp hosting without needing Docker.

And here's the Hello World URL for this tutorial! [https://rust-nix-
helloworld.onrender.com/hostinfo](https://rust-nix-
helloworld.onrender.com/hostinfo)

More info at [https://render.com/docs/deploy-rocket-
rust](https://render.com/docs/deploy-rocket-rust). I'm the founder; happy to
answer questions here or in our user chat at
[https://render.com/chat](https://render.com/chat).

~~~
rafaelgoncalves
Didn't know about this render.com, very nice hosting. Thanks for this.

------
rvz
As much as I use Rust personally and for some of my own pet projects, for
serious HTTP services and introducing it into a new developer team, I'm afraid
that isn't feasible for my requirements. I'm put off with the immaturity of
the crates ecosystem and the costs it will bring. Most of the crates are not
even 1.0 including Rocket and that also requires a nightly build which isn't
good enough for my requirements. (I only use stable Rust).

As a language it is mature, but the crates ecosystem is of lesser quality,
especially for applications like HTTP servers. I've already overcome its
learning curve, but the devs who swear by other languages that pay them well
may not be so forgiving.

~~~
correct_horse
I totally agree that Rocket requiring nightly is a deal-breaker for
production. I suspect the author picked Rocket because it feels more beginner
friendly than alternatives.

Actix-web is in 2.0. The original author stepped away recently, but it has
quite the community behind it.

If readers are curious why Rocket requires nightly, it has to do with the
macros (that resemble Java annotations/python decorators). Specifically Rust
wants all its macros to be hygienic - they shouldn't be able to (for example)
create variables that clobber user-defined variables. Rust has multiple types
of macros, but the type used by Rocket, procedural macros, don't have this
hygienic property yet. For context, procedural macros operate on TokenStreams,
whereas "normal macros" (declarative macros) operate on AST nodes. See the
issue
[https://github.com/SergioBenitez/Rocket/issues/19](https://github.com/SergioBenitez/Rocket/issues/19)

~~~
golergka
> Actix-web is in 2.0.

What's the situation with use of unsafe now?

Although toxicity that was shown by the community was obviously uncalled for,
I was very put off by the original author's stance on the issue and am wary of
using it in production.

~~~
correct_horse
All issues dealing with undefined behavior (UB), segfaults and the like are
currently closed. Actix-Web makes use of unsafe (as high-performance Rust
usually does) so there could be some UB hiding.

~~~
adamc
Honest question: What is the point of using Rust if you have to use unsafe
constructs to get performance?

~~~
Too
What are your alternatives? Managed memory language or C/C++?

Rust has much more modern and ergonomic syntax, features and package
management over C, so even inside unsafe blocks it's simply easier to write.

~~~
pjmlp
Managed memory languages like Swift, F#, C#, D, Nim, offer the productivity of
GC, value types, unsafe if really needed and AOT compiled toolchains.

~~~
pkolaczk
"productivity of GC" is often offset by much harder management of resources
other than memory due to lack of deterministic destruction.

Also benchmarks show Swift and C# are still about 2x-5x slower than Rust and
C++, and that only if you're very careful and you write non-idiomatic code
(e.g by avoiding heap allocations). When you're not, and you use OOP
abstractions heavily, 10x worse is a much more likely outcome.

~~~
pjmlp
That is only true if using a language that doesn't not offer mechanisms to
have deterministic destruction, when required to do so.

Winning the benchmarks game is meaningless, other than "I fell good" kind of
thing.

What matters is having the language features that allow to optimize the 1% of
the code base, when it actually impact the acceptance criteria of project
delivery.

I have been replacing C++ systems with Java and .NET based solutions since
2006, it hardly matters that C++ wins in the micro-benchmarks.

And if really needed, only that 1% gets packed into a C++ library with managed
language bindings, turning C++ into our "unsafe" module.

~~~
pkolaczk
My point is these languages you mentioned don't offer good tools to optimize
that 1% and they make the other 99% an order of magnitude slower and more
resource hungry to the point where it actually matters and annoys users. Also
neither C# nor Java offers deterministic destruction. Try with resources is a
joke, because it is limited to a lexical scope. Java and C# are not true
alternatives to Rust or C++. They are inferior both on performance side and
abstraction/productivity side, and severely inferior if you want both in the
same fragment of code.

In many applications there is also no single bottleneck and the split is not
1/99, nor even 20/80\. After you optimize the most obvious bottlenecks you end
up with a flat profile, where majority of time is taken by cache misses
scattered across almost the whole codebase.

It might not matter for some apps where performance is less critical, but in
this case you probably don't want to use Java or C# when there exist languages
offering much better abstractions (and surprisingly - Rust and C++ can be
higher-level than Java or C#, leading to better abstractions, shorter code and
higher productivity). Don't conflate EASY with PRODUCTIVE. Easy languages are
not always more productive (if it was true everybody would be coding in
Scratch).

~~~
pjmlp
C++ can be indeed higher level than Java, except that you are forgetting about
the money spent fixing C related bugs, developer salaries, lack of tooling to
plugin into a cluster and just monitor it like JFR, VisualVM, ETW, lack of
interoperability between libraries due to conflicting requirements (RTTI,
Exceptions, STL avoidance, ...)

As for Rust, it remains to have something that matches Orleans, Akka, Kafka,
....

Then there is the whole IDE tooling experience, libraries like the one here
that require nightly toolchains, and lack of support for stagging binary
libraries, which cargo might eventually get one day, but isn't here today.

Java and C# might be inferior products from your point of view, but as
mentioned, what I get are rewrites from C++ into Java and .NET languages, not
the other way around.

And I never sensed lack of productivity, quite the contrary, specially when I
usually don't have to think about which C++ flavor of the month I am allowed
to use, or having political discussions about enforcing the use of static
analyzers on the CI/CD pipeline (assuming there is even one to start with).

~~~
pkolaczk
But now you're taking about tooling and not languages. Rust will eventually
get there.

IDE support is already very good and better than for many popular languages
(e.g. dynamic ones).

Performance profiling is also better than in Java. I'd take perf over visualvm
any time.

Java had much worse tooling when it was at the age of Rust today.

~~~
pjmlp
Languages are not used in isolation, the days that grammar and semantics were
enough to warrant a language change.

IDE support can be considered very good if the baseline is the 90's Borland
and Microsoft IDE experience, not what modern Java and .NET are capable of,
which starts to finally approach the Smalltalk and Common Lisp development
experience of yore.

Rust IDEs can't still offer completion that works all the time, let alone all
the other IDE features.

Perf is no match for Visual VM, as it is a Linux only tool, and it's usability
is found lacking. It is so good that Google was forced to create their own
graphical based tooling after years of Android developers complaints being
forced to use it.

Yeah, except not every business is willing to wait 25 years for Rust to
achieve parity with today's Java ecosystem.

Note that C++ is 40 something years old and still there are domains that it is
fighting against C, which require a generational change before being open to
try out anything else.

Rust's has a big selling story for OS low level systems libraries, the niche
C++ is heading to, with the caveat that C++ will as secure as Rust, that is
where the language should focus.

To be honest, had Go supported generics from day one, Nim or D some corporate
backing, and I would have never considered Rust for hobby projects beyond the
language geek thing of trying out new languages every year.

~~~
pkolaczk
> Yeah, except not every business is willing to wait 25 years for Rust to
> achieve parity with today's Java ecosystem.

Unfounded speculation. You've made this number up.

> Rust IDEs can't still offer completion that works all the time, let alone
> all the other IDE features

I didn't see any problems in IntelliJ. If there are cases where autocomplete
doesn't work, these are rare edge cases and they don't affect predictivity.
VSCode was a bit laggy, but that's probably VSCode problem not Rust's. This is
what happens if you base a desktop tool on a browser running JS.

> Rust's has a big selling story for OS low level systems libraries

It is good at that, but this is not the primary reason to use Rust. Rust
selling point are explicit lifetimes which make it virtually impossible to
create pointer hell I found in every commercial Java codebase I worked on. It
is the same level of productivity enhancement as introducing static types over
dynamic. Explicit lifetimes make it a bit harder to write in Rust but code is
being read 99% of time and written only 1% of time.

------
jakearmitage
I love the way this tutorial was written. I can't explain what exactly, but I
found the entire experience to be very didactic. Congrats, and thank you! Wish
we had more of this.

------
xena
Author of the post here, feel free to ask me anything!

~~~
hrombach
It was really helpful doing some first steps with rust.

One suggestion: add a warning for macOS users that psutil::host::uptime is not
available on macOS. I had to figure that out myself and it was quite
frustrating.

I figure you didn't know that, and it's not like the article claims otherwise,
but it would be useful to add I think.

~~~
chidg
Thanks, I had the same issue - and only figured it out because this comment
came up in Google results for the error message.

------
deepsun
> routes!(...)

> routes_with_openapi!(...)

Macros, macros everywhere. I understand their use in basic functions like
println!(), but I'm worried too much macros in client code will lead to
problems (unreadable, clashes that will need custom names). If there are no
problems with macros, why not making every function a macro then?

~~~
dgb23
Macros are extremely important to make rust productive. It sometimes feels as
if you were writing in a higher level language.

One of the best examples is the serde crate. What a beautiful library!

~~~
kibwen
Macros are powerful, but they should be used sparingly because they often have
outsize effects on compilation times and degrading error message quality. I
personally give myself a "macro budget" where macro-based solutions are
prioritized based on benefit to a particular project.

------
CoffeeDregs
Love seeing more Rust. It's good stuff.

    
    
        extern crate rocket;
    

I thought "extern crate XYZ" was old-style? "use XYZ"?

~~~
cjbassi
It's still fairly common to use in conjunction with

    
    
        #[macro_use]
    

for when you want to easily import all of the macros from a crate across your
entire project. But yes, you could do

    
    
        use rocket::{get, routes};
    

instead.

------
status_quo69
One thing I wish was covered in these is how to go past 'hello world',
especially things like error responses and serialization models. It took me a
while before I got to the point where I think I can be productive in both
querying the data from the front-end and getting back the shape that I wanted.
Couple of lessons learned:

\- Start with separating your API models and DB models out early, you'll be
thankful later. You can also help yourself out by implementing From/Into for
your database models to your API models. Obvious lesson from CQRS but since
rust doesn't allow easily for ad-hoc structs this is a must, especially since
there are a lot of examples out there that put a Serialize/Deserialize onto
the database object.

\- If you have deeply nested objects, look into JSON:API or Juniper. There are
a couple of crates that implement the jsonapi spec and I'm hacking on one of
them to make it a bit more usable for my personal pet project, but Juniper
will make your life easier especially if you're starting with a React
frontend.

\- Understand that there's still a tracking issue for a few items such as
multipart data, although there's a third party crate that integrates the
multipart crate into rocket. However if you need an upload you could probably
get away with just returning a signed S3 endpoint to the user, which is what I
ended up doing

\- If integrating with redis or some other database that's not application
critical, there is no (as far as I'm aware) graceful degredation that comes
out of the box with rocket_contrib, unless there was some config value I was
missing. This is usually fine, since you can easily declare an ad-hoc fairing
that connects to redis and falls back to an Option::None in the case where it
couldn't connect.

And finally, if you want to support different error status codes yourself
based off of mappings from internal errors (such as 409 conflict when
inserting the same data), your best bet is an enum that's wrapped by a
responder derive like so:

    
    
        #[derive(Responder, Debug)]
        pub enum UserApiError {
            #[response(status = 500, content_type = "json")]
            Unavailable(Json<UserApiErrorMessage>),
        }
    
        impl UserApiError {
            fn wrap_body(message: &str) -> Json<UserApiErrorMessage> {
                Json(UserApiErrorMessage {
                    status: "error".to_string(),
                    message: message.to_string(),
                })
            }
    
            pub fn unavailable(message: &str) -> Self {
                Self::Unavailable(Self::wrap_body(message))
            }
        }
    

Which can be called like:

    
    
        Err(UserApiError::unavailable("The database is gone!"))
    

Most of my other lessons learned come from diesel, both are an absolute
pleasure to work with and have given me incredible confidence while coding.

~~~
dochtman
> Start with separating your API models and DB models out early, you'll be
> thankful later. You can also help yourself out by implementing From/Into for
> your database models to your API models. Obvious lesson from CQRS but since
> rust doesn't allow easily for ad-hoc structs this is a must,

Can you explain why? This advice doesn't make intuitive sense for me.

~~~
tene
Not the parent, so I can't speak for them, but my experience is that it's
pretty likely that you'll eventually run into situations where you really want
to store something different in the DB than what you send out from your API.

For example, you may want to keep some fields internal and not expose them to
users, or you may want to normalize your DB schema so some fields are stored
in a linked table, or maybe you had something as timestamp+duration, and need
to keep the same external API for compatibility, but also want to refactor it
internally into two timestamps.

~~~
status_quo69
Yep! Serde allows us to cheat a bit since you can call for it to skip certain
fields (such as password hashes), but my preference is for the field to not
exist at all in your serialization structs. Less of a chance to forget that
#[serde(skip)] flag

------
saagarjha
> Adding the --vcs git flag also has cargo create a gitignore file so that the
> target folder isn’t tracked by git.

Doesn’t cargo make a git repository by default, anyways? Why doesn’t it add a
sensible gitignore in the process?

~~~
seaish
Yeah but I'm guessing it's there in case someone has disabled it globally.

------
bcheung
Why "cargo init" instead of "cargo new"?

Also, wondering when Rocket will be usable without nightly. That makes me
concerned that it is not stable enough.

~~~
status_quo69
I've used it for 6 months on the async branch and the master branch and master
was incredibly stable. Async is less so because it's a large scale effort to
try and convert over the entire API with minimal breakage(apart from
fairings/middlewares). You can pin to a specific stable nightly if you're
needing extra sanity, although CI makes this hard with the nightly docker
images. There's a tracking issue for proc_macro_hygine here:
[https://github.com/rust-lang/rust/issues/54727](https://github.com/rust-
lang/rust/issues/54727)

Looks like the last feature is almost stable!

------
baby
A few comments:

* missing `use rocket_contrib::json::Json`

* `unwrap_or` is better than `or` followed by `unwrap`

* the psutil crate already has hostname info

* the psutil crate doesn't provide an uptime function if you're on a unix system.

* once you setup the hostinfo route, your example says to curl the index

* your docker link is broken

------
MuffinFlavored
> Rocket has support for unit testing built in

Do I really want my HTTP service framework to be my unit testing framework
too? Could these not be separated?

~~~
hathawsh
I believe the author meant that Rocket provides helpers to make unit testing
easy. The tests are written using the ordinary "#[test]" Rust annotation and
run by the standard Rust test runner.

