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
bykeyword 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 consts may be imported, and, if the relevant feature gate is enabled, so may associated types.
This feature may also be used in trait impls:
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
implblock. - The imported name clashes with an existing definition in the
implblock (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
useitem in a trait impl; it is always implied to bepub use, as with all other trait impl items. Drop::dropmay not beused, 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 ifThas 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/ArenaExtexample 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).