DRAFT DRAFT DRAFT
I finally got back to my idea of a small Rust project. Here I'll list my findings so far.
You'll notice that I talk about FFI a lot. Maybe that's just me but I see the following ways I can start playing with an emerging language:
- Build some webby stuff. A small service for my existing webapp in a different language would be nice. However, HTTP is still not great in Rust (it's getting there!), so I decided not to do it for now.
- Just play with it. Write some code with a lot of math or text/csv processing. Project Euler and all that. A good way to learn the language but the utility is somewhat limited.
- Integrate with some existing library. I can play with the language and at the same time the final product me be beneficial for the community at large.
At this point I prefer the last option is case of Rust, although that means that my work will involve dealing with FFI. Quite a few people have similar thoughts: if you check projects on Rust-CI many of them (1/3 or 1/4?) are bindings to some preexisting libraries.
It's a pity that FFI doesn't get as much attention as it should. Guides consider this an "advanced" topic. Information on the web is sparse, and my FFI-related questions often stay unanswered on IRC. At the same time reading FFI declarations in Rust can be overwhelming.
On the other hand binding-type projects are extremely valuable for any emerging language community early on, and I feel like we should pay more attention to helping people getting up to speed with FFI.
Anyway, here are my findings so far.
- Cargo is Ok. Not great yet. I kinda like npm's
scripts
section a lot more than Cargo'sbuild
..toml
doesn't bother me at all. - Lifetimes elision is awesome! The best addition to the language since 0.8 days.
- Build-in testing is fine. Not BDD-ish but enough for actually getting things done.
-
Not 100% Rust-related, but in general the world of Native is a huge mess. Compiling, linking, all those command-line flags,
.o
vs.a
vs.so
. Why my compiler can produce dynamic libraries but cannot produce static ones? Why I can't produce a library in one go and instead I have to saycc my.c to my.o
andcc my.o to libmy.so
? Later thatlibmy.so
is referenced as-l my
(where's 'lib' part?) but it won't work anyway because I also have to have myDYLD_LIBRARY_PATH
set up! Too bad things like that are not covered well enough on the internet. -
Rust FFI guide doesn't show how to use C's
struct
s andtypedef
s from Rust. Besides, there are way too many type aliases involved at the border between C and Rust.libc::
types,c_str
,std::ptr
and some types that are just too scary! (especially when you see a bunch of them next toerror
in a terminal):error: mismatched types: expected `core::option::Option<extern "C" fn(*mut libc::types::common::c95::c_void, u64) -> *mut libc::types::common::c95::c_void>`, found `core::option::Option<unsafe extern "C" fn(u64) -> *mut libc::types::common::c95::c_void>` (expected normal fn, found unsafe fn)
-
Rust can't FFI with C macros. For many cases that's fine. People complain on StackOverflow about not being able to reference
#define
d constants but those are trivial to copy-paste.However, if a library decided to export some of its API not as functions but as macros then you're stuck. Either you have to avoid calling these functions or you have to write wrappers in C. Which is kinda strange to be honest: what's the reason why my C code can pick up macro-defined functions from 3rd-party library but my Rust code cannot? The object files are the same, so in theory macro-fuctions should be visible.
It's even worse in practice. I have to write 2 wrappers: a C wrapper function around a C macro and then write a safe Rust wrapper around an unsafe
extern
function. -
Speaking of libraries,
cargo
doesn't let one pass-l
parameters torustc
without going through the wholebuild.rs
mambo-jambo. In my case the build is simple: 1 .c file, 1 .so/.a file, very similar to a sample from Cargo guide. However, RustCommand
API is so verbose compared to a plain Shell script that I wonder why bother describing builds in Rust in a first place. -
Docs are still not great. Main guide is great but theme guides are no near as comprehensive as I'd like to. I found myself consulting with the FFI guide (or Lifetimes guide, or Modules guide) and not finding answers there. Then I would go to API reference but it usually way too specific. It can answer 'what parameters this function accepts' and not 'when and how should I use it'. Rust-by-Example should fill this niche but it's still too shallow. Compare that to Node docs that have a narrative around the ways in which this or that API can be used - with examples, discussions about the tradeoffs, etc. I'd love to see something similar for different aspects of Rust APIs.
-
Also, Rust docs should link to Cargo docs and vice versa. No, a single link somewhere on Index page won't cut it: the guides and API docs should be deeply linked.
-
Although Rust claims to be almost 1.0 bikeshedding is still at full speed. Almost every library I saw has commits in last month updating to a new Rust nightly.
-
Finally, updating Rust is cumbersome. Somehow I assumed that Cargo should autoupdate my Rust installation during the build. Turned out that I have to
curl | sudo sh --uninstall
andcurl | sudo sh
again to get a new build.Rustup
triggers 100+ MiB download even when uninstalling. Still, the build is fast and unless you're on a cellular connection it won't bother you much.
Macros aren't functions. Macros are a kind of preprocessor trickery that performs transformations directly on the source code in C. By the time the code has been compiled, no trace of the macros remain in the object files.