typeshare

repository·main·Indexed 25 days ago

https://github.com/1password/typeshare

A tool that converts Rust types into equivalent forms in Swift, Go, Python, Kotlin, Scala, and TypeScript to facilitate safe FFI communication. It uses the #[typeshare] attribute to mark types for generation and leverages serde for serialization and deserialization to keep cross-language codebases in sync.

Tokens
11.7K
Snippets
30
Records
79
Agent score
84%

What's inside typeshare

  1. Overview of Typeshare

    main
    Typeshare is a tool designed to generate type definitions in various programming languages based on type definitions defined in Rust. It is primarily used for Foreign Function Interface (FFI) scenarios where types are passed as fully serialized blobs and subsequently decoded in the target language.
  2. Add and update snapshot tests in typeshare-core

    main

    Typeshare uses snapshot testing. To declare a new test, use the macro defined in tests/snapshot_tests.rs.

    To initialize a new test and generate the necessary directory structure and starter files in data/tests, run:

    env UPDATE_EXPECT=1 cargo test -p typeshare-core

    After running this, place your Rust source input into the input.rs file within the newly created test folder. To update the expected output files with Typeshare's current generation results, run the same command again:

    env UPDATE_EXPECT=1 cargo test -p typeshare-core

    If you need to update expectations for a specific test only, use the following command (replacing the last argument with your test name):

    env UPDATE_EXPECT=1 cargo test -p typeshare-core --test snapshot_tests -- <test_name>
  3. Generate FFI definitions using the Typeshare CLI

    main

    Use the typeshare CLI tool to generate type definitions for specific target languages based on annotated Rust code.

    To generate definitions, you must provide:

    1. The directory containing your Rust code (the CLI will recursively search this directory tree for annotated types).
    2. The --lang option to specify the target language.
    3. The --output-file option to specify the destination file for the generated definitions.

    Supported languages include:

    • Kotlin
    • Typescript
    • Swift
    • Scala
    • Go
    typeshare ./my_rust_project --lang=kotlin --output-file=my_kotlin_definitions.kt
    typeshare ./my_rust_project --lang=swift --output-file=my_swift_definitions.swift
    typeshare ./my_rust_project --lang=typescript --output-file=my_typescript_definitions.ts
    typeshare ./my_rust_project --lang=scala --output-file=my_scala_definitions.scala
  4. Use `target_os` conditional compilation in Rust

    main

    You can use standard Rust #[cfg] attributes to control which types are included in the generated output. Typeshare evaluates these attributes against the values provided in the --target-os CLI flag.

    Supported Patterns

    • Simple standalone: #[cfg(target_os = "android")] will only be generated if android is in the --target-os list.
    • Simple not rule: #[cfg(not(target_os = "android"))] will only be generated if android is not in the --target-os list.
    • Multiple not any rule: #[cfg(not(any(target_os = "android", target_os = "ios")))] will only be generated if neither android nor ios are in the --target-os list.

    Note: Typeshare only considers target_os attributes. It does not evaluate other cfg attributes like feature. If a cfg attribute combines target_os with another attribute (e.g., #[cfg(any(target_os = "android", feature = "android-test"))]), the type will be generated if the target_os condition is met, regardless of the other attribute's state.

  5. Annotate Rust types with #[typeshare]

    main

    To include a struct or enum in the code generation process, annotate it with the #[typeshare] attribute. Typeshare leverages serde for serialization and deserialization logic, so you can use standard serde attributes (like #[serde(tag = ...)]) to control how enums are represented in the target language.

    // Rust type definitions
    
    #[typeshare]
    struct MyStruct {
        my_name: String,
        my_age: u32,
    }
    
    #[typeshare]
    #[serde(tag = "type", content = "content")]
    enum MyEnum {
        MyVariant(bool),
        MyOtherVariant,
        MyNumber(u32),
    }
  6. Install Typeshare CLI and dependency

    main

    To use Typeshare, you must install the CLI tool via cargo and add the typeshare crate to your Rust project's dependencies.

    Note that while the package name is typeshare-cli, the command used in the terminal is typeshare.

  7. Ensure well-formatted output in Typeshare

    main
    Typeshare generates "best-effort" code output and then runs it through language-specific formatting tools to ensure the generated code meets language standards. To receive beautifully formatted output, you must have the appropriate formatting tools for each target language installed and available in your environment when running Typeshare. If these tools are missing, Typeshare will fall back to its default, less-formatted output.
  8. Generate code from Rust files with typeshare

    main

    Use the typeshare command to generate code in various target languages from a specific Rust file. Use the --lang flag to specify the target language. Some languages like kotlin and scala also support a package name flag.

    typeshare --lang=typescript some/file.rs
    typeshare --lang=swift some/file.rs
    typeshare --lang=kotlin --java-package=com.some.package.name some/file.rs
    typeshare --lang=scala --scala-package=com.some.package.name some/file.rs