Fills an `impl` with the associated items required by the trait.

Overview

portrait

GitHub actions crates.io crates.io docs.rs GitHub GitHub

Fill impl-trait blocks with default, delegation and more.

Motivation

Rust traits support provided methods, which are great for backwards compatibility and implementation coding efficiency. However they come with some limitations:

  • There is no reasonable way to implement an associated function if its return type is an associated type.
  • If a trait contains many highly similar associated functions, writing the defaults involves a lot of boilerplate. But users can only provide one default implementation for each method through procedural macros.

With portrait, the default implementations are provided at impl-level granularity instead of trait-level.

Usage

First of all, make a portrait of the trait to implement with the #[portrait::make] attribute:

#[portrait::make]
trait FooBar {
  // ...
}

Implement the trait partially and leave the rest to the #[portrait::fill] attribute:

#[portrait::fill(portrait::default)]
impl FooBar {}

The portrait::default part is the path to the "filler macro", which is the item that actually fills the impl block. The syntax is similar to #[derive(path::to::Macro)].

If there are implementations in a different module, the imports used in trait items need to be manually passed to the make attribute:

#[portrait::make(import(
  foo, bar::*,
  // same syntax as use statements
))]
trait FooBar {...}

If the fill attribute fails with an error about undefined foo_bar_portrait, import it manually together with the FooBar trait; the #[portrait::make] attribute generates this new module in the same module as the FooBar trait.

Provided fillers

portrait provides the following filler macros:

  • default: Implements each missing method and constant by delegating to Default::default() (Default is const-unstable and requires nightly with #![feature(const_default_impls)]).
  • delegate: Proxies each missing method, constant and type to an expression (usually self.field) or another type implementing the same trait.
  • lo]: Calls a format!-like macro with the method arguments.

How this works

Rust macros are invoked at an early stage of the compilation chain. As a result, attribute macros only have access to the literal representation of the item they are applied on, and cross-item derivation is not directly possible. Most macros evade this problem by trying to generate code that works regardless of the inaccessible information, e.g. the Default derive macro works by invoking Default::default() without checking whether the actual field type actually implements Default (since the compiler would do at a later stage anyway).

Unfortunately this approach does not work in the use case of portrait, where the attribute macro requires compile time (procedural macro runtime) access to the items of the trait referenced in the impl block; the only available information is the path to the trait (which could even be renamed to a different identifier).

portrait addresses this challenge by asking the trait to export its information (its "portrait") in the form of a token stream in advance. Through the #[portrait::make] attribute, a declarative macro with the same identifier is derived, containing the trait items. The (#[portrait::fill]) attribute on the impl block then passes its inputs to the declarative macro, which in turn forwards them to the actual attribute implementation (e.g. #[portrait::make]) along with the trait items.

Now the actual attribute has access to both the trait items and the user impl, but that's not quite yet the end of story. The trait items are written in the scope of the trait definition, but the attribute macro output is in the scope of the impl definition. The most apparent effect is that imports in the trait module do not take effect on the impl output. To avoid updating implementors frequently due to changes in the trait module, the #[portrait::make] attribute also derives a module that contains the imports used in the trait to be automatically imported in the impl block.

It turns out that, as of current compiler limitations, private imports actually cannot be re-exported publicly even though the imported type is public, so it becomes impractical to scan the trait item automatically for paths to re-export (prelude types also need special treatment since they are not part of the module). The problem of heterogeneous scope thus becomes exposed to users inevitably: either type all required re-exports manually through import, or make the imports visible to a further scope.

Another difficulty is that module identifiers share the same symbol space as trait identifiers (because module::Foo is indistinguishable from Trait::Foo). Thus, the module containing the imports cannot be imported together with the trait, and users have to manually import/export both symbols unless the trait is referenced through its enclosing module.

Disclaimer

portrait is not the first one to use declarative macros in attributes. macro_rules_attribute also implements a similar idea, although without involving the framework of generating the macro_rules! part.

You might also like...
List public items (public API) of library crates. Enables diffing public API between releases.

cargo-public-items List public items (the public API) of a Rust library crate by analyzing the rustdoc JSON of the crate. Automatically builds the rus

List public items (public API) of Rust library crates. Enables diffing public API between releases.

cargo wrapper for this library You probably want the cargo wrapper to this library. See https://github.com/Enselic/cargo-public-items. public_items Li

An interface for managing collections of labeled items and generating random subsets with specified restrictions

An interface for managing collections of labeled items and generating random subsets with specified restrictions

rpsc is a *nix command line tool to quickly search for file systems items matching varied criterions like permissions, extended attributes and much more.

rpsc rpsc is a *nix command line tool to quickly search for file systems items matching varied criterions like permissions, extended attributes and mu

Derive conversion traits when items are structurally similar.

structural-convert Derive conversion traits when items are structurally similar. Inspired by serde and struct-convert crates. Features One to one fiel

Pure-rust implementation of legacy H.263 video codec and associated color transforms

website | demo | nightly builds | wiki h263-rs h263-rs is a pure-Rust implementation of ITU-T Recommendation H.263 (2005/08), a video codec commonly u

The rust implementation of IPLD schemas and some associated functionalities.

Linked Data IPLD schemas and what to do with them. Beacon Directory of user content and metadata. Since this object should not change it can be used a

Authenticated Encryption with Associated Data Algorithms: high-level encryption ciphers

RustCrypto: Authenticated Encryption with Associated Data (AEAD) Algorithms Collection of Authenticated Encryption with Associated Data (AEAD) algorit

A balanced unbounded interval-tree in Rust with associated values in the nodes

store-interval-tree A balanced unbounded interval-tree in Rust with associated values in the nodes. Based on rudac and bio. Example use store_interval

Gnosis Safe Tx Service API client & associated tooling

Safe Transaction Service API Client Using the SDK Instantiate an API client use safe_sdk::SafeClient; /// From a chain id, by looking up hardcoded en

Trait aliases on stable Rust

trait-set: trait aliases on stable Rust Status: Project info: Support for trait aliases on stable Rust. Description This crate provide support for tra

A stack-allocated box that stores trait objects.

This crate allows saving DST objects in the provided buffer. It allows users to create global dynamic objects on a no_std environment without a global allocator.

A framework for iterating over collections of types implementing a trait without virtual dispatch
A framework for iterating over collections of types implementing a trait without virtual dispatch

zero_v Zero_V is an experiment in defining behavior over collections of objects implementing some trait without dynamic polymorphism.

The trait for generating structured data from arbitrary, unstructured input.

Arbitrary The trait for generating structured data from arbitrary, unstructured input. About The Arbitrary crate lets you construct arbitrary instance

The Arbitrary trait

Arbitrary The trait for generating structured data from arbitrary, unstructured input. About The Arbitrary crate lets you construct arbitrary instance

A stack for rust trait objects that minimizes allocations

dynstack A stack for trait objects that minimizes allocations COMPATIBILITY NOTE: dynstack relies on an underspecified fat pointer representation. Tho

Build SQLite virtual file systems (VFS) by implementing a simple Rust trait.

sqlite-vfs Build SQLite virtual file systems (VFS) by implementing a simple Rust trait. Documentation | Example This library is build for my own use-c

Generate enum from a trait, with converters between them
Generate enum from a trait, with converters between them

Derive macro for Rust that turns traits into enums, providing tools for calling funtions over channels

Macro for fast implementing serialize methods in serde::Serializer trait

impl_serialize! This library provides a simple procedural macro for fast implementing serialize methods in serde::Serializer trait. [dependencies] imp

Comments
  • Support using fill attribute on DeriveInput

    Support using fill attribute on DeriveInput

    Instead of manually writing #[portrait::fill] impl, it may be useful to #[portrait::fill] struct directly. This also provides the filler macro with direct information from the struct (to be precise, both the struct and the trait).

    enhancement 
    opened by SOF3 0
Owner
Jonathan Chan Kwan Yin
a.k.a. SOFe, @chankyin. Infrastructure developer. I Go during the day and Rust at night. My code on GitHub are not affiliated with my company unless specified.
Jonathan Chan Kwan Yin
A lending iterator trait based on generic associated types and higher-rank trait bounds

A lending iterator trait based on higher-rank trait bounds (HRTBs) A lending iterator is an iterator which lends mutable borrows to the items it retur

Sebastiano Vigna 6 Oct 23, 2023
my attempt at compromise between unwrapping and bullying my dependencies' authors for Error impl

string-eyre Has this happened to you? error[E0599]: the method `wrap_err` exists for enum `Result<(), tauri::Error>`, but its trait bounds were not sa

Michał Sidor 1 Nov 25, 2021
Decode SCALE bytes into custom types using a scale-info type registry and a custom Visitor impl.

scale-decode This crate attempts to simplify the process of decoding SCALE encoded bytes into a custom data structure given a type registry (from scal

Parity Technologies 6 Sep 20, 2022
Todo-server impl by using actix.rs

Todo-Server Todo-server impl by using actix.rs Why actix.rs 1. It's blazing fast Benchmark From now on, Actix is the second fastest web framework in t

null 5 Nov 17, 2022
impl LSP (Language Server Protocol) Server for librime

rime-ls 为 rime 输入法核心库 librime (的部分功能) 实现 LSP 协议, 从而通过编辑器的代码补全功能输入汉字. 项目还处在早期阶段, 各方面都非常不成熟. 目标是提供 rime + LSP 的通用解决方案, 在不同编辑器内实现与其他 rime 前端类似的输入体验. Feat

zilch40 55 Jan 22, 2023
Rustpad is an efficient and minimal collaborative code editor, self-hosted, no database required

Rustpad is an efficient and minimal open-source collaborative text editor based on the operational transformation algorithm

Eric Zhang 2.5k Dec 31, 2022
A command-line tool to generate a list of required missing Android OS Project blobs.

aosp-missing-blobs aosp-missing-blobs is a nifty tool to identify required blobs (.so) that are missing from AOSP ROM builds, and to show which existi

Josh 176 Dec 16, 2022
This is an example Nostr rust project to enable '402 Payment Required' responses for requests to paid content.

Nostr Paywall Example This is an example Nostr rust project to enable 402 Payment Required responses for requests to paid content. To prove payment, a

Blake Jakopovic 6 May 6, 2023
Send files between machines - no installation required!

skicka.pwy.io:99 Skicka (from Swedish send) allows to send files between machines - no installation required! Transmitting a file is as easy as piping

Patryk Wychowaniec 55 Jul 12, 2023
A syntax exploration of eventually stable Rust Iterator items

Rust Iterator Items: a syntax exploration This crate is a thin wrapper around the unstable generator feature, allowing users to create new items that

Esteban Kuber 28 Sep 1, 2022