{"id":37309807,"url":"https://github.com/z-galaxy/zlink","last_synced_at":"2026-02-22T16:32:49.745Z","repository":{"id":282318158,"uuid":"926490157","full_name":"z-galaxy/zlink","owner":"z-galaxy","description":"An asynchronous no-std-compatible Varlink Rust crate","archived":false,"fork":false,"pushed_at":"2026-02-09T20:57:27.000Z","size":1762,"stargazers_count":44,"open_issues_count":14,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-10T00:20:29.115Z","etag":null,"topics":["ipc","rust","varlink"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/z-galaxy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"zeenix"}},"created_at":"2025-02-03T10:53:24.000Z","updated_at":"2026-02-09T20:57:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"2deaa171-f2ef-4330-ad80-7bc859b6f2bb","html_url":"https://github.com/z-galaxy/zlink","commit_stats":null,"previous_names":["zeenix/zarlink","z-galaxy/zlink"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/z-galaxy/zlink","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z-galaxy%2Fzlink","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z-galaxy%2Fzlink/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z-galaxy%2Fzlink/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z-galaxy%2Fzlink/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/z-galaxy","download_url":"https://codeload.github.com/z-galaxy/zlink/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/z-galaxy%2Fzlink/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29718423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T15:10:41.462Z","status":"ssl_error","status_checked_at":"2026-02-22T15:10:04.636Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["ipc","rust","varlink"],"created_at":"2026-01-16T02:59:54.330Z","updated_at":"2026-02-22T16:32:49.733Z","avatar_url":"https://github.com/z-galaxy.png","language":"Rust","funding_links":["https://github.com/sponsors/zeenix"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://crates.io/crates/zlink\"\u003e\n    \u003cimg alt=\"crates.io\" src=\"https://img.shields.io/crates/v/zlink\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://docs.rs/zlink/\"\u003e\n    \u003cimg alt=\"API Documentation\" src=\"https://docs.rs/zlink/badge.svg\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/z-galaxy/zlink/actions/workflows/rust.yml\"\u003e\n    \u003cimg alt=\"Build Status\" src=\"https://github.com/z-galaxy/zlink/actions/workflows/rust.yml/badge.svg\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"Project logo\" src=\"https://raw.githubusercontent.com/z-galaxy/zlink/3660d731d7de8f60c8d82e122b3ece15617185e4/data/logo.svg\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003ezlink\u003c/h1\u003e\n\nA Rust implementation of the [Varlink](https://varlink.org/) IPC protocol. zlink provides a safe,\nasync API for building Varlink services and clients.\n\n## Overview\n\nVarlink is a simple, JSON-based IPC protocol that enables communication between system services and\napplications. zlink makes it easy to implement Varlink services in Rust with:\n\n- **Async-first design**: Built on async/await for efficient concurrent operations.\n- **Type safety**: Leverage Rust's type system with derive macros and code generation.\n- **Multiple transports**: Unix domain sockets and (upcoming) USB support.\n- **Code generation**: Generate Rust code from Varlink IDL files.\n\n## Project Structure\n\nThe zlink project consists of several subcrates:\n\n- **[`zlink`]**: The main unified API crate that re-exports functionality based on enabled features.\n  This is the only crate you will want to use directly in your application and services.\n- **[`zlink-core`]**: Core no-std foundation providing essential Varlink types and traits.\n- **[`zlink-macros`]**: Contains the attribute and derive macros.\n- **[`zlink-tokio`]**: `Tokio`-based transport implementations and runtime integration.\n- **[`zlink-codegen`]**: Code generation tool for creating Rust bindings from Varlink IDL files.\n\n## Examples\n\n### Example: Calculator Service and Client\n\nHere's a complete example showing both service and client implementations using zlink's attribute\nmacros:\n\n```rust\nuse serde::{Deserialize, Serialize};\nuse tokio::{select, sync::oneshot, fs::remove_file};\nuse zlink::{introspect, proxy, service, unix, ReplyError, Server};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Create a channel to signal when server is ready.\n    let (ready_tx, ready_rx) = oneshot::channel();\n\n    // Run server and client concurrently.\n    select! {\n        res = run_server(ready_tx) =\u003e res?,\n        res = run_client(ready_rx) =\u003e res?,\n    }\n\n    Ok(())\n}\n\nasync fn run_client(ready_rx: oneshot::Receiver\u003c()\u003e) -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Wait for server to be ready.\n    ready_rx.await.map_err(|_| \"Server failed to start\")?;\n\n    // Connect to the calculator service.\n    let mut conn = unix::connect(SOCKET_PATH).await?;\n\n    // Use the proxy-generated methods.\n    let result = conn.add(5.0, 3.0).await?.unwrap();\n    assert_eq!(result.result, 8.0);\n\n    let result = conn.multiply(4.0, 7.0).await?.unwrap();\n    assert_eq!(result.result, 28.0);\n\n    // Handle errors properly.\n    let Err(CalculatorError::DivisionByZero { message }) = conn.divide(10.0, 0.0).await? else {\n        panic!(\"Expected DivisionByZero error\");\n    };\n    assert_eq!(message, \"Cannot divide by zero\");\n\n    // Test invalid input error with large dividend.\n    let Err(CalculatorError::InvalidInput { field, reason }) =\n        conn.divide(2000000.0, 2.0).await?\n    else {\n        panic!(\"Expected InvalidInput error\");\n    };\n    println!(\"Field: {field}, Reason: {reason}\");\n\n    let stats = conn.get_stats().await?.unwrap();\n    assert_eq!(stats.count, 2);\n    println!(\"Stats: {stats:?}\");\n\n    Ok(())\n}\n\n// The client proxy.\n#[proxy(\"org.example.Calculator\")]\ntrait CalculatorProxy {\n    async fn add(\n        \u0026mut self,\n        a: f64,\n        b: f64,\n    ) -\u003e zlink::Result\u003cResult\u003cCalculationResult, CalculatorError\u003c'_\u003e\u003e\u003e;\n    async fn multiply(\n        \u0026mut self,\n        x: f64,\n        y: f64,\n    ) -\u003e zlink::Result\u003cResult\u003cCalculationResult, CalculatorError\u003c'_\u003e\u003e\u003e;\n    async fn divide(\n        \u0026mut self,\n        dividend: f64,\n        divisor: f64,\n    ) -\u003e zlink::Result\u003cResult\u003cCalculationResult, CalculatorError\u003c'_\u003e\u003e\u003e;\n    async fn get_stats(\n        \u0026mut self,\n    ) -\u003e zlink::Result\u003cResult\u003cStatistics\u003c'_\u003e, CalculatorError\u003c'_\u003e\u003e\u003e;\n}\n\n// Types shared between client and server.\n#[derive(Debug, Serialize, Deserialize)]\nstruct CalculationResult {\n    result: f64,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\nstruct Statistics\u003c'a\u003e {\n    count: u64,\n    #[serde(borrow)]\n    operations: Vec\u003c\u0026'a str\u003e,\n}\n\n#[derive(Debug, PartialEq, ReplyError, introspect::ReplyError)]\n#[zlink(interface = \"org.example.Calculator\")]\nenum CalculatorError\u003c'a\u003e {\n    DivisionByZero {\n        message: \u0026'a str,\n    },\n    InvalidInput {\n        field: \u0026'a str,\n        reason: \u0026'a str,\n    },\n}\n\nasync fn run_server(ready_tx: oneshot::Sender\u003c()\u003e) -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    let _ = remove_file(SOCKET_PATH).await;\n\n    // Setup and run the server.\n    let listener = unix::bind(SOCKET_PATH)?;\n    let server = Server::new(listener, Calculator::new());\n\n    // Signal that server is ready.\n    let _ = ready_tx.send(());\n\n    server.run().await.map_err(|e| e.into())\n}\n\n// The calculator service.\nstruct Calculator {\n    operations: Vec\u003cString\u003e,\n}\n\nimpl Calculator {\n    fn new() -\u003e Self {\n        Self { operations: Vec::new() }\n    }\n}\n\n#[service(interface = \"org.example.Calculator\")]\nimpl Calculator {\n    async fn add(\u0026mut self, a: f64, b: f64) -\u003e CalculationResult {\n        self.operations.push(format!(\"add({a}, {b})\"));\n        CalculationResult { result: a + b }\n    }\n\n    async fn multiply(\u0026mut self, x: f64, y: f64) -\u003e CalculationResult {\n        self.operations.push(format!(\"multiply({x}, {y})\"));\n        CalculationResult { result: x * y }\n    }\n\n    async fn divide(\n        \u0026mut self,\n        dividend: f64,\n        divisor: f64,\n    ) -\u003e Result\u003cCalculationResult, CalculatorError\u003c'_\u003e\u003e {\n        if divisor == 0.0 {\n            Err(CalculatorError::DivisionByZero {\n                message: \"Cannot divide by zero\",\n            })\n        } else if dividend \u003c -1000000.0 || dividend \u003e 1000000.0 {\n            Err(CalculatorError::InvalidInput {\n                field: \"dividend\",\n                reason: \"must be within range\",\n            })\n        } else {\n            self.operations\n                .push(format!(\"divide({dividend}, {divisor})\"));\n            Ok(CalculationResult {\n                result: dividend / divisor,\n            })\n        }\n    }\n\n    async fn get_stats(\u0026self) -\u003e Statistics\u003c'_\u003e {\n        let ops: Vec\u003c\u0026str\u003e = self.operations.iter().map(|s| s.as_str()).collect();\n        Statistics {\n            count: self.operations.len() as u64,\n            operations: ops,\n        }\n    }\n}\n\nconst SOCKET_PATH: \u0026str = \"/tmp/calculator_example.varlink\";\n```\n\n\u003e **Note**: Typically you would want to spawn the server in a separate task but that's not what we\n\u003e did in the example above. Please refer to [`Server::run` docs] for the reason.\n\n## Code Generation from IDL\n\nzlink-codegen can generate Rust code from Varlink interface description files:\n\n```sh\n# Install the code generator\ncargo install zlink-codegen\n\n# Let's create a file containing Varlink IDL\ncat \u003c\u003cEOF \u003e calculator.varlink\n# Calculator service interface\ninterface org.example.Calculator\n\ntype CalculationResult (\n    result: float\n)\n\ntype DivisionByZeroError (\n    message: string\n)\n\nmethod Add(a: float, b: float) -\u003e (result: float)\nmethod Multiply(x: float, y: float) -\u003e (result: float)\nmethod Divide(dividend: float, divisor: float) -\u003e (result: float)\nerror DivisionByZero(message: string)\nEOF\n\n# Generate Rust code from the IDL\nzlink-codegen calculator.varlink \u003e src/calculator_gen.rs\n```\n\nThe generated code includes type definitions and proxy traits ready to use in your application.\n\n### Pipelining\n\nzlink supports method call pipelining for improved throughput and reduced latency. The `proxy` macro\nadds variants for each method named `chain_\u003cmethod_name\u003e` and a trait named `\u003cTraitName\u003eChain` that\nallow you to batch multiple requests and send them out at once without waiting for individual\nresponses.\n\n\u003e **Note**: Chain methods are only generated for proxy methods that use owned types\n\u003e (`DeserializeOwned`) in their return type. Methods with borrowed types (non-static lifetimes)\n\u003e don't get chain variants since the internal buffer may be reused between stream iterations. Input\n\u003e arguments can still use borrowed types.\n\n```rust,no_run\nuse futures_util::{StreamExt, pin_mut};\nuse serde::{Deserialize, Serialize};\nuse zlink::{proxy, unix, ReplyError};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Connect to a batch processing service\n    let mut conn = unix::connect(\"/tmp/batch_processor.varlink\").await?;\n\n    // Send multiple pipelined requests without waiting for responses.\n    // Note: chain methods are only generated for proxy methods with owned return types.\n    let replies = conn\n        .chain_process(1, \"first\")?\n        .process(2, \"second\")?\n        .process(3, \"third\")?\n        .batch_process(vec![\n            ProcessRequest { id: 4, data: \"batch1\" },\n            ProcessRequest { id: 5, data: \"batch2\" },\n        ])?\n        .send::\u003cProcessReply, ProcessError\u003e()\n        .await?;\n\n    // Collect all responses\n    pin_mut!(replies);\n    let mut results = Vec::new();\n    while let Some(reply) = replies.next().await {\n        let (reply, _fds) = reply?;\n        if let Ok(response) = reply {\n            match response.into_parameters() {\n                Some(ProcessReply::Result(result)) =\u003e {\n                    results.push(result);\n                }\n                Some(ProcessReply::BatchResult(batch)) =\u003e {\n                    results.extend(batch.results);\n                }\n                None =\u003e {}\n            }\n        }\n    }\n\n    // Process results\n    for result in results {\n        println!(\"Processed item {}: {}\", result.id, result.processed);\n    }\n\n    Ok(())\n}\n\n// Proxy trait with owned return types - chain methods are generated.\n#[proxy(\"org.example.BatchProcessor\")]\ntrait BatchProcessorProxy {\n    async fn process(\n        \u0026mut self,\n        id: u32,\n        data: \u0026str,\n    ) -\u003e zlink::Result\u003cResult\u003cProcessReply, ProcessError\u003e\u003e;\n\n    async fn batch_process(\n        \u0026mut self,\n        requests: Vec\u003cProcessRequest\u003c'_\u003e\u003e,\n    ) -\u003e zlink::Result\u003cResult\u003cProcessReply, ProcessError\u003e\u003e;\n}\n\n// Input types can use borrowed data (they implement Serialize, not DeserializeOwned).\n#[derive(Debug, Serialize)]\nstruct ProcessRequest\u003c'a\u003e {\n    id: u32,\n    data: \u0026'a str,\n}\n\n// Owned reply types - required for chain API (DeserializeOwned).\n#[derive(Debug, Deserialize)]\n#[serde(untagged)]\nenum ProcessReply {\n    Result(ProcessResult),\n    BatchResult(BatchResult),\n}\n\n#[derive(Debug, Deserialize)]\nstruct ProcessResult {\n    id: u32,\n    processed: String,\n}\n\n#[derive(Debug, Deserialize)]\nstruct BatchResult {\n    results: Vec\u003cProcessResult\u003e,\n}\n\n#[derive(Debug, ReplyError)]\n#[zlink(interface = \"org.example.BatchProcessor\")]\nenum ProcessError {\n    InvalidRequest { message: String },\n}\n```\n\n## Examples\n\nThe repository includes a few examples:\n\n- **[resolved.rs](zlink/examples/resolved.rs)**: DNS resolution using systemd-resolved's Varlink\n  service\n- **[varlink-inspect.rs](zlink/examples/varlink-inspect.rs)**: Service introspection tool\n\nRun examples with:\n\n```bash\ncargo run --example resolved -- example.com systemd.io\ncargo run \\\n  --example varlink-inspect \\\n  --features idl-parse,introspection -- \\\n  /run/systemd/resolve/io.systemd.Resolve\n```\n\n## Features\n\n### Main Features\n\n- `tokio` (default): Enable tokio runtime integration.\n- `smol`: Enable smol runtime integration.\n- `server` (default): Enable server-related functionality (Server, Listener, Service).\n- `service` (default): Enable the `#[service]` macro. Implies `server` and `introspection`.\n- `proxy` (default): Enable the `#[proxy]` macro for type-safe client code.\n- `tracing` (default): Enable `tracing`-based logging.\n- `defmt`:  Enable `defmt`-based logging. If both `tracing` and `defmt` is enabled, `tracing` is\n  used.\n\n### IDL and Introspection\n\n- `idl`: Support for IDL type representations.\n- `introspection`: Enable runtime introspection of service interfaces.\n- `idl-parse`: Parse Varlink IDL files at runtime.\n\n## Getting Help and/or Contributing\n\nIf you need help in using these crates, are looking for ways to contribute, or just want to hang out\nwith the cool kids, please come chat with us in the\n[`#zlink:matrix.org`](https://matrix.to/#/#zlink:matrix.org) Matrix room. If something doesn't seem\nright, please [file an issue](https://github.com/z-galaxy/zlink/issues/new).\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nThis project is licensed under the [MIT License][license].\n\n[cips]: https://github.com/z-galaxy/zlink/actions/workflows/rust.yml\n[crates.io]: https://crates.io/crates/zlink\n[license]: ./LICENSE\n[`zlink`]: https://docs.rs/zlink\n[`zlink-core`]: https://docs.rs/zlink-core\n[`zlink-tokio`]: https://docs.rs/zlink-tokio\n[`zlink-codegen`]: https://docs.rs/zlink-codegen\n[`zlink-macros`]: https://docs.rs/zlink-macros\n[`Server::run` docs]: https://docs.rs/zlink/latest/zlink/struct.Server.html#method.run\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fz-galaxy%2Fzlink","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fz-galaxy%2Fzlink","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fz-galaxy%2Fzlink/lists"}