Skip to content

Overview

6 min read

In TypeScript you reach for <T> generics when you want one function or class to work over many types, and interface when you want to describe a shared shape. Rust offers the same two ideas — generics and traits — but builds them on a different foundation: generics are monomorphized into specialized machine code at compile time (not erased like TypeScript’s), traits are nominal contracts you opt into with explicit impl blocks (not structural), and the whole system is governed by trait bounds, trait objects for opt-in dynamic dispatch, and coherence rules that prevent two crates from clashing. This section maps each TypeScript habit onto its idiomatic Rust counterpart, so you can write polymorphic, zero-cost-abstraction code the Rust way.


  • How TypeScript <T> generic functions become Rust fn f<T>(...), and why monomorphization (one specialized copy per concrete type) differs from TypeScript’s type erasure — plus the turbofish ::<> for when inference needs a hint
  • How to put type parameters on structs and enums, including multiple type parameters and constraints attached to impl blocks, with Option<T> and Result<T, E> as the canonical generic enums
  • How a TypeScript interface becomes a trait, written as a separate impl Trait for Type block, and the difference between required and provided (default) methods
  • How to constrain generics with trait bounds (<T: Trait>, multiple bounds with +, and where clauses), including bounds on return types
  • When to use trait objects (&dyn Trait, Box<dyn Trait>) for runtime dynamic dispatch, what dyn compatibility (object safety) requires, and the static-vs-dynamic dispatch trade-off
  • How impl Trait works in argument and return position (RPIT), with a note on return-position impl Trait in traits (RPITIT)
  • How default method implementations and supertraits (one trait requiring another) reduce boilerplate and express prerequisites
  • How to overload operators (Add, Sub, Mul, Index, …) by implementing the corresponding traits, and what the marker traits Send, Sync, Copy, and Sized signal to the compiler
  • Why the orphan rule forbids implementing a foreign trait for a foreign type, and the newtype pattern that works around it

TopicDescription
Generic FunctionsGeneric functions <T>; monomorphization vs TypeScript type erasure; the turbofish ::<>.
Generic StructsGeneric data structures; multiple type parameters; constraints attached to impl blocks.
Generic EnumsGeneric enums, with Option<T> and Result<T, E> as the canonical examples.
TraitsInterfaces → traits: defining and implementing a trait via the impl Trait for Type syntax.
Trait MethodsRequired vs provided (default) methods; calling them and overriding defaults.
Trait BoundsTrait bounds <T: Trait>, multiple bounds, where clauses, and bounds on return types.
Trait ObjectsDynamic dispatch: &dyn Trait / Box<dyn Trait>, dyn compatibility, and static-vs-dynamic trade-offs.
impl Traitimpl Trait in argument and return position (RPIT), with a brief note on RPITIT.
Default ImplementationsDefault method bodies and how they cut implementation boilerplate.
SupertraitsSupertraits (trait inheritance): requiring one trait as a prerequisite for another.
Operator OverloadingOperator traits Add, Sub, Mul, Index, and friends; implementing + for your own type.
Marker TraitsMarker traits Send, Sync, Copy, Sized: what they signal, and auto traits.
The Orphan RuleCoherence and the orphan rule; why you cannot impl a foreign trait for a foreign type; the newtype workaround.

By the end of this section, you will be able to:

  • Write generic functions, structs, and enums, and explain how monomorphization turns one generic source into specialized, zero-cost concrete code
  • Reach for the turbofish ::<> or a binding annotation exactly when inference needs help, and not before
  • Define traits, implement them with impl Trait for Type, and distinguish required methods from provided (default) ones
  • Constrain a generic with the loosest trait bounds that compile, using + and where clauses for readability
  • Choose deliberately between static dispatch (generics / impl Trait) and dynamic dispatch (dyn Trait), and recognize when a trait is not dyn compatible
  • Use impl Trait in argument and return position, and know when a trait method that returns an impl Trait (RPITIT) is appropriate
  • Eliminate boilerplate with default method implementations and express prerequisites with supertraits
  • Overload operators by implementing the relevant std::ops traits, and read what Send, Sync, Copy, and Sized tell the compiler
  • Diagnose an orphan-rule error (E0117) and resolve it by owning the trait or applying the newtype pattern

  • Section 06: Data Structures — structs, enums, Option<T>, pattern matching, and impl blocks; this section generalizes all of them with type parameters and trait contracts (and completes the light introduction to associated types started there)
  • Section 05: Ownership — what &self, &mut self, and self receivers mean, plus moves, borrows, and Clone, all of which trait method signatures depend on
  • Section 02: Basics — concrete types (i32, f64, String), immutability by default, and #[derive(...)]-style trait derivation

  • Reading: 5-6 hours
  • Hands-on Practice: 4-5 hours
  • Exercises: 3 hours
  • Total: 12-14 hours

Tip: Read the topics roughly in listed order. Generics (functions, structs, enums) teach you to abstract over types; traits, their methods, and bounds teach you to abstract over behavior; trait objects and impl Trait are the two dispatch strategies that tie them together; and operator overloading, marker traits, and the orphan rule are the practical edges you will hit in real code. The biggest mental shift for a TypeScript developer is that Rust generics are monomorphized (real specialized code, not erased) and traits are nominal (you must write an explicit impl, not just match a shape).


Next: Section 10: Smart Pointers →Box, Rc/Arc, RefCell/Mutex, Cow, and Weak, where Box<dyn Trait> from this section ties generics and trait objects to heap allocation.