Skip to content

RFC: brand Keyword for Structural Branded Types #61479

Closed as not planned
Closed as not planned
@clearfram3

Description

@clearfram3

🔍 Search Terms

"brand", "brand type", "branded", "branded types", "structural brand", "tag type"

✅ Viability Checklist

⭐ Suggestion

Add a brand keyword to TypeScript for defining structurally branded types based on primitives (e.g., string, number, bigint). This feature introduces compile-time type distinction without runtime overhead, ensuring that branded types remain structurally compatible with their base types for operations while being distinct in assignment contexts. For example, UserId and ProductId, both branded from number, cannot be assigned to each other but can be used in arithmetic operations together.

📃 Motivating Example

TypeScript’s structural typing is powerful but can cause issues when types that are structurally identical represent different semantic concepts, like UserId and ProductId both being number. Developers currently use intersection types (e.g., number & { __brand: 'UserId' }) to enforce distinction, but this approach is cumbersome and leads to confusing error messages. The brand keyword provides a clean, native solution that enhances type safety and developer experience. Here’s how it improves the language:

brand type UserId = number;
brand type ProductId = number;

let userId: UserId = 1 as UserId;
let productId: ProductId = 2 as ProductId;

userId = productId; // Error: Type 'ProductId' is not assignable to type 'UserId'
let total: number = userId + productId; // OK: Both are structurally numbers

This feature eliminates the need for hacks, provides clear error messages, and aligns with TypeScript’s structural typing philosophy.

💻 Use Cases

  1. What do you want to use this for?
    To safely distinguish between different kinds of identifiers, measurements, or other primitive-based types that should not be interchangeable, such as UserId vs. ProductId, or Kilometers vs. Miles.

  2. What shortcomings exist with current approaches?

    • The intersection type pattern is verbose and results in error messages that reference internal branding properties, which can be confusing.
    • Using wrapper classes introduces unnecessary runtime complexity and overhead, especially for primitives.
  3. What workarounds are you using in the meantime?

    • Employing intersection types like type UserId = number & { __brand: 'UserId' }; to achieve type distinction.
    • Creating constructor functions to enforce branding, but still relying on intersection types for the actual distinction.

The brand keyword addresses these issues by offering a straightforward, language-supported way to define and enforce branded types, improving both safety and usability in TypeScript projects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions