Rust for Functions
You can write your functions in Rust. This guide describes the shopify_function
Rust crate that Shopify provides to help developers build with Shopify Functions.
Anchor to How it worksHow it works
The shopify_function
Rust crate performs type generation, reduces boilerplate code, and makes it easier to test various function inputs. It includes the following components:
Component | Description |
---|---|
typegen | A macro to enable struct generation from the Function API, based on the provided GraphQL schema and input query. |
shopify_function | An attribute macro that marks the given function as the entrypoint for Shopify Functions, by:
|
run_function_with_input | A utility for unit testing that enables you to add new tests based on a given JSON input string. |
Anchor to Viewing the generated typesViewing the generated types
To preview the types generated by the shopify_function
Rust crate, use the cargo doc
command.
Terminal
You can also use the cargo-expand crate to view the generated source:
Terminal
Anchor to Development toolsDevelopment tools
To make development easier, install the rust-analyzer VSCode extension for:
- Code completion
- Go to definition
- Real-time error checking
- Type information on hover
The generated .output.graphql
files are used for output type generation purposes. You can add these files to your .gitignore
file.
Anchor to Example implementationsExample implementations
Explore example implementations using the shopify_function
Rust crate.
Explore an example of how to use the shopify_function
crate to implement a Shopify Function in Rust.
Explore an example of how to use the shopify_function
crate to implement a Shopify Function in Rust compatible with API versions 2023-07 and earlier.
Anchor to Binary size tipsBinary size tips
Shopify Functions compiled Wasm file must be under 256 kB. Here are a few tips to keep binary size small when using Rust:
-
Update the
shopify_function
crate to the latest version. -
For regular expressions, use the regex_lite crate.
-
Follow tips and documentation in the johnthagen/min-sized-rust GitHub repository.
-
Use
wasm-snip
to remove the panicking code, thenwasm-opt
to strip debug information. For example:
-
Use
to_ascii_uppercase
andto_ascii_lowercase
when possible to avoid pulling in Unicode tables, unless needed. -
Only query for data you need.
Code generation happens for all types and fields included in the input queries (for example,
run.graphql
). Review and remove any unused parts of the queries. -
Keep JSON metafields that require deserialization as small as possible.
Code generated for deserialization increases the binary size. The smaller the metafield is, the less code needs to be generated.
-
Bring your own types and deserializers.
Instead of using the generated structs from the
shopify_function_macro
crate, write the appropriate struct definitions and derive the deserializers usingmini_serde
.The structs from
shopify_function_macro
can be used as a starting point, see them withcargo expand
.Alternative serializers are generally less efficient than serde, make sure to benchmark the instruction count when going down this path.
-
Updating the
shopify_function
crate in your function to version1.0.0
and above as outlined below.
Anchor to Updating existing function to using shopify_function 1.0.0 and higherUpdating existing function to using shopify_ function 1. 0. 0 and higher
Migrate your function to the latest shopify_function
crate for potential speedups and smaller binary sizes. Follow these steps:
-
In
main.rs
, add imports forshopify_function
.use shopify_function::prelude::*; -
In
main.rs
, add type generation, right under your imports. Remove any references to thegenerate_types!
macro.pub mod schema {pub mod run {}}If your Function has multiple targets each with their own input query, add a nested module for each. For example:
pub mod schema {pub mod fetch {}pub mod run {}} -
In
main.rs
, ensure that you have amain
function that returns an error indicating to invoke a named export:fn main() {eprintln!("Invoke a named import");std::process::exit(1);} -
If you have an input query to retrieve a JSON metafield value in your
run.graphql
file, for example:Rust input query
src/run.graphqlquery Input {deliveryCustomization {metafield(namespace: "delivery-customization", key: "function-configuration") {jsonValue}}}You can deserialize the
jsonValue
directly into an object you define in yourrun.rs
file and annotate with#[shopify_function(rename_all = "camelCase")]
and#[derive(Deserialize)]
as shown below:Rust
src/run.rspub struct DeliveryConfiguration {state_province_code: String,message: String,}Finally, use
custom_scalar_overrides
to link thejsonValue
with its object definition in yourmain.rs
file as shown below:Rust
src/main.rsmod schema {pub mod run {}} -
Ensure your source file that has the function logic defined, includes the following imports.
use shopify_function::prelude::*;use shopify_function::Result;use super::schema;typically this is in
run.rs
orfetch.rs
-
Throughout all of your source files, replace any references to
#[shopify_function_target]
with the#[shopify_function]
macro, and change its return type. Typically, this is located in a file with a name equal to the target, e.g.run.rs
.fn run(input: schema::run::Input) -> Result<schema::FunctionRunResult> { -
Update the types and fields utilized in the function to the new, auto-generated structs. For example:
Old New input::ResponseData
schema::run::Input
input::InputDiscountNodeMetafield
schema::run::input::discount_node::Metafield
input::InputDiscountNode
schema::run::input::DiscountNode
output::FunctionRunResult
schema::FunctionRunResult
output::DiscountApplicationStrategy::FIRST
schema::DiscountApplicationStrategy::First
Anchor to Updating to Rust 1.84 and higherUpdating to Rust 1. 84 and higher
Previously, we encouraged the use of cargo-wasi
as a way to build and optimize your Rust functions. However, as of Rust version 1.84, the WebAssembly build target used by cargo-wasi
was removed.
To migrate an existing Rust function to Rust version 1.84 or higher, complete the following steps:
-
Update to the latest Shopify CLI version.
-
Remove the deprecated
wasm32-wasi
build target usingrustup target
:Terminal
rustup target remove wasm32-wasi -
Update your Rust version using
rustup update
:Terminal
rustup update stable -
Install the new
wasm32-wasip1
build target usingrustup target
:Terminal
rustup target add wasm32-wasip1 -
Update your build
command
andpath
in the[extensions.build]
section of yourshopify.extension.toml
. ReplaceRUST-PACKAGE-NAME
with thename
from yourCargo.toml
:shopify.extension.toml
[extensions.build]command = "cargo build --target=wasm32-wasip1 --release"path = "target/wasm32-wasip1/release/[RUST-PACKAGE-NAME].wasm"
These changes are compatible with Rust 1.78 and higher.
In addition to building your Rust function for WebAssembly, the cargo-wasi
crate also optimized the size of your binary using the Binaryen toolchain. Shopify CLI will now optimize your module by default. You can configure this behavior via the wasm_opt
configuration property.
Anchor to Migrating from JavaScriptMigrating from Java Script
Migrating your JavaScript Shopify Function to Rust can significantly improve performance and help you stay within platform fuel limits. Rust compiles directly to WebAssembly, resulting in more efficient execution compared to JavaScript.
Anchor to JavaScript migration stepsJava Script migration steps
-
Generate a new function using Shopify CLI:
Terminal
shopify app generate extension -
When prompted:
- Choose the same function type as your existing JavaScript function
- Name it the same as your current function but append
-rs
(e.g., if your current function isproduct-discount
, name itproduct-discount-rs
) - Select
Rust
as the language
-
Copy your existing GraphQL query, making these adjustments to support the Rust code generation:
- Copy your
run.graphql
from your JavaScript function to the new Rust function'ssrc
directory - Rename the query from
RunInput
toInput
- Add
__typename
to any fragments on interfaces or unions:
src/run.graphql
# Before (JavaScript):# query RunInput {# cart {# lines {# merchandise {# ... on ProductVariant {# id# }# }# }# }# }# After (Rust):query Input {cart {lines {merchandise {__typename... on ProductVariant {id}}}}} - Copy your
-
Port your JavaScript logic to the generated
src/run.rs
file
Anchor to Reusing extension handlesReusing extension handles
By reusing the extension handle from your JavaScript function, you can seamlessly replace the existing function on the server. This means all existing instances of your function will automatically use the new Rust implementation without any changes required on the merchant's side.
Migration steps:
-
Copy the existing handle from your JavaScript function's
shopify.extension.toml
:extensions/your-function/shopify.extension.toml
# JavaScript function's configurationname = "your-function"handle = "your-existing-handle" # Copy this value -
Update your new Rust function's configuration with the copied handle:
extensions/your-function-rs/shopify.extension.toml
# Rust function's configurationname = "your-function-rs"handle = "your-existing-handle" # Paste the handle here -
Disable the JavaScript function by renaming its configuration file:
Terminal
mv extensions/your-function/shopify.extension.toml extensions/your-function/shopify.extension.disabled.toml
If you deploy both functions, they will both appear in the merchant admin, which may cause confusion. Always ensure you've disabled the JavaScript function before deploying the Rust version.
Anchor to Validating the migrationValidating the migration
Before deploying to production:
-
Test the function locally:
Terminal
shopify app function run --input input.json -
Deploy to a development store and verify the function works as expected
-
Confirm only one function appears in the merchant admin
-
If everything works correctly, you can safely delete the JavaScript function directory
Anchor to Next stepsNext steps
- Explore the reference documentation for the
shopify_function
Rust crate.