Git Product home page Git Product logo

blanket's Introduction

๐Ÿงฃ blanket Star me

A simple macro to derive blanket implementations for your traits.

Actions Codecov License Source Crate Documentation Changelog GitHub issues

๐Ÿ” Overview

The Rust standard library has plenty of traits, but they shine in how well they integrate with new types. Declare an implementation of std::io::Write for a type W, and you also get it for &mut W and Box<W>! This however translates into a lot of boilerplate code that can be hard to maintain, which is why many crates don't bother providing the same convenience implementations.

This is where blanket comes in! This crate helps you build the same kind of blanket implementations for your own traits with as least additional code as possible: in fact, this is as close as what a derive macro would look like for a trait item.

๐Ÿ”Œ Usage

blanket exports a single eponymous attribute macro, which can be imported simply after the crate has been added to the Cargo.toml dependencies:

extern crate blanket;
use blanket::blanket;

#[blanket(derive(...))]

Use this macro attribute to derive a blanket implementation for a trait, provided the trait methods fit the constraints for that derive, such as only declaring methods with &self of &mut self as their receiver. The following derives are available:

Derive Impl block fn (&self) fn (&mut self) fn (self)
Ref impl<T: Trait + ?Sized> Trait for &T โœ”๏ธ
Rc impl<T: Trait + ?Sized> Trait for Rc<T> โœ”๏ธ
Arc impl<T: Trait + ?Sized> Trait for Arc<T> โœ”๏ธ
Mut impl<T: Trait + ?Sized> Trait for &mut T โœ”๏ธ โœ”๏ธ
Boxยน impl<T: Trait + ?Sized> Trait for Box<T> โœ”๏ธ โœ”๏ธ
Boxยฒ impl<T: Trait> Trait for Box<T> โœ”๏ธ โœ”๏ธ โœ”๏ธ
Cow impl<T: Trait + ToOwned + ?Sized> Trait for Cow<_, T> โœ”๏ธ

For instance, with our own version of std::fmt::Write, we can provide an implementation for Box<impl Write> and &mut impl Write:

extern crate blanket;
use blanket::blanket;

#[blanket(derive(Mut, Box))]
pub trait Write {
    fn write_str(&mut self, s: &str) -> std::fmt::Result;
    fn write_char(&mut self, c: char) -> std::fmt::Result {
         self.write_str(c.encode_utf8(&mut [0; 4]))
    }
}

Note that we can't derive Ref because the Write trait we declared expects mutable references, which we can't provide from an immutable reference. If we were to try, the compiler would warn us:

---- src/lib.rs - (line 55) stdout ----
error: cannot derive `Ref` for a trait declaring `&mut self` methods
 --> src/lib.rs:61:18
  |
8 |     fn write_str(&mut self, s: &str) -> std::fmt::Result;
  |                  ^^^^^^^^^

#[blanket(default = "...")]

blanket can delegate default implementations of trait methods to functions of another module. This can be useful for some traits such as visitors to provide a default behaviour as an external function, such as what syn::visit is doing.

The following example implements a very simple visitor trait for types able to process a &str char-by-char.

extern crate blanket;
use blanket::blanket;

#[blanket(default = "visitor")]
trait Visitor {
    fn visit_string(&self, s: &str);
    fn visit_char(&self, c: char);
}

mod visitor {
    use super::Visitor;

    pub fn visit_string<V: Visitor + ?Sized>(v: &V, s: &str) {
        for c in s.chars() {
            v.visit_char(c);
        }
    }

    pub fn visit_char<V: Visitor + ?Sized>(v: &V, c: char) {}
}

blanket will check that all methods are declared without a default block, and then create a default implementation for all of the declared methods, generating the following code:

trait Visitor {
    fn visit_string(&self, s: &str) {
      visitor::visit_string(self, s)
    }
    fn visit_char(&self, c: char) {
      visitor::visit_char(self, c)
    }
}

๐Ÿ“ To-Do

  • โœ“ Delegation of default method to external functions.
  • โœ“ Support for traits with generic arguments.
  • โœ“ #[derive(Ref)]
  • โœ“ #[derive(Mut)]
  • โœ“ #[derive(Box)] for both sized and unsized types.
  • โœ“ #[derive(Rc)]
  • โœ“ #[derive(Arc)]
  • โœ“ #[derive(Cow)]

๐Ÿค Credits

blanket is developed and maintained by:

The following people contributed to the project:

๐Ÿ“‹ Changelog

This project adheres to Semantic Versioning and provides a changelog in the Keep a Changelog format.

๐Ÿ“œ License

This library is provided under the open-source MIT license.

blanket's People

Contributors

alexanderlinne avatar althonos avatar justinlovinger avatar najamelan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

blanket's Issues

Use stricter paths when referring to items

blanket/src/types/arc.rs

Lines 11 to 13 in ce0b8ab

fn wrap(ty: &syn::Ident) -> syn::Type {
parse_quote!(std::sync::Arc<#ty>)
}

Just a couple small tips/recommendations:

It'd probably be better to use e.g. ::std::sync::Arc instead of std::sync::Arc here (and likewise for all of the other types), otherwise the path refers to whatever is named std in the local scope (which isn't necessarily the std crate).

https://doc.rust-lang.org/reference/paths.html#path-qualifiers

Further these types should be pulled from ::core else ::alloc (if not found in core) as these crates are simply re-exported by std but have the additional benefit of being supported/accessible in no_std crates.

blanket 0.4 + async-trait don't work together

blanket 0.4 works with async traits (yay!). Unfortunately, async traits in Rust are still rather limited in functionality so it is often necessary to use the async-trait crate. Unfortunately, blanket does not work with traits that use async-trait. Consider the following code:

trait Foo {
    fn foo();
}

#[blanket::blanket(derive(Box, Mut))]
trait Bar {
    async fn bar();
}

#[blanket::blanket(derive(Box, Mut))]
#[async_trait::async_trait]
trait Xyzzy {
    async fn xyzzy();
}

fn main() {
    println!("Hello, world!");
}

In the Cargo.toml, I have:

[dependencies]
blanket = "0.4"
async-trait = "0.1"

When compiling the above, I get:

$ cargo build
   Compiling async-blanket v0.1.0 (/tmp/async-blanket)
error[E0195]: lifetime parameters or bounds on method `foo` do not match the trait declaration
  --> src/main.rs:13:17
   |
13 |     async fn xyzzy();
   |              ---^-
   |              |  |
   |              |  lifetimes do not match method in trait
   |              lifetimes in impl do not match this method in trait

For more information about this error, try `rustc --explain E0195`.
error: could not compile `async-blanket` (bin "async-blanket") due to 2 previous errors
$ rustc --version
rustc 1.76.0 (07dca489a 2024-02-04)

Blanket does not work with async traits

Rust 1.75 introduced async methods, but they cannot be used with blanket 0.2. Consider:

use blanket;

#[blanket::blanket(derive(Ref))]
trait Foo {
    async fn bar(&self);
}

fn main() {
    println!("Hello, world!");
}
$ cargo build
   Compiling blanket v0.1.0 (/tmp/blanket)
error[E0308]: mismatched types
 --> src/main.rs:3:1
  |
3 | #[blanket::blanket(derive(Ref))]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found associated type
  |
  = note:    expected unit type `()`
          found associated type `impl Future<Output = ()>`
note: calling an async function returns a future
 --> src/main.rs:3:1
  |
3 | #[blanket::blanket(derive(Ref))]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = note: this error originates in the attribute macro `blanket::blanket` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider `await`ing on the `Future`
  |
3 | #[blanket::blanket(derive(Ref))].await
  |                                 ++++++

For more information about this error, try `rustc --explain E0308`.
error: could not compile `blanket` (bin "blanket") due to 1 previous error

The following compiles fine (just remove the async from Foo::bar):

use blanket;

#[blanket::blanket(derive(Ref))]
trait Foo {
    fn bar(&self);
}

fn main() {
    println!("Hello, world!");
}

Changing bar to be an async method () fails:

When providing a blanket implmentation of T also optionally provide an appropriate `as_boxed` method

Let's say I have a Box<T> that implements T, and some function fn f(...) -> impl T. f can return a Box<T>, because Box<T> implements T. If the caller needs to put the return t in a box, then the caller has to do: Box::new(t). That's unfortunate, because t was actually already a Box<T>, we just didn't know (or rather, couldn't exploit the fact).

A solution is to add a method to T: fn as_boxed(self) -> Box<Self>. The implementation on T would do: return Box::new(self), and the implementation on Box<T> would do: return self. No double boxing. This is what we use in Sequoia 1, 2 where we ended up with an object being boxed millions of time.

As far as I can tell, blanket wraps all functions. So, we can't use blanket in this case. I'd like an option to either not wrap particular functions, or a mechanism to add an as_boxed method. As I can't imagine other functions that shouldn't be wrapped, this point solution is probably sufficient.

Wrapping a trait with static methods fails

Consider:

use blanket;

#[blanket::blanket(derive(Ref))]
trait Foo {
    fn bar();

    fn bam(&self);
}

fn main() {
    println!("Hello, world!");
}

Compiling this results in:

$ cargo build
   Compiling blanket v0.1.0 (/tmp/blanket)
error: custom attribute panicked
 --> src/main.rs:3:1
  |
3 | #[blanket::blanket(derive(Ref))]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: message: called `Option::unwrap()` on a `None` value

error: could not compile `blanket` (bin "blanket") due to 1 previous error

It would be good if blanket could handle these traits. At the very least, it would be nice if the error message were more informative.

Failed on trait with generic arguments.

I wanted to give blanket a try because, well, boilerplate. But:

#[ blanket(derive( Ref, Mut, Box, Rc )) ]
//
pub trait SpawnHandle<Out: 'static + Send>
{
   ...
}

gave me:

error[E0658]: associated type bounds are unstable
  --> src/iface/spawn_handle.rs:57:23
   |
57 | pub trait SpawnHandle<Out: 'static + Send>
   |                       ^^^^^^^^^^^^^^^^^^^
   |
   = note: see issue #52662 <https://github.com/rust-lang/rust/issues/52662> for more information

and

error[E0107]: missing generics for trait `spawn_handle::SpawnHandle`
  --> src/iface/spawn_handle.rs:57:11
   |
57 | pub trait SpawnHandle<Out: 'static + Send>
   |           ^^^^^^^^^^^ expected 1 type argument
   |
note: trait defined here, with 1 type parameter: `Out`
  --> src/iface/spawn_handle.rs:57:11
   |
57 | pub trait SpawnHandle<Out: 'static + Send>
   |           ^^^^^^^^^^^ ---
help: use angle brackets to add missing type argument
   |
57 | pub trait SpawnHandle<Out><Out: 'static + Send>

It generates:

impl<Out: 'static + Send, SH: SpawnHandle<Out: 'static + Send>>
            SpawnHandle<Out: 'static + Send> for Box<SH>

Where the manual implementation would do:

impl<T, Out> SpawnHandle<Out> for Box<T> 

   where T  : SpawnHandle<Out> + ?Sized, 
         Out: 'static + Send

So it seems the issue is that the trait bounds on the generic type are repeated and they shouldn't.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.