Random idea: Implied Traits


#1

I had a thought and wanted to post it here. I’d be interested to hear if it’s something that would be unsound, hard-to-implement, add too much complexity, etc.

The idea is that all structs would introduce an implied trait exposed via some syntax extension. The trait members would contain all public functions defined in the struct’s impls.

For example:

struct Person {
    ...
}

impl Person {
    pub fn eat(&mut self) {
        ...
    }

    pub fn terminate(&mut self) {
        ...
    }

    fn digest(&mut self) {
        ...
    }
}

In the above example, Person introduces an implied trait equivalent to:

trait Person {
    pub fn eat(&mut self);
    pub fn terminate(&mut self);
}

The trait would be referenced via Person::Impl.

The use-cases I would find this to be useful are:

  • Creating mock implementations of a type for testing
  • Wrapping a canonical implementation of a struct and extending it. For example, I’m creating an emulator and have a Cpu trait. I have a canonical implementation, and an implementation that adds debugging capabilities. It would be nice to do something like this:

struct Cpu { ... }

impl Cpu {
    pub fn step(&mut self) { ... }
    pub fn reset(&mut self) { ... }
    ...
}

struct CpuWithDebugger {
    inner: Cpu,
    ...
}

impl Cpu::Impl for CpuWithDebugger {
    pub fn step(&mut self) {
        self.inner.step();
        // Do debugger stuff
    }
    ...
}

On the testing front, it would make it much easier to write code that accommodates mocking:

pub fn important_stuff<T: Person::Impl>(person: &mut T) {
    // really important stuff that needs to be tested
}

struct MockPerson { .. }

impl Person::Impl for MockPerson {
    ....
}

#[test]
fn test_important_stuff() {
    // The complex interactions of the canonical Person impl would normally make this hard to test
    let person = MockPerson::new();
    important_stuff(person);
    assert_eq!(false, person.terminate_function_called());
}

#2

I think you should be able to do this today with a procedural macro: #[derive(TraitFromPubFunctions)]


#3

Ah, good point!

I see a couple downsides, though:

  • The trait name would have to follow a convention or be user-specified, which wouldn’t be as nice as something like SomeStruct::Impl.

  • Tooling would require macro expansion in order to know that the trait exists. Introducing things from a macro always feel kinda dirty.

I’m also curious how a macro would accommodate structs with multiple impl blocks, and how it would implement the trait while retaining all the original semantics of a struct impl.