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 RequirementsRequirements
-
The latest stable version of Rust, with the
wasm32-unknown-unknownbuild target installed:Terminal
rustup target add wasm32-unknown-unknown -
Version 3.85.0 or higher of Shopify CLI. Shopify recommends using the latest version.
-
Version 2.0.0 or higher of the
shopify_functioncrate. Functions that you generate with Shopify CLI use the latest version.
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:
|
Deserialize | A derive macro that deserializes function input into your own structs, such as a JSON metafield configuration. It supports the rename_all, rename, and default attributes through #[shopify_function(...)]. |
log! | A macro that writes to your function's logs. Use it instead of eprintln!. |
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 typegen macro, 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
Anchor to Example implementationsExample implementations
Explore example implementations using the shopify_function Rust crate.
Anchor to Binary size tipsBinary size tips
The compiled Wasm file for a Shopify Function must be under 256 kB. Here are a few tips to keep binary size small when using Rust:
-
Update the
shopify_functioncrate to the latest version. -
Keep the release profile settings from the function templates in your
Cargo.toml. They enable link-time optimization, optimize for size, and strip symbols:Cargo.toml
[profile.release]lto = trueopt-level = "z"strip = true -
Keep the
wasm_optconfiguration property enabled. Shopify CLI runswasm-opton your module by default, which further reduces its size. -
For regular expressions, use the regex_lite crate.
-
Follow tips and documentation in the johnthagen/min-sized-rust GitHub repository.
-
Use
to_ascii_uppercaseandto_ascii_lowercasewhen 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.
To reduce the number of instructions that your function executes, refer to Optimize instruction counts.
Anchor to Updating existing function to using shopify_function 2.0.0 and higherUpdating existing function to using shopify_ function 2. 0. 0 and higher
If you are using a version less than 1.0.0, you should update to version 1.1.1 as outlined below before following these steps.
-
Update to the latest Shopify CLI version. Version 2.0.0 of the crate requires Shopify CLI 3.85.0 or higher.
-
Update the
shopify_functiondependency in yourCargo.tomlto the latest version:Cargo.toml
[dependencies]shopify_function = "2.2.0" -
Install the
wasm32-unknown-unknownbuild target usingrustup target:Terminal
rustup target add wasm32-unknown-unknown -
Update your build
commandandpathin the[extensions.build]section of yourshopify.extension.tomlto usewasm32-unknown-unknowninstead ofwasm32-wasip1. ReplaceRUST-PACKAGE-NAMEwith thenamefrom yourCargo.toml:shopify.extension.toml
[extensions.build]command = "cargo build --target=wasm32-unknown-unknown --release"path = "target/wasm32-unknown-unknown/release/[RUST-PACKAGE-NAME].wasm" -
Throughout all of your source files, update any references to
eprintln!to uselog!instead.fn run(input: schema::run::Input) -> Result<schema::FunctionRunResult> {log!("This will be logged");todo!();} -
Throughout all of your source files, update any references to
process::exit(1)to useprocess::abort()instead.fn run(input: schema::run::Input) -> Result<schema::FunctionRunResult> {log!("Please invoke a named export.");process::abort();}
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
If your function uses a version of the shopify_function crate below 1.0.0, then follow these steps to update it to version 1.0.0 and higher. After that, update to version 2.0.0 and higher.
-
In
main.rs, add imports forshopify_function.use shopify_function::prelude::*;use shopify_function::Result; -
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 amainfunction that returns an error indicating to invoke a named export:fn main() {log!("Please invoke a named export.");std::process::abort();} -
If you have an input query to retrieve a JSON metafield value in your
run.graphqlfile, for example:Rust input query
src/run.graphqlquery Input {deliveryCustomization {metafield(namespace: "delivery-customization", key: "function-configuration") {jsonValue}}}You can deserialize the
jsonValuedirectly into an object you define in yourrun.rsfile 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_overridesto link thejsonValuewith its object definition in yourmain.rsfile 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.rsorfetch.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::ResponseDataschema::run::Inputinput::InputDiscountNodeMetafieldschema::run::input::discount_node::Metafieldinput::InputDiscountNodeschema::run::input::DiscountNodeoutput::FunctionRunResultschema::FunctionRunResultoutput::DiscountApplicationStrategy::FIRSTschema::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 wasm32-wasi build target used by cargo-wasi was removed.
If your function still builds with cargo-wasi or the wasm32-wasi target, then update it to version 2.0.0 or higher of the shopify_function crate by following the steps in Updating existing function to using shopify_function 2.0.0 and higher. Version 2.0.0 and higher builds with the wasm32-unknown-unknown target, and works with the latest stable version of Rust.
You can also remove the deprecated wasm32-wasi build target using rustup target:
Terminal
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 optimizes your module by default. You can configure this behavior with the wasm_opt configuration property.
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 optimizes your module by default. You can configure this behavior with 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. To measure and reduce instruction counts before and after migration, refer to Optimize instruction counts.
The safest way to migrate is to add your Rust code alongside your JavaScript code in the same function extension, and then use shopify.extension.toml to choose which language to build. Your extension, its uid, and its configuration stay the same. Only the language and the compiled WebAssembly module change, so merchants don't need to do anything, and you can switch back to JavaScript at any time.
Anchor to Add Rust to your JavaScript functionAdd Rust to your Java Script function
-
Update your JavaScript function to version 2.0.0 or higher of the
@shopify/shopify_functionpackage. While your JavaScript entry file is insrc/, Shopify CLI picks the local function runner based on this package's version. Earlier versions use a function runner that can't run modules built with version 2.0.0 or higher of theshopify_functionRust crate, soapp function run,app function replay, and profiling fail for your Rust build. Run the following command in your function's directory, and then confirm that your JavaScript function still builds and passes its tests:Terminal
npm install @shopify/shopify_function@^2.0.0 -
Install the
wasm32-unknown-unknownbuild target usingrustup target:Terminal
rustup target add wasm32-unknown-unknown -
Generate a temporary Rust function to use as a starting point:
Terminal
shopify app generate extensionWhen prompted, choose the same function type as your existing JavaScript function, and select
Rustas the language. -
Copy the
Cargo.tomlfile from the temporary function into your JavaScript function's directory. -
Copy the
.rsfiles from the temporary function'ssrc/directory into your JavaScript function'ssrc/directory. Your JavaScript files stay where they are, and both languages share the sameschema.graphqlfile and input queries. -
Delete the temporary function's directory, so that it isn't deployed with your app.
-
Add
target/to your function's.gitignorefile to exclude Rust build output. -
Update your input queries for Rust type generation. Add
__typenameto any fragments on interfaces or unions. This change doesn't affect your JavaScript code:src/cart_lines_discounts_generate_run.graphql
query CartInput {cart {lines {merchandise {__typename... on ProductVariant {id}}}}}You don't need to rename your queries. The Rust input type takes its name from the query's operation name, so
query CartInputgenerates aCartInputtype. Update the copied Rust code to use your type names, and make sure that the#[query]paths inmain.rsmatch your input query files. -
If your function reads its configuration from a JSON metafield, then deserialize the metafield directly into a Rust struct. Your JavaScript function reads the parsed object from
jsonValue. In Rust, you define a struct for the configuration and tell thetypegenmacro to use it as the type of thejsonValuefield.For example, the following input query reads a JSON configuration metafield on the discount:
src/cart_lines_discounts_generate_run.graphql
query CartInput {discount {metafield(namespace: "$app:my-discount", key: "function-configuration") {jsonValue}}}Define a struct that matches your configuration JSON. The
Deserializederive macro from theshopify_functioncrate reads the value directly from the function input. Userename_all = "camelCase"to map the JSON keys tosnake_casefield names, anddefaultfor optional fields:src/cart_lines_discounts_generate_run.rs
use shopify_function::prelude::*;pub struct Configuration {pub discount_percentage: f64,pub excluded_product_ids: Vec<String>,}In
main.rs, use thecustom_scalar_overridesargument of the#[query]attribute to map thejsonValuefield to your struct. The key is the path to the field, starting with the query's operation name. A type path that doesn't start with::is relative to theschemamodule, so usesuper::to reach your target's module:src/main.rs
pub mod schema {pub mod cart_lines_discounts_generate_run {}}Your function then receives the configuration as a typed struct, with no JSON parsing in your code:
src/cart_lines_discounts_generate_run.rs
let Some(configuration) = input.discount().metafield().map(|metafield| metafield.json_value()) else {return Ok(schema::CartLinesDiscountsGenerateRunResult { operations: vec![] });};let percentage = configuration.discount_percentage; -
Port your JavaScript logic to the
.rsfiles.
Anchor to Switch between JavaScript and RustSwitch between Java Script and Rust
The [extensions.build] section and each target's export in shopify.extension.toml determine which language Shopify CLI builds. Rust exports use the name of the Rust function marked with #[shopify_function], which is usually in snake_case, so the export values differ between languages.
To build the Rust version, update the export for each target and the [extensions.build] section. Keep your JavaScript values as comments. To switch back, comment out the Rust lines, including watch, and uncomment the JavaScript lines. Replace RUST-PACKAGE-NAME with the name from your Cargo.toml:
shopify.extension.toml
While your JavaScript entry file, such as src/index.js, is in the src/ directory, Shopify CLI treats the extension as a JavaScript function in some places. For example, app function run --profile warns that the profile won't contain function names, even when your Rust build keeps them.
While your JavaScript entry file, such as src/index.js, is in the src/ directory, Shopify CLI treats the extension as a JavaScript function in some places. For example, app function run --profile warns that the profile won't contain function names, even when your Rust build keeps them.
Anchor to Validate and deploy the migrationValidate and deploy the migration
-
Update your integration tests so that the same fixtures work with both languages. Each fixture's
exportvalue records the JavaScript export name, which the Rust module doesn't have. Intests/default.test.js, pass the export fromshopify.extension.tomltorunFunctioninstead:tests/default.test.js
const runResult = await runFunction({ ...fixture, export: targeting[fixture.target].export ?? fixture.export },functionRunnerPath,wasmPath,targetInputQueryPath,schemaPath,); -
Run your function's integration tests with the JavaScript configuration, and then again with the Rust configuration. Both versions should produce the expected output for every fixture. You can also compare instruction counts between the two runs:
Terminal
npm test -
Deploy to a dev store and verify that the function works as expected.
-
Deploy your app. Every store that uses your function runs the Rust version, because the extension itself hasn't changed.
-
If you find an issue, then switch
shopify.extension.tomlback to the JavaScript configuration and deploy again. -
When you're confident in the Rust version, remove your JavaScript source files from
src/.
Anchor to Next stepsNext steps
- Explore the reference documentation for the
shopify_functionRust crate.