Type System
Starlark Type System
Section titled “Starlark Type System”Starlark supports optional type annotations similar to Python’s type hints. Different implementations offer varying levels of type checking.
Basic Type Annotations
Section titled “Basic Type Annotations”Add types to function parameters and return values:
def add(x: int, y: int) -> int: return x + y
def greet(name: str) -> str: return "Hello, " + nameBuilt-in Types
Section titled “Built-in Types”| Type | Description | Example |
|---|---|---|
int | Integer values | 42 |
float | Floating-point numbers | 3.14 |
str | String values | "hello" |
bool | Boolean values | True, False |
None | The None value | None |
list | List values | [1, 2, 3] |
dict | Dictionary values | {"a": 1} |
tuple | Tuple values | (1, 2) |
Generic Types
Section titled “Generic Types”Specify element types for collections:
def sum_numbers(numbers: list[int]) -> int: total = 0 for n in numbers: total += n return total
def lookup(data: dict[str, int], key: str) -> int: return data[key]
def point() -> tuple[int, int]: return (10, 20)Tuple Variants
Section titled “Tuple Variants”# Fixed-size tuple with specific typesdef rgb() -> tuple[int, int, int]: return (255, 128, 0)
# Variable-length tuple (all same type)def numbers() -> tuple[int, ...]: return (1, 2, 3, 4, 5)Union Types
Section titled “Union Types”Allow multiple types using |:
def process(value: int | str) -> str: if type(value) == "int": return str(value) return value
def maybe_find(items: list, key: str) -> str | None: for item in items: if item.key == key: return item.value return NoneSpecial Types
Section titled “Special Types”From the typing module (where available):
| Type | Description |
|---|---|
typing.Any | Matches any value |
typing.Callable | Any callable (function) |
typing.Iterable | Any iterable value |
typing.Never | No valid values (e.g., fail() return) |
def accepts_anything(x: typing.Any) -> None: print(x)
def run_callback(fn: typing.Callable) -> None: fn()Record Types
Section titled “Record Types”Records provide structured data with type-checked fields:
# Define a record typePerson = record( name = str, age = int, email = str,)
# Create instancesalice = Person(name="Alice", age=30, email="alice@example.com")
# Access fieldsprint(alice.name) # "Alice"print(alice.age) # 30
# Type-safe: this would be a runtime error# bob = Person(name="Bob", age="thirty", email="bob@example.com")Default Values
Section titled “Default Values”Use field() for optional fields with defaults:
Config = record( host = str, port = field(int, 8080), # Default: 8080 debug = field(bool, False), # Default: False)
# port and debug are optionalcfg = Config(host="localhost")print(cfg.port) # 8080Enum Types
Section titled “Enum Types”Enums represent a fixed set of values:
# Define an enumStatus = enum("pending", "running", "completed", "failed")
# Create valuescurrent = Status("running")
# Access propertiesprint(current.value) # "running"print(current.index) # 1
# List all valuesprint(Status.values()) # ["pending", "running", "completed", "failed"]
# Iteratefor s in Status: print(s.value)
# Length and indexingprint(len(Status)) # 4print(Status[0]) # Status("pending")Using Enums in Functions
Section titled “Using Enums in Functions”LogLevel = enum("debug", "info", "warn", "error")
def log(level: LogLevel, message: str) -> None: print("[{}] {}".format(level.value.upper(), message))
log(LogLevel("info"), "Server started")# [INFO] Server startedType Checking Modes
Section titled “Type Checking Modes”Types can be checked at different times:
- Runtime - As functions execute (most common)
- Static analysis - Without execution (tooling)
- Compile time - During loading (build systems)
# Runtime checking: error raised when called with wrong typedef square(n: int) -> int: return n * n
square(5) # OK: returns 25square("5") # Runtime error: expected int, got strBest Practices
Section titled “Best Practices”- Add types to public APIs - Makes interfaces clear
- Use records over dicts - Better type safety and memory efficiency
- Prefer specific types -
list[str]overlistwhen possible - Document with types - Types serve as documentation
# Good: Clear types with defined recordUser = record(name=str, age=int)
def create_user(name: str, age: int) -> User: return User(name=name, age=age)
# Avoid: Unclear types, untyped dictdef create_user_bad(name, age): return {"name": name, "age": age}Implementation Support
Section titled “Implementation Support”| Feature | starlark-rust (Buck2) | starlark-java (Bazel) | starlark-go |
|---|---|---|---|
| Basic annotations | ✅ | ✅ | ✅ |
| Generic types | ✅ | ⚠️ Limited | ⚠️ Limited |
| Union types | ✅ | ❌ | ❌ |
record type | ✅ | ❌ | ❌ |
enum type | ✅ | ❌ | ❌ |
| Static checking | ✅ | ⚠️ WIP | ⚠️ Limited |