Rust

Example Output

The structs: lib.rs

The traits: poly.rs

Overview

Warning

The rust generator is still currently under development. Notable missing features are ifabsent processing and the enforcement of rules and constraints.

The Rust Generator produces a Rust crate with structs and enums from a LinkML model, with optional pyo3 and serde support. It additionally generates a trait for every struct to provide polymorphic access, in the poly.rs file.

For all classes having subclasses, an extra enum is generated to represent an object of the class or a subtype. All the enums implement the trait, so they can be (optionally) directly used without match statement.

Feature Compliance

The current implementation status is summarised below. These notes mirror the ongoing work tracked in linkml/linkml#2360.

Supported

  • Core schema constructs: slots, classes, enums, and type aliases are emitted as Rust structs, enums, and aliases.

  • Basic metamodel features: multivalued slots, required vs. optional cardinalities, inheritance (is_a), union slots (any_of), inline list/dict slots, and slot aliases.

  • Build targets: both single-file output and full Cargo crates (with Cargo.toml).

  • Fundamental scalar types: string, integer, bool, and float map to native Rust types.

  • Temporal scalars: date and datetime map to chrono’s NaiveDate and NaiveDateTime respectively.

  • Traits for polymorphic access to class hierarchies, along with enums for class-or-subtype containers.

  • PyO3 bindings for the generated structs (behind a Cargo feature flag).

  • Basic serde deserialization (behind a Cargo feature flag).

Partially Supported

  • Many scalar types (e.g. time, URI-related types) currently fall back to String representations.

  • serde deserialization works; serialization still lacks the normalisation, and will not always produce correct yaml.

  • Testing covers unit-level behaviour with a dedicated Rust CI workflow; dynamic compilation and compliance suites are still pending.

Not Yet Supported

  • Default handling (ifabsent) and broader constraint enforcement (values_from, value_presence, equality and cardinality checks, numeric bounds, and pattern).

  • Schema metadata exports (linkml_meta hash maps and module-level constants such as id and version) .

  • Serde data normalisation for serialization

  • Compliance test integration

  • Rule/expression support

  • Dynamic enumerations

Example

Given a definition of a Person class:

Event:
  slots:
    - started_at_time
    - ended_at_time
    - duration
    - is_current

EmploymentEvent:
  is_a: Event
  slots:
    - employed_at

MedicalEvent:
  is_a: Event
  slots:
    - in_location
    - diagnosis
    - procedure

The generate rust looks like this (serde and pyo3 annotations omitted for brevity):

pub struct Event {
    pub started_at_time: Option<NaiveDate>,
    pub ended_at_time: Option<NaiveDate>,
    pub duration: Option<f64>,
    pub is_current: Option<bool>
}

pub struct EmploymentEvent {
    pub employed_at: Option<String>,
    pub started_at_time: Option<NaiveDate>,
    pub ended_at_time: Option<NaiveDate>,
    pub duration: Option<f64>,
    pub is_current: Option<bool>
}

pub struct MedicalEvent {
    pub in_location: Option<String>,
    pub diagnosis: Option<DiagnosisConcept>,
    pub procedure: Option<ProcedureConcept>,
    pub started_at_time: Option<NaiveDate>,
    pub ended_at_time: Option<NaiveDate>,
    pub duration: Option<f64>,
    pub is_current: Option<bool>
}

pub enum EventOrSubtype {
    Event(Event),
    EmploymentEvent(EmploymentEvent),
    MedicalEvent(MedicalEvent)
}

polymorphic traits are implemented:

pub trait Event {
    fn started_at_time<'a>(&'a self) -> Option<&'a NaiveDate>;
    fn ended_at_time<'a>(&'a self) -> Option<&'a NaiveDate>;
    fn duration<'a>(&'a self) -> Option<&'a f64>;
    fn is_current<'a>(&'a self) -> Option<&'a bool>;
}

pub trait MedicalEvent: Event {
    fn in_location<'a>(&'a self) -> Option<&'a str>;
    fn diagnosis<'a>(&'a self) -> Option<&'a crate::DiagnosisConcept>;
    fn procedure<'a>(&'a self) -> Option<&'a crate::ProcedureConcept>;
}

impl Event for crate::MedicalEvent {
        fn started_at_time(&self) -> Option<&NaiveDate> {
        self.started_at_time.as_ref()
    }
        fn ended_at_time(&self) -> Option<&NaiveDate> {
        self.ended_at_time.as_ref()
    }
        fn duration(&self) -> Option<&f64> {
        self.duration.as_ref()
    }
        fn is_current(&self) -> Option<&bool> {
        self.is_current.as_ref()
    }
}

...

impl Event for crate::EventOrSubtype {
        fn started_at_time(&self) -> Option<&NaiveDate> {
        match self {
                EventOrSubtype::Event(val) => val.started_at_time(),
                EventOrSubtype::EmploymentEvent(val) => val.started_at_time(),
                EventOrSubtype::MedicalEvent(val) => val.started_at_time(),

        }
    }
        fn ended_at_time(&self) -> Option<&NaiveDate> {
        match self {
                EventOrSubtype::Event(val) => val.ended_at_time(),
                EventOrSubtype::EmploymentEvent(val) => val.ended_at_time(),
                EventOrSubtype::MedicalEvent(val) => val.ended_at_time(),

        }
    }
        fn duration(&self) -> Option<&f64> {
        match self {
                EventOrSubtype::Event(val) => val.duration(),
                EventOrSubtype::EmploymentEvent(val) => val.duration(),
                EventOrSubtype::MedicalEvent(val) => val.duration(),

        }
    }
        fn is_current(&self) -> Option<&bool> {
        match self {
                EventOrSubtype::Event(val) => val.is_current(),
                EventOrSubtype::EmploymentEvent(val) => val.is_current(),
                EventOrSubtype::MedicalEvent(val) => val.is_current(),

        }
    }
}

Command Line

gen-rust

gen-rust [OPTIONS] YAMLFILE

Options

-V, --version

Show the version and exit.

-o, --output <output>

Output directory (crate mode) or .rs file (file mode)

-n, --crate-name <crate_name>

Name of the generated crate/module

-s, --serde

Add ‘serde’ to Cargo.toml default features. Source always includes #[cfg(feature=”serde”)] derives/attrs; this flag only enables the crate feature by default.

-p, --pyo3

Add ‘pyo3’ to Cargo.toml default features and emit Python module glue (cdylib + #[pymodule]). Source always includes #[cfg(feature=”pyo3”)] gates; this flag only enables the crate feature by default.

-f, --force

Overwrite output if it already exists

-m, --mode <mode>

Generation mode: ‘crate’ (Cargo package) or ‘file’ (single .rs)

Options:

crate | file

-f, --format <format>

Output format

Default:

'rust'

Options:

rust

--metadata, --no-metadata

Include metadata in output

Default:

True

--useuris, --metauris

Use class and slot URIs over model uris

Default:

True

-im, --importmap <importmap>

Import mapping file

--log_level <log_level>

Logging level

Default:

'WARNING'

Options:

CRITICAL | ERROR | WARNING | INFO | DEBUG

-v, --verbose

Verbosity. Takes precedence over –log_level.

--mergeimports, --no-mergeimports

Merge imports into source file (default=mergeimports)

--stacktrace, --no-stacktrace

Print a stack trace when an error occurs

Default:

False

Arguments

YAMLFILE

Required argument

Generator

class linkml.generators.rustgen.RustGenerator(schema: str | ~typing.TextIO | ~linkml_runtime.linkml_model.meta.SchemaDefinition | ~linkml.utils.generator.Generator | ~pathlib.Path, schemaview: ~linkml_runtime.utils.schemaview.SchemaView | None = None, format: str | None = None, metadata: bool = True, useuris: bool | None = None, log_level: int | None = 30, mergeimports: bool | None = True, source_file_date: str | None = None, source_file_size: int | None = None, logger: ~logging.Logger | None = None, verbose: bool | None = None, output: ~pathlib.Path | None = None, namespaces: ~linkml_runtime.utils.namespaces.Namespaces | None = None, directory_output: bool = False, base_dir: str = None, metamodel_name_map: dict[str, str] = None, importmap: str | ~collections.abc.Mapping[str, str] | None = None, emit_prefixes: set[str] = <factory>, metamodel: ~linkml.utils.schemaloader.SchemaLoader = None, stacktrace: bool = False, include: str | ~pathlib.Path | ~linkml_runtime.linkml_model.meta.SchemaDefinition | None = None, crate_name: str | None = None, pyo3: bool = True, pyo3_version: str = '>=0.21.1', serde: bool = True, mode: ~typing.Literal['crate', 'file'] = 'crate', _environment: ~jinja2.environment.Environment | None = None)[source]

Generate rust types from a linkml schema

generate_attribute(attr: SlotDefinition, cls: ClassDefinition) AttributeResult[source]

Generate an attribute as a struct property

generate_cargo(imports: Imports) RustCargo[source]

Generate a Cargo.toml file

generate_class(cls: ClassDefinition) ClassResult[source]

Generate a class as a struct!

generate_pyproject() RustPyProject[source]

Generate a pyproject.toml file for a pyo3 rust crate

generate_slot(slot: SlotDefinition) SlotResult[source]

Generate a slot as a struct field

generatorname: ClassVar[str] = 'rustgenerator'

Name of the generator. Override with os.path.basename(__file__)

generatorversion: ClassVar[str] = '0.0.2'

Version of the generator. Consider deprecating and instead use overall linkml version

get_rust_range_info_across_descendants(cls: ClassDefinition, s: SlotDefinition) RustRange[source]

Compute a RustRange representing the union of a slot’s ranges across a class and all its descendants.

Container and optionality are taken from the base class slot.

mode: Literal['crate', 'file'] = 'crate'

Generate a cargo.toml file

output: Path | None = None
  • If mode == "crate" , a directory to contain the generated crate

  • If mode == "file" , a file with a .rs extension

If output is not provided at object instantiation, it must be provided on a call to serialize()

pyo3: bool = True

Generate pyO3 bindings for the rust defs

render(mode: Literal['file'] = 'file') FileResult[source]
render(mode: Literal['crate'] = 'crate') CrateResult

Render the template model of a rust file before serializing

Parameters:

mode (RUST_MODES, optional) – Override the instance-level generation mode

serde: bool = True

Generate serde derive serialization/deserialization attributes

serialize(output: Path | None = None, mode: Literal['crate', 'file'] | None = None, force: bool = False) str[source]

Serialize a schema to a rust crate or file.

Parameters:
  • output (Path, optional) – A .rs file if in file mode, directory otherwise.

  • force (bool) – If the output already exists, overwrite it. Otherwise raise a FileExistsError

valid_formats: ClassVar[list[str]] = ['rust']

Allowed formats - first format is default

Features

  • Serde: Code that depends on Serde is behind the Cargo feature serde (#[cfg(feature = "serde")]).

  • PyO3: Python bindings are behind the Cargo feature pyo3 (#[cfg(feature = "pyo3")]).

  • Enable features when building your crate (e.g., --features serde,pyo3) to include the corresponding code paths.

Single-File Mode

  • When generating a single .rs file (--mode file): - serde_utils is inlined into the file (no separate module file). - Polymorphic traits/containers (poly.rs/poly_containers.rs) are not emitted — they are crate-mode only.