I hear people complain that having to pull in random traits to access core functionality of certain types all the time, and I think this is one of Rust's biggest usability issues (prelude modules are a ridiculous workaround). Hopefully this is the minimum proposal that can solve this rough edge once and for all.
tl;dr use
items are permitted in impl
blocks for pulling in trait methods.
Motivation
Suppose I have a type whose sole purpose is to implement some trait, like one of the byteorder
traits. I should not have to write use byteorder::ByteOrder as _;
to access those functions. Another good example is arena allocation APIs in my library, which results in use ArenaExt as _;
in code that manipulates an A: Arena
; this is a fairly common pattern in other code I've encountered.
Non-goals
- Solving general delegation, e.g. delegating a method to a field (e.g. see the
by
keyword in Kotlin). This is a good problem to be solving, but I think this issue of trait imports is a bigger useability issue for library consumers. - Solving the general "my library has a prelude" problem; this merely makes it so that preludes that import a bunch of traits to make its own types' methods visible are unnecessary.
Guide-level explanation
If I have
struct K;
trait T { fn foo(&self); }
impl T for K {
fn foo(&self) { println!("bang!") }
}
I am entitled to write
impl K {
use T::foo;
}
This is syntax sugar for (roughly)
impl K {
#[inline(always)]
fn foo(&self) {
<K as T>::foo(self)
}
}
Associated const
s may be imported, and, if the relevant feature gate is enabled, so may associated types.
This feature may also be used in trait impl
s:
trait U { fn bar(&self); }
impl U for K {
use T::foo as bar;
}
In the standard library, we could write
impl Formatter<'_> {
use std::fmt::Write::{write_fmt, write_str};
}
(Currently, the impl for Formatter
simply contains these two methods verbatim in its body to make write!
work properly without trait imports.)
Reference explanation
We permit use
items in impl
blocks, where path(s) in the item refer to trait items, such as trait methods, associated consts, or associated types. Each path is expanded into a copy of the referenced trait item, with the exact same signature as in the trait, but potentially renamed with as
, as with any regular use
. use Trait::*;
will import all items.
The visibility specifier on the use
item is applied to each item imported.
use
items' paths may contain type parameters. Currently use Foo<Bar>;
is a parse error, but I see no reason why use
cannot consume a general path. For example, you could use use AsRef<[u8]>;
in an impl
. Type/lifetime parameters of the impl
are visible to the use
items.
It is an error if:
- The path does not resolve to a trait item.
- The compiler cannot prove that the implementee type does not implement the trait in question, given the bounds on the
impl
block. - The imported name clashes with an existing definition in the
impl
block (i.e., as if the method had been defined directly, as in the pseudo-desugaring above). - When importing names into a trait
impl
, the resulting imported definition does not match any of the trait-being-implemented's requirements. - Using a visibility specifier on a
use
item in a trait impl; it is always implied to bepub use
, as with all other trait impl items. -
Drop::drop
may not beuse
d, because Drop Is Special.
For example:
trait T { fn foo(&self); }
trait U { fn bar(&self); }
trait V { fn foo(&mut self); }
struct K;
impl T for K { .. }
struct L;
impl K {
use Vec::new; // Error, not a trait item.
}
impl K {
fn foo(&mut self);
use T::foo; // Error, redefinition of `foo`.
}
impl L {
use T::foo; // Error, L: !T.
}
impl U for K {
use T::foo; // Error, no such declaration U::foo.
}
impl V for K {
use T::foo; // Error, wrong signature for V::foo.
}
impl<X> U for X {
use T::foo as bar; // Error, cannot prove for<X> X: T.
// Would be fine given a blanket impl for T.
}
Note that the desugaring given in the guide-level explanation isn't quote correct; imported methods and consts are true aliases and guaranteed to have the same address.
Open Qs
- Should we be able to write
impl K { use T::*; }
even ifT
has associated types? (This would have the effect of not importing associated types until inherent-assoc-types is stabilized.) - How should this be displayed in rustdoc? How does this interact with the "important traits" feature? (I think that any item imported in this manner into an inherent impl should be noted as having come from a trait in some manner.)
- You couldn't write something like
use T::foo where Self: T;
I don't see a reason not to allow this other than the funky syntax, but it's probably best left as a future extension. - Should we punt
use foo::Foo<T>;
to a future extension? - How should this interact with trait aliases, if at all?
- Should we describe an extension where, given
impl<X: T> U for X { .. }
, we can writetrait T { use U::*; ... }
? This would be useful for making extension traits part of the overall trait, such as in myArena
/ArenaExt
example above. - Should
use Struct::method;
be permitted in trait impls? I.e. the dual of this proposal. Note that it is never valid to use this inside of an inherent impl for obvious reasons.
Prior Art
Best I can tell, this is almost identical in spirit to C++'s class-scope using
declarations. In C++, you can write using Base::Foo;
in a derived class to make Foo
visible as a derived class member (there are some caveats around inherited name lookup that, thankfully, are irrelevant in Rust).