Skip to content

feat: add rust module sdk#12229

Open
kjuulh wants to merge 3 commits intodagger:mainfrom
kjuulh:feat/rust-module-sdk
Open

feat: add rust module sdk#12229
kjuulh wants to merge 3 commits intodagger:mainfrom
kjuulh:feat/rust-module-sdk

Conversation

@kjuulh
Copy link
Copy Markdown
Contributor

@kjuulh kjuulh commented Mar 24, 2026

I've gone ahead and updated the rust sdk to now optionally include module support. Along with a few bug fixes (Void) etc.

It has been added as stable, and supports both being used, or using other functions. I did have some limited testing because it seems when modules are added externally they are pulled from git, and as I don't have a commit on main yet, it couldn't be loaded by the dev engine.

All in all, fairly minimal changes given the circumstances.

syntax is done using proc_macros, and you can mix and match async functions

use dagger_sdk::*;

#[derive(Default)]
pub struct TestModule;

/// A test module to verify the Rust SDK integration in the dagger source tree.
#[dagger_module]
impl TestModule {
    /// Returns a greeting.
    #[dagger_function]
    fn hello(&self) -> String {
        "Hello from the dagger source tree!".to_string()
    }

    /// Echoes a message via an alpine container.
    #[dagger_function]
    async fn container_echo(&self, msg: String) -> eyre::Result<String> {
        dag()
            .container()
            .from("alpine:latest")
            .with_exec(vec!["echo", &msg])
            .stdout()
            .await
            .map_err(|e| eyre::eyre!(e))
    }
}

#[tokio::main]
async fn main() -> eyre::Result<()> {
    dagger_sdk::run(TestModule).await
}

kjuulh added 3 commits March 24, 2026 23:26
Add the ability to write Dagger modules in Rust, matching the module
support available in Go, Python, TypeScript, and other SDKs.

SDK changes (sdk/rust/):
- crates/dagger-sdk: add `module` feature with registration, invocation,
  dag() global, gen_deps re-exports, and top-level prelude re-exports.
  Make querybuilder module public (needed by generated code).
- crates/dagger-sdk-derive: new proc macro crate providing
  #[dagger_module] and #[dagger_function] attribute macros
- runtime/: Go runtime module implementing ModuleRuntime() and Codegen()
  for the Dagger engine, with codegen via dagger-bootstrap
- dagger.json: root module config for engine SDK loading via GitHub
- runtime/dagger.json: standalone config for local path testing
- examples/test-module/: working example module

Engine changes (core/sdk/):
- Register "rust" as a built-in SDK using the external module pattern
  (same as Java/PHP/Elixir), resolving to github.com/dagger/dagger/sdk/rust

User code:
  use dagger_sdk::*;

  #[derive(Default)]
  pub struct MyModule;

  #[dagger_module]
  impl MyModule {
      #[dagger_function]
      fn hello(&self) -> String {
          "Hello from Rust!".to_string()
      }

      #[dagger_function]
      async fn build(&self, msg: String) -> eyre::Result<String> {
          dag().container().from("alpine:latest")
              .with_exec(vec!["echo", &msg])
              .stdout().await
              .map_err(|e| eyre::eyre!(e))
      }
  }

  #[tokio::main]
  async fn main() -> eyre::Result<()> {
      dagger_sdk::run(MyModule).await
  }

Signed-off-by: kjuulh <contact@kjuulh.io>
…ror context

Three fixes to the Rust SDK codegen and runtime:

1. Void type: special-case Void in scalar_tmpl.rs to use
   Option<String> with a custom Deserialize impl that accepts null.
   Previously `pub struct Void(pub String)` failed when the API
   returned null for void operations like returnValue().

2. Nullable scalar fields: render_output_type now checks is_optional()
   and wraps nullable scalar return types in Option<T>. Added
   execute_opt() to Selection that returns Option<D> instead of
   unwrapping. Fields like parent_name() that can be null will now
   return Result<Option<String>> after regeneration.

3. Error context: DaggerUnpackError::Deserialize now includes the
   field name that failed deserialization, making errors like
   "failed to deserialize response for field 'parentName'" instead
   of the opaque "failed to deserialize response".

Signed-off-by: kjuulh <contact@kjuulh.io>
Regenerated from engine introspection with the updated codegen:
- Void is now `pub struct Void(pub Option<String>)` with custom
  Deserialize — properly handles null from the API
- return_value() returns `Result<Option<Void>>` using execute_opt()
- Nullable fields use execute_opt() for proper Option<T> handling
- cargo fmt applied to all crates

Signed-off-by: kjuulh <contact@kjuulh.io>
@kjuulh kjuulh requested a review from a team as a code owner March 24, 2026 22:33
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@grouville grouville added this to the v0.20.5 milestone Apr 8, 2026
@grouville
Copy link
Copy Markdown
Member

@kjuulh once you'll have rebased this PR, I'll take another look 🙏

@grouville grouville modified the milestones: v0.20.5, v0.20.6, v0.20.7 Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants