{"id":13774161,"url":"https://github.com/extism/rust-pdk","last_synced_at":"2025-04-04T10:06:35.928Z","repository":{"id":62445030,"uuid":"529317602","full_name":"extism/rust-pdk","owner":"extism","description":"Extism Plug-in Development Kit (PDK) for Rust","archived":false,"fork":false,"pushed_at":"2025-03-21T16:54:14.000Z","size":6835,"stargazers_count":53,"open_issues_count":4,"forks_count":9,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-28T09:06:31.482Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://docs.rs/extism-pdk","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extism.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-26T15:48:04.000Z","updated_at":"2025-03-21T16:52:00.000Z","dependencies_parsed_at":"2023-10-10T18:35:36.860Z","dependency_job_id":"2d497267-a077-42fd-babf-507df0be1bc4","html_url":"https://github.com/extism/rust-pdk","commit_stats":{"total_commits":58,"total_committers":6,"mean_commits":9.666666666666666,"dds":0.4482758620689655,"last_synced_commit":"4b48155c7688bd805fe5c4b8ac8a78ce03c43a5d"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Frust-pdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Frust-pdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Frust-pdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Frust-pdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extism","download_url":"https://codeload.github.com/extism/rust-pdk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247157108,"owners_count":20893215,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T17:01:24.235Z","updated_at":"2025-04-04T10:06:35.903Z","avatar_url":"https://github.com/extism.png","language":"Rust","readme":"# Extism Rust PDK\n\n[![crates.io](https://img.shields.io/crates/v/extism_pdk.svg)](https://crates.io/crates/extism-pdk)\n\nThis library can be used to write\n[Extism Plug-ins](https://extism.org/docs/concepts/plug-in) in Rust.\n\n## Install\n\nGenerate a `lib` project with Cargo:\n\n```bash\ncargo new --lib my-plugin\n```\n\nAdd the library from [crates.io](https://crates.io/crates/extism-pdk).\n\n```bash\ncargo add extism-pdk\n```\n\nChange your `Cargo.toml` to set the crate-type to `cdylib` (this instructs the\ncompiler to produce a dynamic library, which for our target will be a Wasm\nbinary):\n\n```toml\n[lib]\ncrate_type = [\"cdylib\"]\n```\n\n### Rustup and wasm32-unknown-unknown installation\n\nOur example below will use the `wasm32-unknown-unknown` target. If this is not\ninstalled you will need to do so before this example will build. The easiest way\nto do this is use [`rustup`](https://rustup.rs/).\n\n```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nOnce `rustup` is installed, add the `wasm32-unknown-unknown` target:\n\n```bash\nrustup target add wasm32-unknown-unknown\n```\n\n## Getting Started\n\nThe goal of writing an\n[Extism plug-in](https://extism.org/docs/concepts/plug-in) is to compile your\nRust code to a Wasm module with exported functions that the host application can\ninvoke. The first thing you should understand is creating an export. Let's write\na simple program that exports a `greet` function which will take a name as a\nstring and return a greeting string. For this, we use the `#[plugin_fn]` macro\non our exported function:\n\n```rust\nuse extism_pdk::*;\n\n#[plugin_fn]\npub fn greet(name: String) -\u003e FnResult\u003cString\u003e {\n    Ok(format!(\"Hello, {}!\", name))\n}\n```\n\nSince we don't need any system access for this, we can compile this to the\nlightweight `wasm32-unknown-unknown` target instead of using the `wasm32-wasi`\ntarget:\n\n```bash\ncargo build --target wasm32-unknown-unknown\n```\n\n\u003e **Note**: You can also put a default target in `.cargo/config.toml`:\n\n```toml\n[build]\ntarget = \"wasm32-unknown-unknown\"\n```\n\nThis will put your compiled wasm in `target/wasm32-unknown-unknown/debug`. We\ncan now test it using the [Extism CLI](https://github.com/extism/cli)'s `run`\ncommand:\n\n```bash\nextism call target/wasm32-unknown-unknown/debug/my_plugin.wasm greet --input \"Benjamin\"\n# =\u003e Hello, Benjamin!\n```\n\n\u003e **Note**: We also have a web-based, plug-in tester called the\n\u003e [Extism Playground](https://playground.extism.org/)\n\n### More About Exports\n\nAdding the\n[plugin_fn](https://docs.rs/extism-pdk/latest/extism_pdk/attr.plugin_fn.html)\nmacro to your function does a couple things. It exposes your function as an\nexport and it handles some of the lower level ABI details that allow you to\ndeclare your Wasm function as if it were a normal Rust function. Here are a few\nexamples of exports you can define.\n\n### Primitive Types\n\nA common thing you may want to do is pass some primitive Rust data back and\nforth. The\n[plugin_fn](https://docs.rs/extism-pdk/latest/extism_pdk/attr.plugin_fn.html)\nmacro can map these types for you:\n\n\u003e **Note**: The\n\u003e [plugin_fn](https://docs.rs/extism-pdk/latest/extism_pdk/attr.plugin_fn.html)\n\u003e macro uses the\n\u003e [convert crate](https://github.com/extism/extism/tree/main/convert) to\n\u003e automatically convert and pass types across the guest / host boundary.\n\n```rust\n// f32 and f64\n#[plugin_fn]\npub fn add_pi(input: f32) -\u003e FnResult\u003cf64\u003e {\n    Ok(input as f64 + 3.14f64)\n}\n\n// i32, i64, u32, u64\n#[plugin_fn]\npub fn sum_42(input: i32) -\u003e FnResult\u003ci64\u003e {\n    Ok(input as i64 + 42i64)\n}\n\n// u8 vec\n#[plugin_fn]\npub fn process_bytes(input: Vec\u003cu8\u003e) -\u003e FnResult\u003cVec\u003cu8\u003e\u003e {\n    // process bytes here\n    Ok(input)\n}\n\n// Strings\n#[plugin_fn]\npub fn process_string(input: String) -\u003e FnResult\u003cString\u003e {\n    // process string here\n    Ok(input)\n}\n```\n\n### Json\n\nWe provide a\n[Json](https://docs.rs/extism-pdk/latest/extism_pdk/struct.Json.html) type that\nallows you to pass structs that implement serde::Deserialize as parameters and\nserde::Serialize as returns:\n\n```rust\n#[derive(serde::Deserialize)]\nstruct Add {\n    a: u32,\n    b: u32,\n}\n#[derive(serde::Serialize)]\nstruct Sum {\n    sum: u32,\n}\n\n#[plugin_fn]\npub fn add(Json(add): Json\u003cAdd\u003e) -\u003e FnResult\u003cJson\u003cSum\u003e\u003e {\n    let sum = Sum { sum: add.a + add.b };\n    Ok(Json(sum))\n}\n```\n\nThe same thing can be accomplished using the `extism-convert` derive macros:\n\n```rust\n#[derive(serde::Deserialize, FromBytes)]\n#[encoding(Json)]\nstruct Add {\n    a: u32,\n    b: u32,\n}\n\n#[derive(serde::Serialize, ToBytes)]\n#[encoding(Json)]\nstruct Sum {\n    sum: u32,\n}\n\n#[plugin_fn]\npub fn add(add: Add) -\u003e FnResult\u003cSum\u003e {\n    let sum = Sum { sum: add.a + add.b };\n    Ok(sum)\n}\n```\n\n### Raw Export Interface\n\n[plugin_fn](https://docs.rs/extism-pdk/latest/extism_pdk/attr.plugin_fn.html) is\na nice macro abstraction but there may be times where you want more control. You\ncan code directly to the raw ABI interface of export functions.\n\n```rust\n#[no_mangle]\npub unsafe extern \"C\" fn greet() -\u003e i32 {\n    let name = unwrap!(input::\u003cString\u003e());\n    let result = format!(\"Hello, {}!\", name);\n    unwrap!(output(result));\n    0i32\n}\n```\n\n## Configs\n\nConfigs are key-value pairs that can be passed in by the host when creating a\nplug-in. These can be useful to statically configure the plug-in with some data\nthat exists across every function call. Here is a trivial example:\n\n```rust\n#[plugin_fn]\npub fn greet() -\u003e FnResult\u003cString\u003e {\n    let user = config::get(\"user\").expect(\"'user' key set in config\");\n    Ok(format!(\"Hello, {}!\", user))\n}\n```\n\nTo test it, the [Extism CLI](https://github.com/extism/cli) has a `--config`\noption that lets you pass in `key=value` pairs:\n\n```bash\nextism call my_plugin.wasm greet --config user=Benjamin\n# =\u003e Hello, Benjamin!\n```\n\n## Variables\n\nVariables are another key-value mechanism but it's a mutable data store that\nwill persist across function calls. These variables will persist as long as the\nhost has loaded and not freed the plug-in. You can use\n[var::get](https://docs.rs/extism-pdk/latest/extism_pdk/var/fn.get.html) and\n[var::set](https://docs.rs/extism-pdk/latest/extism_pdk/var/fn.set.html) to\nmanipulate them.\n\n```rust\n#[plugin_fn]\npub fn count() -\u003e FnResult\u003ci64\u003e {\n    let mut c = var::get(\"count\")?.unwrap_or(0);\n    c = c + 1;\n    var::set(\"count\", c)?;\n    Ok(c)\n}\n```\n\n## Logging\n\nBecause Wasm modules by default do not have access to the system, printing to\nstdout won't work (unless you use WASI). Extism provides some simple logging\nmacros that allow you to use the host application to log without having to give\nthe plug-in permission to make syscalls. The primary one is\n[log!](https://docs.rs/extism-pdk/latest/extism_pdk/macro.log.html) but we also\nhave some convenience macros named by log level:\n\n```rust\n#[plugin_fn]\npub fn log_stuff() -\u003e FnResult\u003c()\u003e {\n    log!(LogLevel::Info, \"Some info!\");\n    log!(LogLevel::Warn, \"A warning!\");\n    log!(LogLevel::Error, \"An error!\");\n\n    // optionally you can use the leveled macros: \n    info!(\"Some info!\");\n    warn!(\"A warning!\");\n    error!(\"An error!\");\n\n    Ok(())\n}\n```\n\nFrom [Extism CLI](https://github.com/extism/cli):\n\n```bash\nextism call my_plugin.wasm log_stuff --log-level=info\n2023/09/30 11:52:17 Some info!\n2023/09/30 11:52:17 A warning!\n2023/09/30 11:52:17 An error!\n```\n\n\u003e _Note_: From the CLI you need to pass a level with `--log-level`. If you are\n\u003e running the plug-in in your own host using one of our SDKs, you need to make\n\u003e sure that you call `set_log_file` to `\"stdout\"` or some file location.\n\n## HTTP\n\nSometimes it is useful to let a plug-in make HTTP calls.\n\n\u003e **Note**: See\n\u003e [HttpRequest](https://docs.rs/extism-pdk/latest/extism_pdk/struct.HttpRequest.html)\n\u003e docs for more info on the request and response types:\n\n```rust\n#[plugin_fn]\npub fn http_get(Json(req): Json\u003cHttpRequest\u003e) -\u003e FnResult\u003cVec\u003cu8\u003e\u003e {\n    let res = http::request::\u003c()\u003e(\u0026req, None)?;\n    Ok(res.body())\n}\n```\n\n## Imports (Host Functions)\n\nLike any other code module, Wasm not only let's you export functions to the\noutside world, you can import them too. Host Functions allow a plug-in to import\nfunctions defined in the host. For example, if you host application is written\nin Python, it can pass a Python function down to your Rust plug-in where you can\ninvoke it.\n\nThis topic can get fairly complicated and we have not yet fully abstracted the\nWasm knowledge you need to do this correctly. So we recommend reading out\n[concept doc on Host Functions](https://extism.org/docs/concepts/host-functions)\nbefore you get started.\n\n### A Simple Example\n\nHost functions have a similar interface as exports. You just need to declare\nthem as `extern` on the top of your `lib.rs`. You only declare the interface as\nit is the host's responsibility to provide the implementation:\n\n```rust\n#[host_fn]\nextern \"ExtismHost\" {\n    fn a_python_func(input: String) -\u003e String; \n}\n```\n\n\u003e **Note**: Under the hood this macro turns this into an interface that passes a\n\u003e pointer as an argument and a pointer as a return. If you want to pass raw,\n\u003e dereferenced wasm values see the raw interface documentation below.\n\nTo declare a host function in a specific namespace, pass the module name to the\n`host_fn` macro:\n\n```rust\n#[host_fn(\"extism:host/user\")]\n```\n\n\u003e **Note**: The types we accept here are the same as the exports as the\n\u003e interface also uses the\n\u003e [convert crate](https://docs.rs/extism-convert/latest/extism_convert/).\n\nTo call this function, we must use the `unsafe` keyword. Also note that it\nautomatically wraps the function return with a Result in case the call fails.\n\n```rust\n#[plugin_fn]\npub fn hello_from_python() -\u003e FnResult\u003cString\u003e {\n    let output = unsafe { a_python_func(\"An argument to send to Python\".into())? };\n    Ok(output)\n}\n```\n\n### Testing it out\n\nWe can't really test this from the Extism CLI as something must provide the\nimplementation. So let's write out the Python side here. Check out the\n[docs for Host SDKs](https://extism.org/docs/concepts/host-sdk) to implement a\nhost function in a language of your choice.\n\n```python\nfrom extism import host_fn, Plugin\n\n@host_fn()\ndef a_python_func(input: str) -\u003e str:\n    # just printing this out to prove we're in Python land\n    print(\"Hello from Python!\")\n\n    # let's just add \"!\" to the input string\n    # but you could imagine here we could add some\n    # applicaiton code like query or manipulate the database\n    # or our application APIs\n    return input + \"!\"\n```\n\nNow when we load the plug-in we pass the host function:\n\n```python\nmanifest = {\"wasm\": [{\"path\": \"/path/to/plugin.wasm\"}]}\nplugin = Plugin(manifest, functions=[a_python_func], wasi=True)\nresult = plugin.call('hello_from_python', b'').decode('utf-8')\nprint(result)\n```\n\n```bash\npython3 app.py\n# =\u003e Hello from Python!\n# =\u003e An argument to send to Python!\n```\n\n## Raw Import Interface\n\nLike exports, with imports we do some magic to turn the parameters and returns\ninto pointers for you. In some rare situations, you might wish to pass raw wasm\nvalues to the host (not pointers). If you do, you need to drop down into a raw\ninterface. E.g, imagine an interface that sums two i64s\n\n```rust\n#[link(wasm_import_module = \"extism:host/user\")]\nextern \"C\" {\n    fn sum(a: i64, b: i64) -\u003e i64;\n}\n```\n\n## Generating Bindings\n\nIt's often very useful to define a schema to describe the function signatures\nand types you want to use between Extism SDK and PDK languages.\n\n[XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source\nframework to generate PDK bindings for Extism plug-ins. It's used by the\n[XTP Platform](https://www.getxtp.com/), but can be used outside of the platform\nto define any Extism compatible plug-in system.\n\n### 1. Install the `xtp` CLI.\n\nSee installation instructions\n[here](https://docs.xtp.dylibso.com/docs/cli#installation).\n\n### 2. Create a schema using our OpenAPI-inspired IDL:\n\n```yaml\nversion: v1-draft\nexports: \n  CountVowels:\n      input: \n          type: string\n          contentType: text/plain; charset=utf-8\n      output:\n          $ref: \"#/components/schemas/VowelReport\"\n          contentType: application/json\n# components.schemas defined in example-schema.yaml...\n```\n\n\u003e See an example in [example-schema.yaml](./example-schema.yaml), or a full\n\u003e \"kitchen sink\" example on\n\u003e [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).\n\n### 3. Generate bindings to use from your plugins:\n\n```\nxtp plugin init --schema-file ./example-schema.yaml\n    1. TypeScript                      \n    2. Go                              \n  \u003e 3. Rust                            \n    4. Python                          \n    5. C#                              \n    6. Zig                             \n    7. C++                             \n    8. GitHub Template                 \n    9. Local Template\n```\n\nThis will create an entire boilerplate plugin project for you to get started\nwith:\n\n```rust\n// returns VowelReport (The result of counting vowels on the Vowels input.)\npub(crate) fn count_vowels(input: String ) -\u003e Result\u003cVowelReport, Error\u003e {\n\ttodo!(\"Implement count_vowels\")\n}\n```\n\nImplement the empty function(s), and run `xtp plugin build` to compile your\nplugin.\n\n\u003e For more information about XTP Bindgen, see the\n\u003e [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and\n\u003e the official\n\u003e [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).\n\n## Reach Out!\n\nHave a question or just want to drop in and say hi?\n[Hop on the Discord](https://extism.org/discord)!\n","funding_links":[],"categories":["\u003ca name=\"extism\"\u003e\u003c/a\u003e[Extism](https://github.com/extism/extism) \u003csup\u003e[top⇈](#contents)\u003c/sup\u003e","Rust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Frust-pdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextism%2Frust-pdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Frust-pdk/lists"}