Address type param name conflict with trait name

I'm trying to come up with consistent naming and ran into this issue:

    /// Provides the forecaster with ability to calculate taxes.
    pub trait TaxCalculator {
        /// Calculate the taxes.
        /// 
        ///   * _return_ - Calculated taxes.
        fn calculate_taxes(
            & mut self
        ) -> f64;
    }

    ////////////////////////////////////////////////////////////////////////////////////
    // --- structs ---
    ////////////////////////////////////////////////////////////////////////////////////
    /// A simulator
    pub struct Simulator<TaxCalculator>
    where
        TaxCalculator: TaxCalculator + Send + Sync + Clone {
        /// A tax calculator.
        pub tax_calculator: TaxCalculator
    }

This fails with the following error because the type parameter TaxCalculator is the same as the trait name.

   Compiling playground v0.0.1 (/playground)
error[E0404]: expected trait, found type parameter `TaxCalculator`
  --> src/lib.rs:17:20
   |
2  | / pub trait TaxCalculator {
3  | |     /// Calculate the taxes.
4  | |     /// 
5  | |     ///   * _return_ - Calculated taxes.
...  |
8  | |     ) -> f64;
9  | | }
   | |_- you might have meant to refer to this trait
...
15 |   pub struct Simulator<TaxCalculator>
   |                        ------------- found this type parameter
16 |   where
17 |       TaxCalculator: TaxCalculator + Send + Sync + Clone {
   |                      ^^^^^^^^^^^^^ not a trait

For more information about this error, try `rustc --explain E0404`.

I can address by either:

  1. Changing the name of the trait to something like TaxCalculatorTrait
  2. Change the name or casing of the type parameter - e.g. T, TAX_CALCULATOR, TaxCalculatorImpl
  3. Introducing a new name? Maybe my thought process is not great and I'm doing it wrong.

I feel like my naming is reasonable. I thought a trait was an abstraction characterized by a set of functions. In this case there will be many types of tax calculators that will implement the trait.

From The Rust Programming Language

To parameterize the types in a new single function, we need to name the type parameter, just as
we do for the value parameters to a function. You can use any identifier as a type parameter name.
But we’ll use T because, by convention, type parameter names in Rust are short, often just a letter,
and Rust’s type-naming convention is CamelCase. Short for “type,” T is the default choice of most
Rust programmers.

I can see how short single letter type names work for very common types of parameterization like collection classes where there is little confusion on T or maybe K,V are. But I don't see that approach working well in this situation.

What are suggestions and how can I think about this differently? Ideally I'd like an approach that I can just reach for whenever this occurs and not have to come up with new names. The AbstractionTrait for naming a trait or maybe TAbstraction for naming a type parameter does not appear to be done in rust and I'm not sure why (too verbose?). I haven't coded C# but it looks like they address this similar naming issue as follows:

Names of Classes, Structs, and Interfaces

:heavy_check_mark: DO prefix descriptive type parameter names with T .

public interface ISessionChannel<TSession> where TSession : ISession {
    TSession Session { get; }
}

One indirect mitigation: It's usually better not to put bounds on the type parameters in the struct definition.

That would let you use

    /// A simulator
    pub struct Simulator<TaxCalculator> {
        /// A tax calculator.
        pub tax_calculator: TaxCalculator
    }

which works fine.

Then in the various impls you could just do

impl<C: TaxCalculator> Simulator<C> {
    ...
}

With the bound there, and with a shorter name for the type parameter since it's less important there.

2 Likes

I can't disagree with this strongly enough. I absolutely hate Microsoft's naming conventions - prefixing I to interfaces, or T to type params seems like a throwback to Microsoft's awful obsession with hungarian notation. It's noise you don't need in well written code.

Maybe don't take naming advice from a company who names OS versions like this:
3.1
3.51
4
2000
XP
Vista
7
8
10
11

1 Like

My habit in this situation is to use a blatantly abbreviated name which still helps remind the reader what it is: <Calc: TaxCalculator>

2 Likes

I would personally go with @kpreid's solution, but if you're attached to using TaxCalculator as the parameter name you do still have lots of options!

Playground

/// Provides the forecaster with ability to calculate taxes.
pub trait TaxCalculator {
    /// Calculate the taxes.
    ///
    ///   * _return_ - Calculated taxes.
    fn calculate_taxes(&mut self) -> f64;
}

/// Use a path to refer to the trait. `self` refers to the current module, but you could also use a `crate::path::to::TaxCalculator` path.
pub struct Path<TaxCalculator: self::TaxCalculator>(TaxCalculator);

/// Rename the trait in the import rather than changing the actual name of the trait.
use crate::TaxCalculator as TaxCalculatorTrait;
pub struct Renamed<TaxCalculator: TaxCalculatorTrait>(TaxCalculator);

/// Re-export the trait from a private module with a useful name. 
/// This is the basically the same as the [Path] solution, the dummy module name just reads a little nicer. 
/// It also might result in a much shorter path if [TaxCalculator] is in an inconvenient module.
mod traits {
    pub use super::TaxCalculator;
}
pub struct Reexport<TaxCalculator: traits::TaxCalculator>(TaxCalculator);

I've also seen this problem solved by using different tenses. For example the trait might be TaxCalculating while the type parameter would be TaxCalculator but that can be hard to follow since the names are so similar.

2 Likes

Wow, I did not know that, thanks! It simplified things a bit. Is that a generally accepted tenet then? Is it the case that bindings in structs are not really helpful?

I will go with this for the name.

The general consensus is that they restrict the struct unnecessarily. If you use the bound only on the impl, then you can add additional impl blocks with different bounds to allow structs with different functionality. Placing the restriction directly on the struct prevents that sort of reuse.

They're important for things like Cow where you need the bound to get the type of the struct field.

Otherwise it's not helpful in practice because it requires that you copy-paste the bound everywhere, even places that wouldn't otherwise need it.

For example, by not having the bound you can do

impl<C: Debug> Debug for Simulator<C> { ... }

rather than needing to say

impl<C: TaxCalculator + Debug> Debug for Simulator<C> { ... }

because I bet you're not calling any of the TaxCalculator methods in the Debug implementation.