CLI tool
1Password/typeshare avatar
1Password/typeshare

1Password/typeshare: generating Swift, Kotlin, Scala and TypeScript types from annotated Rust

Typeshare is the ultimate tool for synchronizing your type definitions between Rust and other languages for seamless FFI.

2,988 stars132 forksRustApache-2.0

At a glance

What is it?
Typeshare is a CLI and annotation crate that reads Rust structs and enums and writes matching type definitions for other languages. It is a code generator for FFI boundaries, not a hosted service, and the README itself flags Go and Python as experimental.
Who is it for?
Adopt typeshare if your source of truth is a Rust crate that crosses an FFI boundary into Swift, Kotlin, Scala or TypeScript, and you are willing to keep the annotation attribute on every exported type. Do not adopt it if Go or Python is your only target: the README labels both experimental, and the Python limitations are tracked in issue 217.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem typeshare solves at an FFI boundary

When Rust code is called from Swift, Kotlin, Scala or TypeScript, the same data shape has to be declared twice: once as a Rust struct, once as an interface or data class on the other side. The two drift. A field gets renamed in Rust, the other language keeps the old name, and the mismatch only appears at runtime when deserialization fails. Typeshare targets that specific failure. The README frames it as taking away the burden of manually managing types that cross an FFI layer, and the mechanism is a code generator driven by serde.

The audience is narrow on purpose. You need a Rust codebase that already uses serde, an FFI or serialization boundary to at least one of the supported languages, and a build process where regenerating a file is acceptable. If your types never leave Rust, typeshare adds an attribute and a build step for nothing.

How the annotation crate and the CLI divide the work

The workspace splits into four crates: annotation, cli, core and lib, listed in the root Cargo.toml. The annotation crate supplies the procedural macro, so #[typeshare] on a struct or enum marks it for export. The cli crate is the binary. The core crate holds the parsing and generation logic, and lib is the runtime dependency you add to your own Cargo.toml.

The data flow is file-based, not runtime. You point the CLI at a directory of Rust source, it parses the annotated types, and it writes one output file per invocation. Nothing is generated at compile time of your application and nothing is generated at runtime; the output is a source file you commit or regenerate. That is why the README shows one command per target language with an explicit --output-file. It also means the generated file can go stale if nobody reruns the command, and the README does not describe any watch mode or incremental check that would catch that.

Generation follows serde semantics. The README's example annotates an enum with #[serde(tag = "type", content = "content")] and the TypeScript output becomes a discriminated union with a type field and a content field, including content: undefined for the unit variant. That is the interesting part of the design: typeshare does not invent its own tagging rules, it reads serde's.

Installing typeshare-cli and generating a first file

The README gives one install path, through Cargo. The crate is typeshare-cli, but the installed command is typeshare. That mismatch is called out in the README with a note, and it is the first thing that trips people up.

bash
cargo install typeshare-cli

Add the runtime crate to your project's dependencies so the attribute resolves. The README shows version 1.0.0 in the snippet, so check crates.io for the current release before pinning.

toml
[dependencies]
typeshare = "1.0.0"

Annotate a struct and an enum. Without the attribute, the type is not exported.

rust
#[typeshare]
struct MyStruct {
    my_name: String,
    my_age: u32,
}

Then run the generator against the directory holding your Rust code, choosing the language and the output path. The README lists kotlin, swift, scala and typescript as the values for --lang.

bash
typeshare ./my_rust_project --lang=typescript --output-file=my_typescript_definitions.ts
typeshare ./my_rust_project --lang=swift --output-file=my_swift_definitions.swift

After the run you should have a file containing an interface for MyStruct with my_name typed as string and my_age typed as number, matching the README's generated TypeScript sample. If the file is empty, the usual cause is a missing #[typeshare] attribute rather than a CLI error.

Go and Python are behind feature flags for a reason

The README's language list carries two asterisks. Go and Python support is described as experimental, and both require enabling a Cargo feature when installing typeshare-cli: the go feature or the python feature respectively. The README links to issue 217 for the Python limitation list rather than reproducing it, so the exact gaps are not documented in the README itself.

Treat that as a boundary, not a footnote. A team that generates Swift and TypeScript from the same annotated Rust gets the mature path. A team whose only consumer is a Python service is running the experimental path with an issue tracker as its specification. The README does not state which serde constructs the experimental backends handle, and it does not promise API stability for them.

The other limitation is structural. Typeshare generates definitions, not transport. It does not generate the FFI glue, the serialization call sites, or the network layer. You still need a serde-compatible encoding on both sides. If your boundary is not serde-shaped, the generator is the wrong tool regardless of language.

Compared with hand-written definitions and schema-first tools

The realistic alternative is writing the Swift, Kotlin, Scala and TypeScript definitions by hand and keeping them in sync through review. That costs nothing to set up and handles any Rust construct, including ones typeshare may not map. Its failure mode is silent drift, which is exactly what typeshare removes. The trade is that you now depend on a generator's mapping rules for every type you export.

The other alternative is schema-first tooling: define the types once in a language-neutral schema and generate both the Rust and the target-language types from it. Typeshare inverts that. Rust is the source of truth and everything else is derived. For a Rust-first codebase that is less work, because you do not maintain a separate schema file or hand-write the Rust side. For a polyglot codebase where Rust is one participant among several, a schema-first approach keeps every language a peer instead of making one of them the origin.

Licence, releases and what maintenance costs you

The repository is dual licensed under Apache-2.0 and MIT, with LICENSE-APACHE and LICENSE-MIT at the top level. Both are permissive, so the practical question is attribution and notice retention rather than copyleft obligations. This is not legal advice; check how your own distribution model handles dual-licensed dependencies.

The release history in the repository is uneven. v1.13.2 was tagged 2024-11-21, v1.13.3 on 2025-06-10, and v1.13.4 on 2025-12-11. The last push to the default branch was 2026-09-20, so the repository is not archived and work is landing, but the gaps between tagged releases are measured in months. Plan for a pinned version with a deliberate bump rather than expecting frequent releases.

Upgrade cost is mostly in the generated output. Because the CLI writes files you commit, a bump can change formatting or type mapping across every generated file at once. There is a CHANGELOG.md at the top level, so read it before bumping. The README does not document a rollback procedure for a generation change, so keep the generated files in version control and diff them on upgrade.

Editorial conclusion

Adopt typeshare if your source of truth is a Rust crate that crosses an FFI boundary into Swift, Kotlin, Scala or TypeScript, and you are willing to keep the annotation attribute on every exported type. Do not adopt it if Go or Python is your only target: the README labels both experimental, and the Python limitations are tracked in issue 217. Before committing, verify the generated output against your existing hand-written definitions for the serde attributes you actually use, especially tagged enums, since the README only shows one tagging shape.

Frequently asked questions

Is typeshare free to use?

Yes. The repository is dual licensed under Apache-2.0 and MIT, and the CLI is published on crates.io as typeshare-cli.

Does typeshare cost anything per month?

No. Typeshare is an open source CLI installed with cargo install typeshare-cli, not a subscription service, so there is no monthly fee described in the repository.

What is typeshare?

It is a tool that converts Rust types into equivalent definitions in Swift, Go, Python, Kotlin, Scala and TypeScript, using serde for the conversion rules.

Official sources

  1. 1Password/typeshare on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/1password-typeshare.svg)](https://hysenlabs.com/projects/1password-typeshare)