Types and nullability
Meta: nullability is the page's main attraction. Most readers won't have seen GraphQL-style T! outside of a schema and may misread it as "definitely-null" or "force-unwrap."
Built-in types
Int!,Float!,String!,Boolean!,ID![T]and[T]!— lists{{ ... }}— records- custom GraphQL scalars:
URL,Timestamp,JSON, ... - user-defined:
type,interface,union,enum,scalar(see their pages)
The ! sigil
T!— non-nullTT(no bang) — nullableT- assignability:
T!satisfiesT, butTdoes not satisfyT!(nullcan't init aT!slot) - grammar:
!is a postfix wrapper applied to a (possibly list) type —NonNull <- inner:Type BangToken. So[String!]!parses asNonNull(List(NonNull(String)))= non-null list of non-null strings - object/
typeliterals are always non-null in Dang (no nullable-object form) [T]andInt!are unrelated — you can't assignInt!to[Int]
Lists, nullability matrix
| written | meaning |
|---|---|
[T] |
nullable list of nullable T |
[T]! |
non-null list of nullable T |
[T!] |
nullable list of non-null T |
[T!]! |
non-null list of non-null T |
Null propagation
nullable.fieldis nullable even iffield: T!- chains short-circuit:
a.b.cis null if any link is null - recovers via
??(see Operators) or flow narrowing (see Flow-sensitive typing)
Type variables
- single lowercase letters:
a,b - used in generic function signatures
- inferred at call sites
Type hints / casts: ::
expr :: Type!annotates an expression's type (TypeHintnode)- grammar: binds only a bare
Termon the left, so wrap compound exprs in parens —(a + b) :: T!(see Operators for precedence) ::is the explicit materialization/coercion boundary:String→ custom scalar (URL!,Timestamp!, …), enum ("PASSED" :: Status!), andIDcoercions go here- coercion source is limited: only
Stringvalues coerce to custom scalars/enums.42 :: URL!is rejected statically ("type hint mismatch: Int!, but hint expects URL!") - enum casts are checked at runtime:
"NOPE" :: Status!→ "invalid enum value" - nullable → non-null casts do not strip the wrapper statically; they defer to a runtime
Coercethat rejects null:fromJSON("null") :: String!→ "null is not allowed for String!"
Meta: :: deserves a worked example showing the difference between a type hint (narrowing/disambiguation, e.g. [] :: [String!]! binding a type variable) and a type cast (coercion, e.g. myUrl :: URL!). The runtime-assertion behavior for non-null casts is a footgun worth a short callout.
Coercion rules
- assignment / arguments: pure subtyping (
hm.Assignable), no scalar coercion — a non-literalString!won't pass whereURL!is expected:fetchURL(url: myUrl)errors "cannot use String! as Test.URL!" - exception: literal expressions (string/template literals, list literals of them) auto-coerce to compatible scalars at value-handoff boundaries (call args, typed slots, returns) —
fetchURL(url: "https://…")is fine, andfetchURL(url: `https://${host}`)works too ::casts: explicitString/enum/IDcoercion permitted (see above)- list merges are pure —
String!does not becomeID!element-wise - ongoing work — see
soundness.mdfor the model being moved toward