{"id":15654437,"url":"https://github.com/sof3/portrait","last_synced_at":"2025-04-09T15:06:02.977Z","repository":{"id":65702691,"uuid":"594944565","full_name":"SOF3/portrait","owner":"SOF3","description":"Trait-agnostic derive macros","archived":false,"fork":false,"pushed_at":"2025-02-13T06:28:30.000Z","size":91,"stargazers_count":27,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T15:05:54.321Z","etag":null,"topics":["delegation","proc-macro"],"latest_commit_sha":null,"homepage":"","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/SOF3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"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}},"created_at":"2023-01-30T03:49:03.000Z","updated_at":"2025-02-13T06:28:34.000Z","dependencies_parsed_at":"2023-11-07T05:09:19.938Z","dependency_job_id":"e4777d06-e46b-4d5f-ae4b-4ab6c6941240","html_url":"https://github.com/SOF3/portrait","commit_stats":{"total_commits":10,"total_committers":2,"mean_commits":5.0,"dds":0.09999999999999998,"last_synced_commit":"5b90816da8ce423035242125ac7f41e0bae0848e"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fportrait","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fportrait/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fportrait/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fportrait/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SOF3","download_url":"https://codeload.github.com/SOF3/portrait/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055284,"owners_count":21040157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["delegation","proc-macro"],"created_at":"2024-10-03T12:51:48.247Z","updated_at":"2025-04-09T15:06:02.953Z","avatar_url":"https://github.com/SOF3.png","language":"Rust","readme":"# portrait\n\n[![GitHub actions](https://github.com/SOF3/portrait/workflows/CI/badge.svg)](https://github.com/SOF3/portrait/actions?query=workflow%3ACI)\n[![crates.io](https://img.shields.io/crates/v/portrait.svg)](https://crates.io/crates/portrait)\n[![crates.io](https://img.shields.io/crates/d/portrait.svg)](https://crates.io/crates/portrait)\n[![docs.rs](https://docs.rs/portrait/badge.svg)](https://docs.rs/portrait)\n[![GitHub](https://img.shields.io/github/last-commit/SOF3/portrait)](https://github.com/SOF3/portrait)\n[![GitHub](https://img.shields.io/github/stars/SOF3/portrait?style=social)](https://github.com/SOF3/portrait)\n\nFill impl-trait blocks with default, delegation and more.\n\n## Motivation\n\nRust traits support provided methods,\nwhich are great for backwards compatibility and implementation coding efficiency.\nHowever they come with some limitations:\n\n- There is no reasonable way to implement an associated function\n  if its return type is an associated type.\n- If a trait contains many highly similar associated functions,\n  writing the defaults involves a lot of boilerplate.\n  But users can only provide one default implementation for each method\n  through procedural macros.\n\nWith `portrait`, the default implementations are provided\nat `impl`-level granularity instead of trait-level.\n\n## Usage\n\nFirst of all, make a portrait of the trait to implement\nwith the `#[portrait::make]` attribute:\n\n```rs\n#[portrait::make]\ntrait FooBar {\n  // ...\n}\n```\n\nImplement the trait partially and leave the rest to the `#[portrait::fill]` attribute:\n\n```rs\n#[portrait::fill(portrait::default)]\nimpl FooBar {}\n```\n\nThe `portrait::default` part is the path to the \"filler macro\",\nwhich is the item that actually fills the `impl` block.\nThe syntax is similar to `#[derive(path::to::Macro)]`.\n\nIf there are implementations in a different module,\nthe imports used in trait items need to be manually passed to the make attribute:\n\n```rs\n#[portrait::make(import(\n  foo, bar::*,\n  // same syntax as use statements\n))]\ntrait FooBar {...}\n```\n\nIf the fill attribute fails with an error about undefined `foo_bar_portrait`,\nimport it manually together with the FooBar trait;\nthe `#[portrait::make]` attribute generates this new module\nin the same module as the `FooBar` trait.\n\n## Provided fillers\n\n`portrait` provides the following filler macros:\n\n- `default`:\n  Implements each missing method and constant by delegating to `Default::default()`\n  (`Default` is const-unstable and requires nightly with `#![feature(const_default_impls)]`).\n- `delegate`:\n  Proxies each missing method, constant and type\n  to an expression (usually `self.field`) or another type implementing the same trait.\n- `log`:\n  Calls a `format!`-like macro with the method arguments.\n\n## How this works\n\nRust macros are invoked at an early stage of the compilation chain.\nAs a result, attribute macros only have access to the literal representation\nof the item they are applied on,\nand cross-item derivation is not directly possible.\nMost macros evade this problem by trying to generate code\nthat works regardless of the inaccessible information,\ne.g. the `Default` derive macro works by invoking `Default::default()`\nwithout checking whether the actual field type actually implements `Default`\n(since the compiler would do at a later stage anyway).\n\nUnfortunately this approach does not work in the use case of `portrait`,\nwhere the attribute macro requires compile time (procedural macro runtime) access\nto the items of the trait referenced in the `impl` block;\nthe only available information is the path to the trait\n(which could even be renamed to a different identifier).\n\n`portrait` addresses this challenge by\nasking the trait to export its information (its \"portrait\")\nin the form of a token stream in advance.\nThrough the `#[portrait::make]` attribute,\na *declarative* macro with the same identifier is derived,\ncontaining the trait items.\nThe (`#[portrait::fill]`) attribute on the `impl` block\nthen passes its inputs to the declarative macro,\nwhich in turn forwards them to the actual attribute implementation\n(e.g. `#[portrait::make]`) along with the trait items.\n\nNow the actual attribute has access to both the trait items and the user impl,\nbut that's not quite yet the end of story.\nThe trait items are written in the scope of the trait definition,\nbut the attribute macro output is in the scope of the impl definition.\nThe most apparent effect is that\nimports in the trait module do not take effect on the impl output.\nTo avoid updating implementors frequently due to changes in the trait module,\nthe `#[portrait::make]` attribute also derives a module\nthat contains the imports used in the trait\nto be automatically imported in the impl block.\n\nIt turns out that, as of current compiler limitations,\nprivate imports actually cannot be re-exported publicly\neven though the imported type is public,\nso it becomes impractical to scan the trait item automatically for paths to re-export\n(prelude types also need special treatment since they are not part of the module).\nThe problem of heterogeneous scope thus becomes exposed to users inevitably:\neither type all required re-exports manually through import,\nor make the imports visible to a further scope.\n\nAnother difficulty is that\nmodule identifiers share the same symbol space as trait identifiers\n(because `module::Foo` is indistinguishable from `Trait::Foo`).\nThus, the module containing the imports cannot be imported together with the trait,\nand users have to manually import/export both symbols\nunless the trait is referenced through its enclosing module.\n\n## Disclaimer\n\n`portrait` is not the first one to use declarative macros in attributes.\n[`macro_rules_attribute`][macro_rules_attribute] also implements a similar idea,\nalthough without involving the framework of generating the `macro_rules!` part.\n\n  [macro_rules_attribute]: https://docs.rs/macro_rules_attribute/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsof3%2Fportrait","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsof3%2Fportrait","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsof3%2Fportrait/lists"}