4.1.6 set (Bazel 8.1+)
extraset gives Starlark a real hash-set for local collection work: unique elements, constant-time add/remove/membership operations, and the usual set algebra operators.1 In rule and macro code, it replaces the old "dictionary as a set" idiom when you only need membership, deduplication, or relationship checks inside one evaluation step.
What A Starlark Set Is
A set is a mutable collection of unique elements. Its type name is "set", and its elements must be hashable, using the same rule as dictionary keys: a value can be a set element if and only if it can be a key in a dictionary.1
There is no set literal syntax in Starlark. Create one with the set() built-in, either empty or from another iterable. The constructor keeps the unique elements and preserves their first iteration order.2
requested = ["linux", "macos", "linux"]
platforms = set(requested)
if "linux" in platforms:
print("linux-specific path is enabled")
Iteration order is the order in which elements were first added. That makes list(set(values)) useful when you need a deduplicated list that still follows the input's first-seen order, but equality does not depend on that order: two sets are equal when they contain the same elements.1
Use It For Local Membership And Algebra
The practical reason to reach for set is that the operation you are writing is about membership, not sequence position. A list says "these values in this order." A set says "these unique values, and I need to ask what is present."
That matters in small bits of rule-authoring logic:
SUPPORTED_MODES = set(["debug", "release", "profile"])
def _validate_modes(modes):
requested = set(modes)
unknown = requested - SUPPORTED_MODES
if unknown:
fail("unknown modes: %s" % ", ".join(sorted(list(unknown))))
Set operators cover the common cases directly:
| Operation | Meaning |
|---|---|
a | b | union: values in either set |
a & b | intersection: values in both sets |
a - b | difference: values in a but not b |
a ^ b | symmetric difference: values in exactly one set |
The augmented forms |=, &=, -=, and ^= update the left-hand set in place.1 Use those when the set is a scratch collection local to the current function. If the value came from another module, remember the normal Starlark mutability rule: mutable values can be frozen, and attempts to update a frozen set fail.1
Operators Are Stricter Than Methods
The operator forms require sets on both sides. The method forms accept broader collections. For example, s | t requires t to be a set, while s.union(t) can accept a sequence or dictionary of hashable elements.1
That gives you a useful style rule:
allowed = set(["//app", "//lib"])
# Good when the right side may still be a list from an attribute.
all_packages = allowed.union(ctx.attr.extra_packages)
# Good when both sides are already sets and the algebra is the point.
shared_packages = allowed & set(ctx.attr.required_packages)
Relationship methods make checks read like the policy they enforce. issubset() asks whether every value in one collection is allowed by another. issuperset() asks the inverse. isdisjoint() checks that two collections have no overlap.1
required = set(["compile", "link"])
provided = set(ctx.attr.capabilities)
if not required.issubset(provided):
fail("tool is missing required capabilities")
The Old Dict Trick Is Now A Compatibility Choice
Before set, Starlark authors often wrote dictionaries whose keys carried the membership information and whose values were all True:
seen = {x: True for x in values}
if candidate in seen:
...
That worked because dictionaries also provide constant-time key lookup and deterministic key iteration.3 It is still a compatibility escape hatch for .bzl files that must support Bazel releases before the core Starlark set type, which the current depset API reference describes as available since Bazel 8.1.4 For Bazel 8.1 and newer, prefer set() when the value is semantically a set. The code says what it means, and you get union, intersection, difference, symmetric difference, subset, superset, and disjointness operations without encoding them through dictionary keys.
Do Not Confuse set With depset
set and depset solve different problems. A set is a mutable local collection. A depset is an immutable structure designed for efficient transitive merging across the dependency graph.4 If you are inside one helper function, validating attributes, deduplicating a bounded collection, or checking overlap between two local groups, set is the direct tool.
If the data comes from dependencies and will be propagated to downstream targets, keep using depset. Depsets are not simply hash sets and do not support fast membership tests. They are commonly used for accumulating data from transitive dependencies in rules and aspects.4 That boundary connects directly to 4.1.5 depset vs list and becomes part of the provider contract once you write full rules in 4.2.1 Rule Function.
# Local, bounded, and checked immediately: set.
direct_inputs = set(ctx.attr.src_names)
# Transitive data crossing rule boundaries: depset.
return [MyInfo(files = depset(
direct = ctx.files.srcs,
transitive = [dep[MyInfo].files for dep in ctx.attr.deps],
))]
Use set when the code is asking local membership or set-algebra questions. Use depset when the data is transitive rule data that must merge efficiently through the graph.
For Bazel 8.1+ rule-authoring code, the dict-key hack should be a compatibility fallback, not the default expression of a set.
Check your understanding · 3 questions
1.Which situations are good fits for Starlark set in Bazel 8.1+ rule code?
Select all that apply
2.Which statements about Starlark set in Bazel 8.1+ are true?
Select all that apply
3.True or false: choosing between set, dict, and depset in Bazel 8.1+ rule code.
Choose True or False for each sentence
set is clearer than a {x: True for x in values} dictionary when the value is semantically a set..bzl code must support Bazel releases before the Starlark set type.Footnotes
-
set — set type semantics, hashability, iteration order, operators, mutation, and relationship methods. ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
All Bazel files —
set()constructor and first-seen iteration order. ↩ -
dict — constant-time key membership and deterministic key iteration. ↩
-
depset — depsets are not hash sets,
setavailability since Bazel 8.1, dict fallback for older Bazel, and transitive merge semantics. ↩1 ↩2 ↩3