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
, andfloat
map to native Rust types.Temporal scalars:
date
anddatetime
map tochrono
’sNaiveDate
andNaiveDateTime
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 toString
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, andpattern
).Schema metadata exports (
linkml_meta
hash maps and module-level constants such asid
andversion
) .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_class(cls: ClassDefinition) ClassResult [source]¶
Generate a class as a struct!
- 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.
- output: Path | None = None¶
If
mode == "crate"
, a directory to contain the generated crateIf
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()
- 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
- 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 infile
mode, directory otherwise.force (bool) – If the output already exists, overwrite it. Otherwise raise a
FileExistsError
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.