There is a usability tension between implementing a function directly on a struct, and implementing a trait for that struct that defines that method. In particular, when a function is defined through a trait (that isn’t in scope by default), we have to use
that trait, but if we implement the function directly on the struct, we can call it without use
ing anything.
There’s an ugly workaround to avoid the need for callers to use
the trait: define the function directly on the struct, and then implement the trait in terms of the directly-implemented function. See the code below, which is also available at https://is.gd/30Fo09.
mod traits {
pub trait T {
fn t(&self);
}
}
mod inconvenient {
pub struct S { }
impl super::traits::T for S {
fn t(&self) { println!("t()") }
}
}
mod convenient {
pub struct S { }
impl S {
// Allow caller to call `S::t()` without importing the `traits` module.
pub fn t(&self) { println!("t()") }
}
impl super::traits::T for S {
// Implement `T::t()` in terms of `S::t()` so they are consistent.
fn t(&self) { S::t(&self) }
}
}
fn works_but_requires_use() {
use traits::T;
let x = inconvenient::S {};
x.t();
}
fn works_without_use() {
let x = convenient::S {};
x.t();
}
fn broken_without_use() {
// This doesn't work because we didn't `use traits::T;`.
let x = inconvenient::S {};
x.t();
}
fn main() {
works_but_requires_use();
works_without_use();
broken_without_use();
}
I would like to propose a solution:
mod more_convenient {
pub struct S { }
impl super::traits::T for S in scope by default {
fn t(&self) { println!("t()") }
}
}
Note in particular the in scope by default
extension to the impl
. The idea is more_convenient
would get desugared into convenient
. In particular, one would be able to call methods defined in a in scope by default
impl as if they were defined directly on the struct (because they are). Note in particular that the trait T
itself is not brought into scope. That is, you could do this:
fn also_works_without_use() {
let x = more_convenient::S {};
x.t();
}
But, you can’t do this:
fn does_not_work() {
let x = more_convenient::S {};
<x as T>::t(&x); // `T` is not in scope.
}
I intentionally chose a syntax that nobody would like. I suggest we decide whether this is a good or bad idea without worrying about the syntax.