[rust-dev] C++ to Rust - Is that about right?

Robin Kruppe robin.kruppe at gmail.com
Mon Jul 14 21:33:41 PDT 2014


Re-CCing list because you don't seem to have dropped it intentionally
and I'd prefer more experienced people to shout at me if I say
something wrong.


On Tue, Jul 15, 2014 at 5:25 AM, Christoph Husse
<thesaint1987 at googlemail.com> wrote:
>
> > you're thinking in one paradigm exclusively while using in a multi-paradigm
>
> Well, it's really hard to see how to do it otherwise. I think maybe
> the tutorial just needs to make it more clear and keep in mind that
> most people come from a C#, C+ or Java background. It's nice to
> present the "Rust" way but that is not enough to make it clear.
> Somewhere there needs to be an obvious presentation of how to make the
> transition from the "normal" patterns to the "rust" patterns.

You're right, this is yet another demographic that would benefit from
custom tutorials. However, I also believe many people can successfully
pick up the general style just by reading "generic" material. Whether
they'll be sold on it is another question...

> > Second, the signature of MyClass::new() in your example is... unusual.
>
> Yes I noticed that too :D. But up till now I haven't looked into the
> different pointer & reference types yet, that will be next on my list.
> Just what I noticed is that I can't use trait implementations on the
> struct object. I seem to NEED to cast the struct into a trait to use
> the traits method (in contrast to what C# extension methods allow).
> Is that a limitation of Rust right now or a design choice? Why doesn't
> it know all the interfaces a trait/struct implements somewhere and
> then let's you use all that without casting? I could imagine that this
> would make things a lot easier for users. After all I can safely cast
> it anyway, so the compiler could also do it behind the scenes without
> exposing it to the user?

This has nothing to do with pointers/references and everything to do
with the trait system. You can (and probably already did, there's a
couple in the prelude) use trait methods on concrete types. But the
trait needs to be in scope (imported) for its methods to be available.
Lacking time and technical writing skills to give a full tour, let me
just sketch the reasons:

Traits are not just Java interfaces, in particular they can be added
almost anywhere and at any time (even in a completely different
crate), so not only do they sometimes clash with each other (and
importing one is a way to disambiguate) it's also pretty hard to make
a comprehensive list of all trait impls.
It's probably technically feasible to automagically import the impls
that are in the same crate but this is undesirable for other reasons:
It's surprising, can still lead to name collisions and pollutes
namespaces in the same way glob imports do. It also makes less sense
when one takes a less OOP-y interpretation of traits (e.g. as type
classes) because many of the traits implemented for one type may not
be interesting for most users.
For example, if MyClass implements a serialization trait that the web
framework uses internally, then none of my code needs that trait or
its methods. Pulling them in adds no value and has the aforementioned
downsides, especially if the web framework chose a method name you'd
like to use for your actual business logic (cf. explicit interface
implementations in C#). Keeping that trait out of my way makes life
easier for me and for the framework authors, they don't need to choose
stupid non-clashing names for their internals and I don't need to
disambiguate (or worse, rename *my* methods).

At first glance this appears to lead to a cascade of additional
imports. This doesn't appear to be the case for all Rust code I've
seen so far, not even the trait-heavy of your examples. First, usually
many methods are defined in "impl MyStruct" blocks (those *are* always
imported alongside MyStruct), or you'd want to import the trait
anyway: Either to specify it as bound in a generic (fn foo<T:
Trait>(x: T)) or to speak about trait objects (fn foo(x: &Trait)). In
my limited experience, there's one trait I recall importing only to
use its methods, and that's AdditiveIterator which is essentially an
extension method (sum()) in C# parlance.

> Further I still wonder about the file hierachy. Does that mean it's
> really intended that I need to organize the files like I have shown
> above? Or is there a way to export a type into a specific module
> within a crate without it having to be an a specific file for that
> module (which from my POV wouldn't make things more obvious xD)?

No, as Daniel says, you can organize your code in (almost) any way you
like and still present the same public API. This is a significant
difference from the languages you know, with the theoretical (ignoring
existing conventions) exception of C++. To re-export, just "pub use"
it: http://doc.rust-lang.org/tutorial.html#reexporting-names
You can check out the libstd source code for extensive examples. Many
of the modules in its API are actually just a bunch of re-exports,
often even from different crates! std::sync for example is almost
entirely documentation and "pub use core_sync::*". It also defines two
types (Future and TaskPool), which it puts into private sub-modules
(std::sync::future and std::sync::task_pool) for organizational
purposes but exports as std::sync::{Future,TaskPool}. None of this
affects the API or even details of the rustdoc.

> Maybe it's also just because rust is new and still missing best
> practices. I just think maybe someone with a better overview should
> draw up some best practices of how to organize large projects without
> having large monolythic files as most rust projects do at the moment.

Auron recently started an effort in that general direction, though
concerning many many topics beside file organization take precedence:
https://mail.mozilla.org/pipermail/rust-dev/2014-July/010735.html

But I don't think the situation is as dire as you think. The
organization of most rust code perhaps seems more disorganized to you
than it seems to readers from other backgrounds (including the
authors). Coming from Python for example, it is not at all natural to
have one file per class. The boundaries of a module are a balance
between small files, keeping tightly related code together (this can
mean up to tens of small classes per module), and exposing a
convenient, coherent API (non-issue in Rust due to re-exports). It's
another consequence of not making class relationships the pillar of
the design.

> Further I notices that they often seem to mix test and production code
> which is also strange to me. For instance with test driven development
> you have tons of problems with that approach. Only mentioning a few:
>
> 1) Test code will most likely be at least in the order of magnitude of
> the production code it tests, so putting them together in the same
> directory or even file will get unhandy pretty quickly
> 2) The test code this way can access internals of the module, which by
> definition should be avoided, since internals are implementation
> details and if your tests use them you will get cascades of test
> failures for each simple design change
> 3) Performance, mostly. To have fast iteration cycles, tests most
> likely need to be grouped an executed/compiled intelligently (which
> needs some compiler or IDE support) so that only tests that are
> relevant to the changes you just made will be compiled and executed.
> If you mix tests with production code then then it is a lot more
> involved to sieve out tests during compilation, linking and
> execution... that not need to run.
>
> best
> chris


More information about the Rust-dev mailing list