Trait importing syntax


#1

Most of the time, I’m a Python programmer, but sometimes I want to do something that requires compiled performance or a standalone executable or something, and I’m really interested in Rust as a complement to Python in my toolbox.

One of my favourite things about Python is that, if you’re following the style-guide, then every identifier in a source file is either defined in that file, or specifically imported at the top. Conversely, if you see a name imported at the top of the file and it’s not mentioned anywhere else, that means it’s an unused import and you can remove it.

In Rust, as far as I can tell this is almost true: it’s true for importing modules, but not for activating traits, which are lexically identical to module imports:

use a::Foo;
use b::Bar;
use c::Baz;
use d::Qux;

Quick, which of those is an unused import, and which is a trait activation that’s vitally important to the running of the code?

It’s probably too late to change this now, but I wish the trait activation syntax were visibly different from importing. For example, in the same way that you have mod foo { ... } to define a module and mod foo; to pin it in place, maybe you could have a trait declaration:

use a::Foo;
use b::Bar;
trait c::Baz;
use d::Qux;

Alternatively, you could split the two uses of use with a modifying keyword:

use mod a::Foo;
use mod b::Bar;
use trait c::Baz;
use d::Qux; // this is a struct, not a module!

For backwards compatibility you could say that without mod or trait, use guesses what kind of thing you’re importing, but let the style-guide encourage people not to make the computer guess.

(For completeness, it can also be confusing if you see code calling foo.bar() and foo does not implement a bar() method. Obviously a trait is involved somewhere, but figuring out which one can be tedious. I can’t think of a way to make it not tedious without destroying the pleasant conciseness of traits, but at least if trait-imports were easy to spot, it would narrow the search space.)


#2

The good thing is that there is a unusued_imports lint, so you’ll at least get notified if you import too much.


#3

With the module conventions, the module names should start with a lowercase identifier, so should be pretty distinct. There isn’t, however, a distinction made between types and traits.

So, in your example above, it should be:

use a::foo;
use b::bar;
use c::Baz;
use d::Qux;

and the only thing not easy to tell are which ones are traits and which ones are structs. Does this still seem worthwhile to distinguish via syntax?


#4

I started a thread some time back with a proposal to mitigate this: http://discuss.rust-lang.org/t/anonymous-trait-use/1177

To address the visibility issue, perhaps the “use trait” syntax would be more useful, if it’s made mandatory/lint-enforced for traits.


#5

If everybody always sticks to the naming conventions, then that makes things better, but not perfect. I still think it’s important for things with different meanings (import name vs. activate trait) to have different appearances, though.


#6

Why do traits even need to be imported explicitly? If I import some struct Foo, I can use any of the methods defined in the impl for Foo. Why can’t I also call any method in a trait impl for Foo, and have that resolved without importing the trait itself?

Constantly having to add imports for these methods and having to track down which module the trait is located in is really annoying and slows me down while developing.


#7

Because you don’t know which trait to use. Variable foo is of type struct Foo. struct Foo implements trait T1, which defines method m. struct Foo also implements trait T2, which also defines method m. You call foo.m. Which one does it resolve to?


#8

For some reason I had always thought the methods on an object defined by traits had to be unique. If you call Foo::bar() in one file, and then call Foo::bar() in another file and have them refer to two completely different functions based on what trait was imported, that would be very confusing.

But after thinking about it more, this makes sense. A struct should be able to implement any trait, and shouldn’t be restricted by any other traits already implemented. You may want to implement two useful traits on an object, and you should be able to even if the trait authors didn’t know anything about the other trait and used the same method names unknowingly.

But in the majority of cases, there’s only one unique method you can choose. Can we not require the import in these cases, and only require the import if it’s ambiguous?


#9

Can we not require the import in these cases, and only require the import if it’s ambiguous?

We can do that, but that means simply adding a new dependency to your project without modifying any source code can break the project by making methods ambiguous. When that happens, you need to add imports to all places you currently need to import.

I am sorry to say this, but all of these are common knowledge and have been explained over and over in the past. Don’t be surprised if people simply ignore you if you ask “Why do traits even need to be imported explicitly?”.