GithubHelp home page GithubHelp logo

jmg-duarte / sealed-rs Goto Github PK

View Code? Open in Web Editor NEW
45.0 2.0 5.0 187 KB

Macro for sealing traits and structures

Home Page: https://docs.rs/sealed/

License: Apache License 2.0

Rust 100.00%
rust-patterns rust future-proofing

sealed-rs's Introduction

#[sealed]

MSRV 1.56.0

This crate provides a convenient and simple way to implement the sealed trait pattern, as described in the Rust API Guidelines [1].

[dependencies]
sealed = "0.5"

Example

In the following code structs A and B implement the sealed trait T, the C struct, which is not sealed, will error during compilation.

Examples are available in examples/, you can also see a demo in demo/.

use sealed::sealed;

#[sealed]
trait T {}

pub struct A;

#[sealed]
impl T for A {}

pub struct B;

#[sealed]
impl T for B {}

pub struct C;

impl T for C {} // compile error

Arguments

This is the list of arguments that can be used in a #[sealed] attribute:

  • #[sealed(erase)]: turns on trait bounds erasure. This is useful when using the #[sealed] macro inside a function. For an example, see bound-erasure-fn example.

  • #[sealed(pub(crate))] or #[sealed(pub(in some::path))]: allows to tune visibility of the generated sealing module (the default one is private). This useful when the trait and its impls are defined in different modules. For an example, see nesting example. Notice, that just pub is disallowed as breaks the whole idea of sealing.

Contributing

See CONTRIBUTING.md.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

sealed-rs's People

Contributors

jmg-duarte avatar mingweisamuel avatar sh3rm4n avatar tyranron avatar

Stargazers

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

Watchers

 avatar  avatar

sealed-rs's Issues

Allow to seal blanket impls

Synopsis

With the current implementation it's impossible to seal a trait having blanket implementations and foreign types. For example:

#[sealed]
pub trait AsSpan {
    fn as_span(&self) -> Span;
}

impl AsSpan for Span {  // foreign type, cannot place #[sealed]
    fn as_span(&self) -> Self { *self }
}

impl<T: Spanned> AsSpan for &T { // blanket impl, cannot place #[sealed]
    fn as_span(&self) -> Span { self.span() }
}

Proposed solution

Place #[sealed] attribute on impl blocks rather than on types:

#[sealed]
pub trait AsSpan {
    fn as_span(&self) -> Span;
}
// expands to:
// pub trait AsSpan: private::Sealed {
//     fn as_span(&self) -> Span;
// }
// mod private {
//     pub trait Sealed {}
// }

#[sealed]
impl AsSpan for Span {
    fn as_span(&self) -> Self { *self }
}
// expands to:
// impl AsSpan for Span {  // foreign type, cannot place #[sealed]
//     fn as_span(&self) -> Self { *self }
// }
// impl private::Sealed for Span {}

#[sealed]
impl<T: Spanned> AsSpan for &T {
    fn as_span(&self) -> Span { self.span() }
}
// expands to:
// impl<T: Spanned> AsSpan for &T {
//     fn as_span(&self) -> Span { self.span() }
// }
// impl<T: Spanned> private::Sealed for &T {}

Playground

More benefits

Also, having a flat trait signature like pub trait Sealed {} imposes problems with coherence for more complicated traits. Consider the following example:

pub trait Set<V> {}

impl<T> Set<Option<T>> for T {}
impl<T> Set<Option<T>> for Option<T> {}

This compiles perfectly well. But once we seal it, we loose the coherence:

pub trait Set<V>: private::Sealed {}
mod private {
    pub trait Sealed {}
}

impl<T> Set<Option<T>> for T {}
impl<T> private::Sealed for T {}

impl<T> Set<Option<T>> for Option<T> {}
impl<T> private::Sealed for Option<T> {}

Playground

To overcome this limitation, the private::Sealed trait should fully follow the parametrization of the original trait:

pub trait Set<V>: private::Sealed<V> {}
mod private {
    pub trait Sealed<V> {}
}

impl<T> Set<Option<T>> for T {}
impl<T> private::Sealed<Option<T>> for T {}

impl<T> Set<Option<T>> for Option<T> {}
impl<T> private::Sealed<Option<T>> for Option<T> {}

Playground

And we cannot do that by putting a #[sealed] attribute on top of a type rather than an impl block, because we simply don't have parametrization info there.

Add sealing for specific functions

I recently read a very nice blog about sealed traits.
It talked about a few ways to seal not simply a trait, but specific methods that are part of the trait.
It is possible to seal a method, either for implementation, or also for use.

I am not sure if this would be a good addition to this crate, as it is quite niche, as well as somewhat different from what this crate is doing.

I think however, that it would make sense for the ecosystem that if this feature would be a thing, it would be in this crate, as it seems to me as a natural extension.

If others agree that it should be in here I'd be more than happy to make a PR for it, otherwise I'll make my own crate for it.

Allow to control visibility

While using pub(crate) mod allows to overcome nesting for impls, it also makes using #[sealed] pointless in bounds of a single crate: imagine usage in application crate, where the trait is declared in some module, not other crate.

It would be nice to control the visibility via attribute argument:

#[sealed]
pub trait T {}
// expands to
mod __seal_t {}

#[sealed(pub(crate))]
pub trait T {}
// expands to
pub(crate) mod __seal_t {}

// And even follow an arbitrary visiblity:
// https://doc.rust-lang.org/reference/visibility-and-privacy.html

#[sealed(pub(in crate::very::long::path))]
pub trait T {}
// expands to
pub(in crate::very::long::path) mod __seal_t {}

So, for the nesting case (which is quite rare, AFAIK), an user should specify the visibility explicitly, while for the usage in non-library crate, the sealing will work well out-of-the-box.

Support #[sealed] on enum

Currently, using #[sealed] on an enum fails with expected struct or trait. I don't see a reason this shouldn't work on enums too, besides not being implemented in the macro.

Provide a simpler test for const-generics

As it stands, the test for const-generics fails for version 1.51.0

┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
error[E0277]: `[T; N]` is not an iterator
  --> tests/pass/13-const-generics.rs:11:33
   |
11 |         IntoIterator::into_iter(arr).collect()
   |                                 ^^^
   |                                 |
   |                                 expected an implementor of trait `IntoIterator`
   |                                 help: consider borrowing here: `&arr`
   |
   = note: the trait bound `[T; N]: IntoIterator` is not satisfied
   = note: required because of the requirements on the impl of `IntoIterator` for `[T; N]`
   = note: required by `into_iter`

error[E0599]: the method `collect` exists for array `[T; N]`, but its trait bounds were not satisfied
  --> tests/pass/13-const-generics.rs:11:38
   |
11 |         IntoIterator::into_iter(arr).collect()
   |                                      ^^^^^^^ method cannot be called on `[T; N]` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `[T; N]: Iterator`
           which is required by `&mut [T; N]: Iterator`
           `[T]: Iterator`
           which is required by `&mut [T]: Iterator`
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈

Finding a simpler test that works would allow test runs for 1.50.0

Traits with lifetimes fail to compile

#[sealed::sealed]
pub trait Testing<'a> {}

error[E0106]: missing lifetime specifier
  |
1 | #[sealed::sealed]
  | ^ expected named lifetime parameter
  |
  = note: this error originates in the attribute macro `sealed::sealed` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using the `'a` lifetime
  |
1 | #<'a>[sealed::sealed]
  | ~~~~~

For more information about this error, try `rustc --explain E0106`.

Cannot seal two traits in the same module

This is due to #[sealed] attribute desugras into the hardcoded private name of a module.

The name should be unique per trait and must be derivable from the trait's name. Something like:

#[sealed]
pub trait MyTrait {}
// expands to:
// pub trait MyTrait: __my_trait::Sealed {}
// mod __my_trait { pub trait Sealed {} }

Do not run certain tests for certain versions

The most clear example would be const generics which were made "stable" in 1.51.0, as far as I know the crate should be able to work with earlier Rust versions.

Thus, ideally, the test would only run from 1.51 forwards and all others would run normally.

Cross-ref to #14 which is related but not the same

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.