Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: brand Keyword for Structural Branded Types #61479

Closed
6 tasks done
clearfram3 opened this issue Mar 24, 2025 · 5 comments
Closed
6 tasks done

RFC: brand Keyword for Structural Branded Types #61479

clearfram3 opened this issue Mar 24, 2025 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@clearfram3
Copy link

🔍 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.

@MartinJohns
Copy link
Contributor

Duplicate of #202.

@clearfram3
Copy link
Author

@MartinJohns we have the same last name :)

My proposal isn’t actually the same. #202 wants true nominal types that don’t behave like their base (e.g. a “nominal string” might not be assignable to string anymore). That’s a much bigger departure from TypeScript’s structural model. My brand keyword is just a lightweight way to keep using the underlying primitive (like number) seamlessly for math or function calls, while preventing accidental assignment between incompatible “brands.” It’s more in line with TypeScript’s existing patterns and addresses the common ID-distinction scenario without forcing nominal typing everywhere.

@MartinJohns
Copy link
Contributor

My brand keyword is just a lightweight way to keep using the underlying primitive (like number) seamlessly for math or function calls, while preventing accidental assignment between incompatible “brands.”

That's #202.

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Mar 27, 2025

Yeah, I don't think anyone wants branded types that are not assignable to their base types. That doesn't make much sense (may as well use a symbol at that point)

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Mar 27, 2025
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Mar 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants