# How KCL Schemas Provide Advanced Type System Capabilities Beyond Traditional Approaches

> Discover how KCL schemas, acting as type system primitives, offer advanced capabilities like inheritance, structural typing, and compile-time validation for robust configuration.

- Repository: [The KCL Programming Language/kcl](https://github.com/kcl-lang/kcl)
- Tags: deep-dive
- Published: 2026-03-05

---

**KCL schemas function as first-class type system primitives that combine object-oriented inheritance, structural typing via protocols, mixins, index signatures, and compile-time validation to create a constraint-based configuration language.**

Unlike simple data structures or basic type definitions found in JSONSchema or YAML, the KCL language treats schemas as rich type objects capable of expressing complex configuration contracts. According to the `kcl-lang/kcl` source code, the `SchemaType` struct in [`crates/sema/src/ty/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/mod.rs) implements a sophisticated type system that bridges compile-time static analysis with runtime validation, enabling developers to define reusable, extensible configuration models with built-in validation logic.

## Named Types with Package Context

KCL schemas exist as fully qualified named types within their package namespace. The compiler generates qualified names using the `@pkg.Schema` format, allowing schemas to be referenced unambiguously across module boundaries.

In [`crates/sema/src/ty/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/mod.rs), the `SchemaType::ty_str_with_at_pkgpath_prefix` method (lines 70-77) constructs these fully qualified identifiers. This enables type-safe imports and prevents naming collisions when composing configurations from multiple libraries.

## Instance vs. Type Distinction

The type system maintains a strict distinction between a schema as a *type* and a schema as an *instance*. The same identifier can denote the type `Person` or an instantiated value `person = Person{}`, with the runtime distinguishing between them via the `is_instance` field in `SchemaType` (lines 30-47 of [`crates/sema/src/ty/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/mod.rs)).

This dual nature allows schemas to serve both as blueprints for validation and as factory functions for creating validated configuration objects.

## Single Inheritance and Multiple Mixins

KCL supports traditional single inheritance through the `base` field (`Option<Box<SchemaType>>` at lines 53-55) while simultaneously enabling multiple mixins via the `mixins: Vec<SchemaType>` field (lines 58-60). This combination provides the reusability of inheritance with the compositional flexibility of traits.

When the resolver constructs schema types in [`crates/sema/src/resolver/global.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/global.rs), the `build_schema_type` function collects inherited attributes and validates that mixin compositions do not create conflicting attribute definitions.

## Protocols and Structural Typing

Beyond nominal typing, KCL implements **structural typing** through protocols. A schema can declare a protocol—a set of required attributes—and any schema satisfying those requirements is assignable to the protocol type, regardless of explicit inheritance.

The `protocol: Option<Box<SchemaType>>` field (lines 56-57) stores these constraints. The `is_sub_schema_of` function in [`crates/sema/src/ty/unify.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/unify.rs) (lines 104-112) validates protocol conformance by checking that all required members exist in the target schema, enabling duck-typing for configuration interfaces.

## Index Signatures for Dynamic Keys

Schemas support **index signatures** similar to TypeScript, allowing arbitrary keys with uniform value types. The `index_signature: Option<Box<SchemaIndexSignature>>` field (lines 63-65) enables schemas to accept dynamic configuration keys while maintaining type safety.

During evaluation, `SchemaEvalContext::is_fit_config` in [`crates/evaluator/src/schema.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/schema.rs) validates that only declared attributes or keys matching the index signature appear in the configuration.

## Optional Attributes and Default Values

Attributes support optional markers and default expressions through the `SchemaAttr` fields `is_optional`, `has_default`, and `default`. When the resolver processes schemas in [`crates/sema/src/resolver/global.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/global.rs), it captures these properties to influence both validation logic and instance creation.

Optional attributes with defaults reduce boilerplate while ensuring that configurations remain valid even when users omit common settings.

## Decorators and Validation Logic

Both schemas and individual attributes can attach **decorators**—functions that run custom validation or transformation logic. The `decorators: Vec<Decorator>` field (lines 65-66) stores these annotations, which execute during the `schema_check` phase.

This mechanism allows cross-cutting concerns like range validation, regex matching, or custom business rules to be declared declaratively alongside type definitions.

## Rules as Validation-Only Types

KCL introduces **rules**—special schema types that contain only validation logic and cannot be instantiated. Rules share the same `SchemaType` representation but set `is_rule = true` (lines 51-53).

Rules enable reusable validation logic that can be applied across multiple schemas without creating inheritance hierarchies, supporting the "mix in validation" pattern for configuration governance.

## Runtime Type Information and Lazy Evaluation

At runtime, the `SchemaEvalContext` struct defined in [`crates/evaluator/src/schema.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/schema.rs) (lines 30-45) carries AST nodes, lazy-evaluation scopes, configuration metadata, and evaluation state flags. This context prevents repeated work during configuration generation and supports lazy evaluation of schema attributes.

The evaluator uses this runtime type information to enforce constraints that cannot be verified statically, ensuring that generated JSON/YAML output conforms to the schema definitions.

## Practical Implementation Examples

### Basic Schema with Optional Attributes

```kcl
schema Person:
  name: str
  age?: int = 30   # optional, defaults to 30

```

The resolver creates a `SchemaAttr` with `is_optional = true` and `default = Some(30)`, initialized through `SchemaEvalContext::init_lazy_scope`.

### Inheritance and Mixins

```kcl
schema Base:
  id: str

schema MixinA:
  a: int

schema MixinB:
  b: bool

schema Derived inherits Base mixins MixinA, MixinB:
  name: str

```

Here, `Derived` receives `base = Some(Base)` and `mixins = [MixinA, MixinB]`, with the type system merging attributes and validating name uniqueness.

### Protocol-Based Structural Typing

```kcl
schema PrintableProtocol:
  print(): str

schema Foo:
  name: str
  print(): str = "foo"

# Foo is assignable to PrintableProtocol because it implements `print`

```

The `protocol` field enables interface-like behavior without explicit inheritance declarations.

### Dynamic Configuration with Index Signatures

```kcl
schema Config:
  [key: str]: any   # any key maps to any value

config = Config {
  "host": "localhost",
  "port": 8080,
}

```

The `index_signature` stores `key_ty = str` and `val_ty = any`, validated at runtime by `SchemaEvalContext::is_fit_config`.

### Validation Rules

```kcl
rule PositiveNumber:
  x: int
  check x > 0

schema Product:
  price: int
  check PositiveNumber(price)

```

Rules with `is_rule = true` provide reusable validation blocks that cannot be instantiated as data.

## Summary

- **First-class type primitives**: KCL schemas in [`crates/sema/src/ty/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/mod.rs) implement `SchemaType` as a comprehensive type object supporting inheritance, mixins, and protocols.
- **Structural and nominal typing**: The system combines traditional inheritance (`base` field) with structural protocols (`is_sub_schema_of` in [`unify.rs`](https://github.com/kcl-lang/kcl/blob/main/unify.rs)) for flexible interface definitions.
- **Runtime validation**: `SchemaEvalContext` in [`crates/evaluator/src/schema.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/schema.rs) bridges compile-time types with runtime configuration validation and lazy evaluation.
- **Advanced features**: Index signatures, decorators, optional attributes with defaults, and rules provide capabilities exceeding traditional configuration schemas or simple struct definitions.

## Frequently Asked Questions

### How does KCL handle schema inheritance conflicts?

When a schema inherits from a base and mixes in multiple schemas, the resolver in [`crates/sema/src/resolver/global.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/global.rs) validates that attribute names do not clash during `build_schema_type`. If conflicting attributes exist across mixins or between the base and mixins, the compiler raises an error at resolution time, ensuring unambiguous attribute resolution.

### What is the difference between a protocol and a base schema in KCL?

A **base schema** establishes an "is-a" relationship through single inheritance (`base: Option<Box<SchemaType>>`), carrying both attributes and methods. A **protocol** (`protocol` field) defines structural requirements—any schema containing the required members is assignable to the protocol type, regardless of inheritance hierarchy, enabling duck-typing for configuration interfaces.

### Can KCL schemas validate values at runtime or only at compile time?

KCL performs both **static type checking** and **runtime validation**. The compiler validates type assignments and schema conformance using `is_sub_schema_of` in [`crates/sema/src/ty/unify.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/unify.rs). At runtime, `SchemaEvalContext` in [`crates/evaluator/src/schema.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/schema.rs) executes validation checks, evaluates default expressions, and ensures that dynamic keys conform to index signatures before generating output.

### What are schema decorators and how do they work?

**Decorators** are metadata annotations stored in `decorators: Vec<Decorator>` (lines 65-66 of [`crates/sema/src/ty/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/ty/mod.rs)) that attach to schemas or attributes. During the `schema_check` phase, these decorators execute custom validation or transformation logic, enabling cross-cutting concerns like range validation (`@range(min=0, max=120)`) without polluting the core schema definition with imperative validation code.