{"id":51389796,"url":"https://github.com/attackgoat/read-only","last_synced_at":"2026-07-03T22:34:28.502Z","repository":{"id":367039871,"uuid":"1247455256","full_name":"attackgoat/read-only","owner":"attackgoat","description":"Read-only field exposure and safe composition helpers via proc macros","archived":false,"fork":false,"pushed_at":"2026-05-23T18:33:26.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-03T22:34:25.038Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/attackgoat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-23T10:43:05.000Z","updated_at":"2026-05-23T18:33:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/attackgoat/read-only","commit_stats":null,"previous_names":["attackgoat/read-only"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/attackgoat/read-only","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attackgoat%2Fread-only","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attackgoat%2Fread-only/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attackgoat%2Fread-only/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attackgoat%2Fread-only/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/attackgoat","download_url":"https://codeload.github.com/attackgoat/read-only/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attackgoat%2Fread-only/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35104115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-03T02:00:05.635Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-07-03T22:34:23.326Z","updated_at":"2026-07-03T22:34:28.496Z","avatar_url":"https://github.com/attackgoat.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# read-only\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/logo.svg\" alt=\"read-only logo\" width=\"600\"\u003e\n\u003c/p\u003e\n\n[![Crates.io](https://img.shields.io/crates/v/read-only.svg)](https://crates.io/crates/read-only)\n[![docs.rs](https://docs.rs/read-only/badge.svg)](https://docs.rs/read-only)\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT)\n\nRead-only field exposure and safe composition helpers via proc macros.\n\nThis crate intentionally combines two patterns:\n\n- `#[cast]` is the classic cast-based readonly-field pattern, re-exported from\n  [`readonly::make`](https://docs.rs/readonly/latest/readonly/attr.make.html).\n- `#[embed]` is this crate's safe composition-based alternative that builds a\n  dedicated `ReadOnly*` view struct and dereferences into it without unsafe code.\n\n## Installation\n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\nread-only = \"0.1\"\n```\n\n## Usage\n\nTwo attribute macros are provided.\n\n### `#[cast]`\n\n\u003e [!NOTE]\n\u003e `#[cast]` is a re-export of `readonly::make`. You can use either\n\u003e `read_only::cast` or `readonly::make` — they are the same proc macro.\n\nImplements `Deref` via an unsafe pointer cast — wrapped safely so callers\nnever write `unsafe` themselves. Fields remain writable inside the defining\nmodule but become read-only externally.\n\n**All fields read-only** (no `#[readonly]` annotation needed):\n\n```rust\nuse read_only::cast;\n\n#[cast]\npub struct Config {\n    pub timeout: u64,\n    pub retries: u32,\n}\n\n// Inside this module:  config.timeout = 5;        // ✅ write\n// Outside:             config.timeout = 5;        // ❌ compile error\n//                      let t = config.timeout;    // ✅ read\n```\n\n**Selective read-only** (annotate specific fields with `#[readonly]`):\n\n```rust\nuse read_only::cast;\n\n#[cast]\npub struct Device {\n    #[readonly]\n    pub serial: u64,\n    pub mutable_count: i32,\n    internal: f64, // stays private in both views\n}\n\n// Inside this module:  dev.mutable_count = 1;     // ✅ write\n//                      dev.serial = 2;             // ✅ write (inside module)\n// Outside:             dev.mutable_count = 1;      // ✅ write (not readonly)\n//                      let s = dev.serial;         // ✅ read only\n//                      dev.serial = 2;             // ❌ compile error\n```\n\nWhen no `#[readonly]` attribute appears on any field, the entire struct\nbecomes read-only through a generated view struct. When any field is annotated,\nonly annotated fields are lifted into the read-only view; unannotated fields\nkeep their original mutability.\n\n### `#[embed]`\n\nMoves `#[readonly]` annotated fields into a `ReadOnly*` struct that is embedded\ninside the original struct. A safe `Deref` implementation is generated — no\nunsafe code is involved.\n\n```rust\nuse read_only::embed;\n\n#[embed]\npub struct Device {\n    #[readonly]\n    pub handle: u64,\n    #[readonly]\n    pub label: String,\n    pub mutable: i32,\n}\n\nimpl Device {\n    pub fn new(handle: u64, label: \u0026str, mutable: i32) -\u003e Self {\n        Self {\n            read_only: ReadOnlyDevice { handle, label: label.to_string() },\n            mutable,\n        }\n    }\n}\n\n// let mut dev = Device::new(42, \"dev1\", 0);\n// let h = dev.handle;    // ✅ read — goes through Deref to ReadOnlyDevice\n// let l = \u0026dev.label;    // ✅ read — goes through Deref\n// dev.mutable = 1;       // ✅ write — mutable stays on the outer struct\n// dev.handle = 0;        // ❌ compile error — handle is read-only\n```\n\n\u003e [!NOTE]\n\u003e The generated `ReadOnly*` struct is public. You can implement `Deref`, `Display`,\n\u003e or other traits on it to forward or customize behavior.\n\n## Choosing a Macro\n\n- Use `#[cast]` if you want the familiar `readonly` behavior and the smallest\n  syntax change.\n- Use `#[embed]` if you want an explicit readonly view type and a safe\n  composition-based implementation with no unsafe code in the generated access\n  path.\n\n## Examples\n\nRunnable examples are available under `examples/`:\n\n- `cargo run --example basic`\n- `cargo run --example embed_deref`\n\n## Alternatives\n\nDirectly comparable:\n\n- [`readonly`](https://crates.io/crates/readonly): the canonical cast-based\n  readonly-field crate and the upstream implementation re-exported here as\n  `#[cast]`.\n\nRelated accessor-generation crates:\n\n- [`getset`](https://crates.io/crates/getset): generates getter/setter methods\n  instead of preserving direct field reads.\n- [`derive-getters`](https://crates.io/crates/derive-getters): generates public\n  getter methods for named structs.\n- [`fieldwork`](https://crates.io/crates/fieldwork): a broader accessor macro\n  system for structs and enums with many generated method styles.\n\nThe main difference is that those crates expose access through generated\nmethods, while `read-only` focuses on preserving direct readonly field access\nsyntax.\n\n## Testing\n\nThe workspace includes both compile-time macro tests and runtime tests.\n\n- `trybuild` macro tests cover pass/fail macro expansion behavior.\n- `tests/miri.rs` exercises the `#[cast]` unsafe pointer-cast path under\n  Miri to help detect undefined behavior and provenance issues.\n\nRun the Miri suite with:\n\n```sh\ncargo +nightly miri test --test miri\n```\n\nFor source coverage, the CI allows one uncovered line/region for the remaining\nLLVM coverage artifact in the proc-macro expansion code:\n\n```sh\ncargo llvm-cov --workspace --all-features --summary-only --fail-uncovered-lines 1 --fail-uncovered-regions 1\n```\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))\n- MIT license ([LICENSE-MIT](LICENSE-MIT))\n\nat your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fattackgoat%2Fread-only","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fattackgoat%2Fread-only","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fattackgoat%2Fread-only/lists"}