Skip to content

Type System Roadmap

Starlark’s type system is evolving. This page tracks the current state and future direction across implementations.

Meta’s Rust implementation has the most complete type system:

# Full type annotations
def process(items: list[str], count: int) -> dict[str, int]:
result = {}
for item in items[:count]:
result[item] = len(item)
return result
# Record types
Config = record(
name = str,
value = int,
tags = field(list[str], []),
)
# Enum types
Status = enum("pending", "active", "done")
# Union types
def handle(input: str | int) -> str:
return str(input)

Available now:

  • ✅ Function parameter and return types
  • ✅ Generic types (list[T], dict[K, V])
  • ✅ Union types (A | B)
  • record for structured data
  • enum for constrained values
  • typing module (Any, Callable, Iterable)

Experimental:

  • ⚠️ Static type checking (compile-time)
  • ⚠️ Type inference

Google’s Go implementation supports basic annotations:

# Basic type hints (documentation only in most cases)
def add(x: int, y: int) -> int:
return x + y
# No record/enum support
# Use dicts instead
config = {"name": "test", "value": 42}

Available now:

  • ✅ Basic type annotations (syntax support)
  • ✅ Some runtime type checking

Not available:

  • record type
  • enum type
  • ❌ Union types
  • ❌ Generic type checking

Bazel’s Java implementation is actively developing static types:

# Proposed (not yet implemented)
def my_rule_impl(ctx: ctx) -> list[Provider]:
...

Tracking: bazelbuild/bazel#27370

  1. Proposal

    New type features are proposed on bazelbuild/starlark

  2. Discussion

    Maintainers from all implementations discuss feasibility and design

  3. Specification Update

    Accepted features are added to the spec

  4. Implementation

    Each implementation adds support at their own pace

The biggest upcoming change is compile-time type verification:

# With static types, this error is caught before execution
def greet(name: str) -> str:
return "Hello, " + name
greet(42) # Error: expected str, got int

Benefits:

  • Catch errors earlier in development
  • Better IDE support (completion, refactoring)
  • Documentation through types
  • Performance optimizations

Challenges:

  • Maintaining Starlark’s simplicity
  • Backwards compatibility with untyped code
  • Cross-implementation consistency

The likely path forward is gradual typing:

# Fully typed function
def typed_function(x: int) -> int:
return x * 2
# Untyped function (still valid)
def untyped_function(x):
return x * 2
# Mixed: typed functions can call untyped ones
result = typed_function(untyped_function(5))

This allows:

  • Incremental adoption
  • Legacy code compatibility
  • Opt-in strictness

Similar to Python’s protocols for structural typing:

# Hypothetical future syntax
Printable = protocol(
to_string = typing.Callable[[], str],
)
def print_all(items: list[Printable]) -> None:
for item in items:
print(item.to_string())

For complex type definitions:

# Hypothetical future syntax
StringMap = dict[str, str]
Callback = typing.Callable[[int], str]
def process(data: StringMap, handler: Callback) -> None:
...
# Good: Types work across implementations
def calculate(values: list[int]) -> int:
total = 0
for v in values:
total += v
return total

Do: Encapsulate Implementation-Specific Features

Section titled “Do: Encapsulate Implementation-Specific Features”
# If using starlark-rust records, isolate them
# config_types.star (starlark-rust only)
Config = record(name=str, value=int)
def make_config(name: str, value: int) -> Config:
return Config(name=name, value=value)

Don’t: Mix Untyped and Typed Inconsistently

Section titled “Don’t: Mix Untyped and Typed Inconsistently”
# Avoid: Partial typing is confusing
def process(data, count: int): # data untyped, count typed
...
# Better: All typed or all untyped
def process(data: dict[str, str], count: int) -> list[str]:
...
# Avoid: Relying on type errors at runtime
def risky(x: int) -> int:
return x * 2
try:
risky("not an int") # May not error in all implementations
except:
...
# Better: Explicit validation
def safe(x) -> int:
if type(x) != "int":
fail("expected int, got " + type(x))
return x * 2
Featurestarlark-ruststarlark-gostarlark-java
Basic annotations✅ Now✅ Now✅ Now
Runtime checking✅ Now⚠️ Partial⚠️ Partial
Records/Enums✅ Now❌ N/A❌ N/A
Static checking⚠️ Experimental❓ Unknown🔜 In Progress