Jim Tcl in Rust
This is the documentation site for the rust-jim project, which supplies two things:
- The
jimtclcrate, and its underlyingjimtcl-sys, to enable Jim Tcl to be embedded in Rust programs. - Guardian Tcl, an extended version of Jim Tcl with additional commands leveraging the rich Rust ecosystem.
The rust-jim repository on Codeberg has the source code and issue tracker. Pull requests welcome!
Embedding Jim Tcl
The jimtcl crate is the primary entry point to embed stock Jim Tcl into Rust.
It uses the separate jimtcl-sys crate that provides the Jim Tcl source code
and low-level FFI bindings.
The API reference is the primary documentation for using this crate.
Jim is compiled and statically linked into the executable. Linking against a system-provided Jim is TBD.
Note
Currently, Jim Tcl only supports Unix-like systems. Windows suppport is planned but not yet implemented.
Crate Features
The following features control Rust integration, and are enabled by default:
cli— add a dependency onClapand provide a reusable command line for Jim Tcl shells. Enables therust-jimshbinary as well.serde— add aDeserializeSeedimplementation to allow Jim objects to be directly deserialized with Serde format libraries. Serialization is not yet supported, as it would require some kind of schema mechanism.
Extension Features
Each of the Jim Tcl optional extensions, except for database extensions, is
exposed through a Rust crate feature, and passed through to a correpsonding
feature on jimtcl-sys. They are all enabled by default.
- aio
- array
- binary
- clock
- ensemble
- eventloop
- exec
- file
- glob
- history
- interp
- json (enables both
jsonandjsonencodeextensions) - namespace
- oo
- pack
- package
- posix
- readdir
- regexp
- signal
- tclcompat
- tree
Introducing Guartcl
Guardian is Jimmy Olsen with superhero gear.
Guartcl is Jim Tcl with extra bits.
Guardian Tcl is provided by the guartcl crate, and supplies a guarsh binary
to run Guardian Tcl scripts.
Guardian uses Jim with all default extensions enabled, except for SSL. For full details on the Jim Tcl commands, see Jim Tcl manual. These pages only document Guardian’s extensions to Jim Tcl.
Installing
Guardian Tcl release binaries can be downloaded from Codeberg. The
easiest way to install them is with cargo-binstall:
$ cargo binstall guartcl
You can also install with UBI:
$ ubi --forge=forgejo --project mdekstrand/rust-jim -r guartcl -e guarsh
You can also install Guardian with Mise. Add the following to your
mise.toml or mise/config.toml:
[tools."forgejo:mdekstrand/rust-jim"]
version = "latest"
matching = "guartcl"
Commands
Guardian Tcl currenly adds one ensemble command, parse. This command
exposes subcommands to parse data from a variety of formats directly into nested
Tcl data structures.
parse
The parse command has the following usage:
parse <format> <data>
parse <format> -file <path>
<format>can be any ofjson,toml, oryaml.<data>is a Tcl string containing the raw data to deserialize.<path>is the path to a file containing data in the specified format.
For example, to deserialize the contents of the $conf_src variable as JSON,
you can write:
set config [parse json $conf_src]
These commands are wrappers around the corresponding Serde deserializers. You
can also parse by parsing to typedata and using td native to
obtain a native Tcl structure.
uuid
The uuid ensemble can generate and inspect UUIDs (backed by the uuid crate).
UUIDs are represented as hexadecimal strings without braces.
- uuid v3 namespace name
- Generate a version 3 (MD5) UUID with the specified namespace and name. namespace can be either a UUID, or one of the strings
oid,url,dns, orx500. - uuid v4
- Generate a version 4 (random) UUID.
- uuid v5 namespace name
- Generate a version 5 (SHA1) UUID. Parameters are identical to
uuid v3. - uuid v7
- Generate a version 7 (timestamp + random, sortable) UUID.
- uuid version uuid
- Query the version number of the specified UUID.
- uuid timestamp uuid
- Query the timestamp of the specified UUID (v1, v6, or v7).
- uuid random
- Generate a random UUID (currently alias for
uuid v4). - uuid bytes
- Obtain the binary representation of the UUID as a 16-byte binary string.
Binary Extensions
Guardian Tcl extends the binary ensemble with binary encode and binary decode commands. These function much like the standard Tcl binary encode/decode
commands1, except they do not support any options.
- binary encode encoding data
- Encode data with encoding.
- binary decode encoding data
- Decode data with encoding.
It also provides a command to generate random bytes:
- binary random n
- Generate n random bytes as a binary string. This uses the system’s (secure) random number generator via getrandom.
Supported Encoding
The following encodings are supported:
- base64 (using data-encoding)
- base32 (using data-encoding)
- hex (using data-encoding)
- base58 (using bs58)
Type Data (td)
Note
These commands are only available in Guardian Tcl 0.5 or later, which is currently in prerelease.
Guardian Tcl provides a “typed data” structure, accessed through the td
ensemble command, that represents JSON-like data along with type information,
instead of Tcl’s usual string-based representation. It is similar in that
respect to Huddle, but uses JSON as its string representation and checks
types in Rust. You can think of it as a native-code Huddle, however. Any valid
JSON string is a valid TypedData object.
Note
JSON is the string representation as of 0.5, but this may change in the future if we find a need for data types not supported by JSON.
Creating Typed Data
The following commands create a typed data value:
- td null
- Create a null value.
- td boolean val
- Create a boolean value. val is any valid Tcl boolean.
- td integer n
- Create an integer value with value n.
- td float x
- Create a floating-point value with value x.
- td string str
- Create a string value with value str.
- td list value…
- Create a list value with the value(s) provided as arguments. The list elements themselves must already be typed data.
- td record key value …
- Create a record (dictionary) value with the key-value pairs provided as arguments (works like
dict create). The values themselves must already be typed data.
Conversion
- td native td
- Convert the typed data td to Tcl-native layout (values, lists, and dicts).
- td coalesce td …
- Return the first non-null argument.
Conversion to native is not directly reversible, because Tcl-native format loses the type information from the original.
Since typed data is stored as JSON, use the Jim Tcl json::encode command to convert Tcl-native data to JSON.
Querying Typed Data
These commands all query typed data, and require the typed data itself as input.
- td get ?-native? td key…
- Traverses the typed data td by the specified keys path to query an record. The first key is looked up in td, the second in the first key’s value, and so forth. Keys can be strings for records, or valid indices for lists. The full Tcl list index syntax (e.g.
end,end-1) is supported. - td type td
- Get the type of the typed data value.
- td length td
- Get the length of the typed data value if it is a record, list, or string. Fails for other types of data.
Manipulating Typed Data
Unlike the other typed-data functions, these commands take a tdvar parameter specifying the name of the variable storing the typed data to manipulate.
Important
Typed data values are effectively immutable, and manipulating typed data updates the variable to store a new typed data. In particular, using these functions on the result of a
td getcommand will not modify the original data structure thattd getqueried.
- td set tdvar key… value
- Update the location in the record specified by the key path to store value. If a key does not exist in a record value, it will be added (this does not work for lists).
- td lappend tdvar key… ?–? value…
- Append the specified values to the list addressed by the key path. Fails if the key path does not specify a list. Separating the key path from the values with
--is required if you want to add more than one value to the list.
Data Format Interchange
Parsing
- td parse json ?-file? input
- Parse the specified JSON input as typed data. Since typed data is represented as JSON, this just validates the input. The
-fileoption instructs it to treat input as a filename instead of JSON text. - td parse yaml ?-file? input
- Just like
td parse json, except it parses YAML. - td parse toml ?-file? input
- Just like
td parse json, except it parses TOML.
Dumping
Typed data can also be dumped to several formats.
- td dump json ?-pretty? ?-file outfile? td
- Save the typed data as JSON, optionally to a file, optionally with pretty-printing. The file is saved with a terminating newline in either case.
- td dump toml ?-pretty? ?-file outfile? td
- Save the typed data as TOML, optionally to a file, optionally with pretty-printing.
- td dump yaml ?-file outfile? td
- Save the typed data as TOML, optionally to a file.
Date-Time Support
Since 0.7, Guardian Tcl includes an experimental dt command providing a modern
date-time manipulation API that is aware of calendars, time zones, and similar
complexifiers. It is a powered by (and designed around) the Rust Jiff
library, which in turn is heavily inspired by JavaScript Temporal.
Warning
The Guardian
dtcommand is highly experimental and may change significantly as I figure out what an ergonomic, modern Tcl date/time API looks like.
Date Object Types
The dt commands operate on multiple types of date/time objects (referred to below as dto). The
supported types map to different Jiff objects, and are as follows:
- zoned
- An instant in time with an associated time zone. This is the most complete date/time type, and corresponds to
jiff::Zoned. The time zone may be a fixed offset, or it may be a transition-aware time zone such asAmerica/New_York. - datetime
- A date and time without an associated time zone. It is ambiguous whether such times are local or UTC, and interpretation is determined by context and/or command options.
- date
- A date without a time. Represented in ISO format (e.g.,
2026-10-09). - time
- A time without a date. Represented as
HH:MM:SS[.SS…] - timestamp
- An instant in time, represented as seconds since the epoch UTC or an ISO Zulu date-time. Integer and floating-point numbers are interpreted as timestamps when passed to
dtcommands.
The dt commands interpret strings as the most precise object they can
successfully parse into. When serialized as strings, they are normalized to the
corresponding ISO format as implemented by Jiff.
Note
There is not currently any mechanism to work with local timestamps (seconds since the epoch local time).
Current Time
- dt now
- Obtain the current time as a
zoned.
Query / Extraction
- dt type dto
- Query the type of date object stored in dto.
- dt date dto
- Extract the date part of dto.
- dt time dto
- Extract the time part of dto.
- dt timestamp dto
- Convert dto to a Unix timestamp (seconds since the epoch).
- dt year dto
- Extract the year from dto.
- dt month dto
- Extract the month from dto.
- dt day dto
- Extract the day from dto.
- dt week dto
- Extract the ISO week-date week number from dto. ISO weeks start on Monday. See [Weeks][#weeks] for further discussion.
- dt weekdate dto
- Convert the date in dto to an ISO week date (e.g.,
2026-03-29becomes2026-W13-7). See Weeks for further discussion. - dt weekday ?-named? dto
- Extract the weekday of dto. This is an ISO weekday number (see Weeks), or a lowercase weekday name if
-namedis passed. - dt is complete dto
- Query whether dto is a complete specification of an exact point in time. Timestamps and zoned date-times are complete;, dates, times, and unzoned date-time objects are not complete.
- dt is span obj
- Query whether obj is a span.
- dt same month dto1 dto2
- Query whether dto1 and dto2 occur in the same month (and year).
- dt same week dto1 dto2
- Query whether dto1 and dto2 occur in same year and ISO (Monday-based) week.
Display and Formatting
- dt format fmt dto
- Format dto according to fmt. The format specifier fmt uses the syntax of
jiff::fmt::strtime, which is mostly compatible with Unixstrftime. Truly locale-aware formatting is not yet supported. - dt rfc2822 ?-local? dto
- Display the date/time dto in RFC 2822 (HTTP / e-mail header) format. With ?-local?, converts to local time first. To display the current local time in RFC 2822, you can write
dt rfc2822 -local now. - dt friendly dto
- Display a “friendly” version of dto (or a span), intended for human consumption. This format has no stability guarantees, although the friendly version of a span is the same as Jiff’s, and will round-trip for commands like
dt add.
Parsing / Normalization
- dt parse ?flags? str
-
Parse a date-time, ensuring it is valid and possibly converting it. The supported flags are:
- -local
- Parse into local time, yielding a
zoned. Zoned and timestamp inputs are translated to the system’s local time, and unzoned date-time inputs are interpreted as local time and turned intozoned. Bare dates and times are unchanged. - -utc
- Like
-local, but interprets or translates to UTC instead of the system’s local time. - -span
- Parse a span (such as
1moor2h50m) instead of a date/time. This validates that the object is properly interpretable as a span by Jiff, and pre-parses it into ajiff::Spanfor subsequent use by other commands likedt add. - -format fmt
- Parse a date according to fmt, which is a
jiff::fmt::strtimeformat string.
Manipulation
- dt add dto span
-
Add the specified span to dto. The base date can be any supported date/time object, although not all date and span combinations are possible (e.g., adding a month to a time will fail).
- dt adjust dto how args
-
Adjust the specified dto to a different day. how specifies the kind adjustment, and args provides additional information. The following combinations of how and args are supported:
- next weekday, prev weekday
- Change to the next or previous weekday (
mon,tue, etc.) - [+-]N weekday
- Advance (or step back) by N weekdays, e.g.
+2 mondays - first weekday, last weekday
- Adjust to the first or last weekday of the date’s month.
second,third, etc. (up tofifth) are also accepted. - to zone zone
- Adjust to time zone zone. If the dto has a time zone, it is adjusted to be in the specified zone. If it does not, then it is interpreted in the specified zone without adjustment.
- to local, to utc
- Adjust to local or UTC time zone. Unzoned dates are interpreted to be in the taret zone.
Examples:
# adjust to 2026-10-12 dt adjust 2026-10-10 next monday # find US Thanksgiving - Nov. 26 dt adjust 2026-11-01 fourth thursday - dt subtract dto rhs
-
Subtract rhs (either a span or a DTO) from a DTO, yielding a DTO or a span.
String Representations
The following string representations are supported for calendar date/time objects:
- ISO timestamp (
YYYY-MM-DDTHH:MM:SS.SSSZ), parsed as atimestamp. Seconds and fractional seconds are optional. - Unix timestamps (either integer or float), parsed as a
timestamp. - ISO dates (
YYYY-MM-DD), parsed as adate. - ISO week dates (
YYYY-Wnn-D), parsed as adate. - Times (
HH:MM:SS.SSS), parsed as atime. Seconds and fractional seconds are optional. - ISO date-time (
YYYY-MM-DDTHH:MM[:SS[.SSS]]), parsed as an unzoneddatetime. A space may be used instead ofT. Seconds and fractional seconds are optional. - ISO date-time with offset (
YYYY-MM-DDTHH:MM:SS.SSS+OO:oo), parsed as azonedwith a fixed-offset time zone. A space may be used instead ofT. Seconds and fractional seconds are optional. - ISO date-time with zone (
YYYY-MM-DDTHH:MM:SS.SSS[Zone]), parsed as azoned.Zonecan be any time zone name or abbreviation supported by the system. A space may be used instead ofT. Seconds and fractional seconds are optional.
Spans are represented in ISO duration format, e.g. P1M or P2DT3H. Note that the duration elements are interpreted in calendar-space: P1D and P24H are not the same when added to a date/time with a DST adjustment or a leap second, because the former moves a zoned date-time by one day regardless of how many hours are in the day, and the latter moves it by 24 hours.
Human-readable duration notation, such as 2h 30m, is also accepted as input where a span is required.
Weeks
For consistency, the dt command suite operates on ISO weeks
consistently for week-related operations. Weeks start on Monday, and weekdays
are numbered from 1, so Sunday is weekday 7 (although 0 may be accepted in some
places).
ISO weeks are numbered from the first week of the year, starting with 1, and a week’s year is defined as the Gregorian year in which its Thursday occurs. A consequence of this is that some dates at the beginning or end of a normal Gregorian calendar year may appear in different ISO years. For example:
- If Jan. 1 is on a Friday, then it appears in the previous year in ISO week-date format. Friday, Jan. 1, 2027 becomes
2026-W53-5. - If Jan. 1 is on a Wednesday, then late December will appear in the next year. Wednesday, Dec. 31, 2025 becomes
2026-W01-3.
The week-related commands (dt week, dt weekdate, dt weekday, and dt same week) all operate on ISO weeks. Gregorian dates are converted to ISO weeks
when needed. dt same week therefore checks whether two dates are in the same
ISO week (and year), which is usually what you want for answering “do these two
dates appear in the same week?”, unless you need weeks to begin on Sunday. I may
consider future extensions to provide options for Sunday-based weeks, but this
was the easy way to get code with good probability of correctness based on
Jiff’s capabilities.
HTTP Client
The Guardian http command provides an HTTP(S) client to Tcl scripts.
It is currently based on ureq, but this is not guaranteed.
Important
This command is currently in early preview, it may change in upcoming releases. It is not enabled by default, but must be explicitly enabled with the
httpfeature.
Note
The
httpcommand currently requires the URL to be untainted, and taints the response data and response headers. Query parameters and request headers are not currently required to be untainted, but this is not guaranteed and feedback is welcome on what should be checked for tainted data.
Tip
The
platform-sslRust feature, enabled by default, uses the host platform’s SSL certificates and validation logic HTTPS certificates. If this feature is disabled, a built-in certificate bundle is used.
HTTP GET
The http get command exposes GET requests:
http get ?-query dict? ?-headers dict? ?-headers-out var? ?-out-file file? ?-binary? url
- -query dict
-
Add query parameters to the request from the dictionary dict.
- -headers dict
-
Add HTTP request headers from the dictionary dict.
- -headers-out var
-
Save response headers to a dictionary in variable var.
- -out-file file
-
Save output to file instead of returning it. Saves the data directly, ignoring
-binary. - -binary
-
Return binary output instead of decoding to string.
Note
This just uses
read_to_vec()instead ofread_to_string(). When it is not supplied, it is not clear ifureqdecodes according to the content-encoding, or just treats data as UTF-8.
Compiling Binaries
Guardian Tcl supports building stand-alone executables containing both the Tcl
interpreter and a Tcl script. This functionality is exposed through the
guardian::compile Tcl command. It is intended to function somewhat like
Tclkits for OG Tcl, but is more limited and based on Jim Tcl with the Guardian
extensions.
- guardian::compile ?-usage? ?-out exe? script ?packages?
- Compile script (the path to a Tcl file, not a Tcl script string) into a standalone executable. If
-outis not specified, the output name is derived from the script name.-usageenables CLI parsing based on Usage specifications embedded in the script. Additional Tcl source files named as packages are included in the compiled executable and can be accessed withpackage require.