Names of traits that enable mocks

I can distinguish two types of trait, at least for the purpose of naming them in the application I'm developing.

  • A role-named trait is named for what it does and may have several implementors. For example, trait Printable might have a function print() that is implemented by diverse structs such Invitation and Order. struct PrintScheduler has something like pub fn queue(&mut self, to_be_printed: Arc<Mutex<dyn Printable>>) to queue things to to be printed in due course.
  • A service interface trait abstracts a single concrete struct so it can be injected and mocked. I've not yet been able to find a Rust-idiomatic way to name such traits. So let's explore one of many real examples from my application.

Tuner is a struct that receives microtuning specifications in a given format, and converts each microtuning into a set of MIDI messages that are then sent to a synthesizer of a particular range of models. From the MIDI messages, the synthesizer's firmware configures its tuning to conform with the required microtuning. To facilitate testing, actually sending the MIDI messages is decoupled from Tuner . We need:

  • a struct that can actually send MIDI messages (the production struct);
  • a struct that sends no MIDI messages but allows tests to confirm that Tuner will send them correctly (the mock struct);
  • a trait that allows either struct to be injected into Tuner .

What would you call the two MIDI sender structs and their common trait? The mock struct is the easy one: MockMidiSender. But what about the the production struct and the trait? There are several options.

  • trait MidiSender with something like struct MidiSenderImpl or struct RealMidiSender. Yet, as the production struct is what actually sends MIDI, we should not have to qualify its name. And I understand that Rust naming guidelines discourage Impl suffixes for structs.
  • struct MidiSender with something like trait MidiSenderTrait or trait MidiSenderBase. It's a trait, so it should not need a Trait suffix. And Base sounds like a superclass in object-oriented programming languages, which it not what is going on here.
  • struct MidiSender with trait TMidiSender. I like that. However, T in Rust is a common type parameter, so perhaps it would be better to avoid it in this context.
  • struct MidiSender with trait IMidiSender, where I stands for interface and is the standard prefix for interface names, where interface is the equivalent of trait in .NET. This is what I've chosen as the standard for service interface traits in my application. But many Rust developers will understandably not like borrowing from a rather different development technology. I'm an experienced C#/.NET developer and am quite new to Rust.

What would you do?

While not answering your questions directly...

I totally understand, your examples are artificially, but I would like to advice you to drop the OO-style of solving problems because I think the problems you're trying to solve are originating from "OO tactics".

My suggestion is to use traits like some sort of "last resort" and first look out for solutions using concrete types.

For the printing example, instead struct PrintScheduler accepting trait Printable it should accept something like a struct PrintJob or whatever: a concrete type, maybe even a POD like data, or a Vec with print commands etc.

Then printing clients provide methods like print_document(&self) -> PrintJob, and print_overview(&self) -> PrintJob etc.

For testing, you can simple check the generated PrintJob of some clients match your expectation.

Same thing for the PrintScheduler. It accepts PrintJob as inputs and generates some output, whatever the role of the concrete PrintScheduler is. The generated output of processed PrintJobs are also checked against your expectation etc.

In summary: pass around concrete data: a call-site ask object A to generate some data (struct), takes this data and feeds this data into object B. That's how things fits nice together. There is no need, that A implements some interface (trait) that B uses to call back on A.

Shuffle concrete data around, and things become simple(r).