Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Enums vs Traits vs Generics vs Trait Objects

Rule

Use enums for closed sets, traits for open extension points, generics for static dispatch, and dyn Trait for runtime heterogeneity.

Why

Each choice encodes a different extension and dispatch model, so the API should make that model deliberate.

Do

  • Use an enum when this crate controls every variant.
  • Put shared behavior on a closed enum when callers should not extend it.
  • Use a trait when callers or another layer must supply new behavior.
  • Use impl Trait or a type parameter when one implementor type flows through the call.
  • Use trait objects for runtime selection or heterogeneous collections.
  • Prefer returning concrete types or impl Trait unless runtime polymorphism is required.
  • Follow trait design for contracts, object safety, and sealing.

Avoid

  • Do not create a trait only because closed variants share method names.
  • Do not use a closed enum when downstream users must add variants.
  • Do not spread generics through many layers when a trait object localizes the choice.
  • Do not use dynamic dispatch merely to avoid a type parameter.
  • Do not expose a trait-object API before confirming the trait is object-safe.

Public API Notes

Adding an enum variant can break exhaustive downstream matches. An open public trait commits the crate to supporting external implementors.

Example

#![allow(unused)]
fn main() {
pub enum DeliveryTarget {
    Email(EmailAddress),
    Webhook(WebhookUrl),
}

pub trait Notifier {
    fn notify(&self, message: &Message) -> Result<(), NotifyError>;
}

pub fn notify_once(notifier: &impl Notifier, message: &Message) -> Result<(), NotifyError> {
    notifier.notify(message)
}

pub struct Broadcast {
    notifiers: Vec<Box<dyn Notifier>>,
}
}

Exceptions

  • Use a trait for a closed set when existing generic infrastructure requires it.
  • Use an enum wrapper around trait objects when the public categories are closed but implementations are dynamic.
  • Use generics publicly only when caller flexibility outweighs type complexity.