I would say that your project organization is reasonable, and that the hack is required just because tool support for multi crate projects is a bit lacking. Let me elaborate on this a bit.
Rust is a very interesting language when it comes to modularity. Almost all languages have a module (namespace) concept. However, none of the languages I know besides Rust has a concept of library (would really love to hear about such a language!). That is, in Java, C++, Python, Clojure, Ocaml, Haskell, etc. there is a single global shared namespace for modules. Modules of all the libraries are just dumped into a single search path (PYTHONPATH, GOPATH, classpath) and curious things happen if you have a name collision between libraries. However unlikely it may seems, it actually happens quite often in practice between different versions of the same library. That is, if you depend on A and B, and A depends on C 1.0.0 and B depends on C 2.0.0 then you have a name conflict between two version of C.
And Rust is quite different in this regard. First of all, it has
modules which are analogous to similar concepts in other languages. But is also has
crates, which are like libraries in other languages, except that in Rust this concept is reified. So, a single crate is a tree of modules, and the crucial thing is that the root module of a crate is anonymous. You declare dependencies between crates by using an
extern crate foo; syntax. And
<hand-waving> when you compile your project, it is the job of cargo to pass flags to the compiler for "look the crate named
foo in the directory
</hand-waving>. This allows to have several versions of a same library in a single binary without any problem (well, if the library itself does not assume that there can be only single copy of it).
To say the same with less words, lets quote the reference
Note: Unlike in many languages, use declarations in Rust do not declare linkage dependency with external crates. Rather, extern crate declarations declare linkage dependencies.
So, module is a unit of abstraction/encapsulation, and if you want logical modularity, split your code in different modules.
A crate is a unit of compilation and linking, and if you want physical modularity, split your code in different crates.
Corollary: you can have cyclic dependencies between modules, but crates form a direct acyclic graph
Corollary: coherence actually work.
And now the messy part about tools support for this beautiful modularity =)
Currently, the unit of incremental compilation is a crate, so you may want to separate code into several crates just to speed up incremental compilation (In my minim I segregate parsing into a separate crate because LALRPOP (which is wonderful, btw), generates massive amounts of code and I don’t really enjoy recompiling it while I mess with other parts of implementation).
And there is also a push in the opposite direction. While
cargo makes it easy to split a project into several crates with
[dependencies.cionc_parser] path = "src/parser", it currently lacks convenient shortcuts for execute commands on all crates simultaneously. And the hack by @LilianMoraru is exactly for working around this problem. It makes tests from the root cargo project to point inside the children projects.
There is an RFC about similar topics: https://github.com/rust-lang/rfcs/pull/1525.
And in you particular case, maybe you should just use separate modules instead of separate crates?