Easily create trait-dispatching sum types (TagSets).
* Fixed discriminant values - Each included type's discriminant will not change,
even as new variants are added in future versions.
* Build-in serde support - Easily derive Serialize and Deserialize for your set.
* Supports any trait - Trait items which are not dispatchable (such as associated types) can be
explicitly provided.
* Compositable sets - Sets can easily include other sets as sub-sets.
* Extended trait defaults - Annotate your trait to allow for trait defaults not supported by standard Rust,
such as associated type defaults.
* (Mostly) scope-independent - With a few rare exceptions, nothing is required to be in scope,
and types and traits are not required to have unique identifiers.
```rust
pub mod my_trait {
#[telety::telety(crate::my_trait)]
pub trait MyTrait {
fn ascii_char(&self) -> u8;
}
}
pub mod my_impl {
#[derive(Debug)]
pub struct A;
impl super::my_trait::MyTrait for A {
fn ascii_char(&self) -> u8 {
b'A'
}
}
#[derive(Debug)]
pub struct B;
impl super::my_trait::MyTrait for B {
fn ascii_char(&self) -> u8 {
b'B'
}
}
}
pub mod my_set {
# use tagset::tagset;
#[tagset(impl super::my_trait::MyTrait)]
#[tagset(super::my_impl::A)]
#[tagset(super::my_impl::B)]
#[tagset(derive(Debug))]
pub struct MySet;
}
fn main() {
use my_trait::MyTrait as _;
let set_a: my_set::MySet = my_impl::A.into();
let set_b: my_set::MySet = my_impl::B.into();
assert_eq!(set_a.ascii_char(), b'A');
assert_eq!(set_b.ascii_char(), b'B');
let a: my_impl::A = set_a.try_into().unwrap();
let result_b: Result<my_impl::A, my_set::MySet> = set_b.try_into();
let set_b: my_set::MySet = result_b.unwrap_err();
}
```
You can also add generic parameters and bounds:
```rust
pub mod my_trait {
#[telety::telety(crate::my_trait)]
pub trait MyTrait<U> { }
}
pub mod my_impl {
#[derive(Clone)]
pub struct Parameter<T>(T);
impl<T, U> super::my_trait::MyTrait<U> for Parameter<T> { }
}
pub mod my_set {
# use tagset::tagset;
#[tagset(impl<U> super::my_trait::MyTrait<U>
// See the type bounds section for details
where for<VAR> VAR: Clone
)]
#[tagset(super::my_impl::Parameter<T>)]
pub struct MySet<T>;
}
# fn main() { }
```
# Discriminants
tagset supports consistent discriminants. Like enums, by default numbering is incremental starting at 0.
To choose the initial discriminant, use the `#[tagset(index(...))]` attribute. `index` takes an integer
literal with an optional suffix if you want to specify the discriminant type. e.g., `[tagset(index(-4ii8))]`
sets the discriminant type as `i8`, and the first discriminant value to `-4`.
To modify the default numbering, use `#[tagset(reserved(...))]` or `#[tagset(deprecated(...))]` attributes.
(Both are functually equivalent, but semantically distinguish discriminants that can and cannot be used in
the future). Both attributes take a range literal with an open start and an inclusive or exclusive end, e.g.
`..5` or `..=5`. All discriminants starting after the previous variant to the end of the range will be skipped.
```rust
# use tagset::tagset;
pub struct A;
pub struct B;
pub struct C;
#[tagset(index(2u8))]
#[tagset(A)] #[tagset(deprecated(..5))]
#[tagset(B)] #[tagset(C)] #[tagset(reserved(..16))]
pub struct MySet;
fn main() {
use tagset::TagSet as _;
let a: MySet = A.into();
let b: MySet = B.into();
let c: MySet = C.into();
assert_eq!(a.discriminant(), 2u8);
assert_eq!(b.discriminant(), 5u8);
assert_eq!(c.discriminant(), 6u8);
}
```
Discriminant values are available at runtime through the [TagSet] trait, and at compile-time with
[TagSetDiscriminant] (variant type to variant discriminant) and [TagSetTypeI32] (variant discriminant to variant )
(use the trait corresponding to the discriminant type, [TagSetTypeU8], etc.).
# External traits
If a trait comes from an external crate, you can create a telety proxy for it:
```rust
pub mod external_crate {
pub trait MyTrait {
fn ascii_char(&self) -> u8;
}
}
pub mod internal_proxy {
#[telety::telety(crate::internal_proxy, proxy = "crate::external_crate::MyTrait")]
pub trait MyTrait {
fn ascii_char(&self) -> u8;
}
}
pub mod proxy_impl {
# use tagset::tagset;
pub struct A;
impl super::external_crate::MyTrait for A {
fn ascii_char(&self) -> u8 {
b'A'
}
}
pub struct B;
impl super::internal_proxy::MyTrait for B {
fn ascii_char(&self) -> u8 {
b'B'
}
}
#[tagset(impl super::internal_proxy::MyTrait)]
#[tagset(A)]
#[tagset(B)]
pub struct Set;
}
# fn main() { }
```
# Overrides
You can override item definitions inside traits:
```rust
pub mod my_trait {
#[telety::telety(crate::my_trait)]
pub trait MyTrait {
type Output;
fn get(&self) -> Self::Output;
}
}
pub mod my_impl {
pub struct A(pub i32);
impl super::my_trait::MyTrait for A {
type Output = i32;
fn get(&self) -> Self::Output {
self.0
}
}
pub struct B(pub u32);
impl super::my_trait::MyTrait for B {
type Output = u32;
fn get(&self) -> Self::Output {
self.0
}
}
}
pub mod my_set {
# use tagset::tagset;
#[tagset(impl super::my_trait::MyTrait {
// associated types usually require an override
type Output = i64;
// trait functions will usually delegate to the variant by default,
// but sometimes this is not possible (e.g. there is no reciever,
// or the reciever is not convertable such as Rc<Self>), or not
// desired.
// Overrides are provided 2 macros to generate implementations based
// based on the included types:
// `match_by_value!(value, value_identifier => expr)`
// `value` is a value of type `Self`, `&Self`, or `&mut Self`.
// `value_identifier` is any identifier to be used in `expr` as
// the inner value of the set.
// `expr` is an expression in which `value_identifier` is the
// inner value of the set.
// `match_by_discriminant!(discriminant, type_identifier => expr)`
// `discriminant` is a value of the type of the set's discriminant.
// If the value is invalid, the function will panic.
// `type_identifier` is any identifier to be substituted with the
// type path corresponding to `discriminant`.
// `expr` is an expression in which `type_identifier` will be
// substituted for `discriminant`'s corresponding type path.
fn get(&self) -> Self::Output {
match_by_value!(self, value => value.get() as i64)
}
})]
#[tagset(super::my_impl::A)]
#[tagset(super::my_impl::B)]
pub struct MySet;
}
fn main() {
use my_trait::MyTrait as _;
let a: my_set::MySet = my_impl::A(3i32).into();
let b: my_set::MySet = my_impl::B(4u32).into();
assert_eq!(a.get() + b.get(), 7);
}
```
# Type bounds
tagset support a special syntax when specifying type bounds:
`for <VAR>: VAR: MyTrait`
This bound is expanded for variant, substituting `VAR` in the bound for the type path of the variant.
For example, in a set containing types `A`, `B`, and `C`, this is equivalent to:
```rust, ignore
A: MyTrait,
B: MyTrait,
C: MyTrait,
```
# Includes
Sets are compositable: you can use `#[tagset(include(MySubSet))]` to include all variant types from
a telety-enabled `MySubSet`.
Included types will retain their *relative* discriminants, but not their *absolute* discriminants.
```rust
pub mod my_subset {
# use tagset::tagset;
pub struct A;
pub struct B;
#[telety::telety(crate::my_subset)]
#[tagset(index(7i32))]
#[tagset(A)]
#[tagset(reserved(..11))]
#[tagset(B)]
pub struct MySubSet;
}
pub mod my_set {
# use tagset::tagset;
pub struct C;
pub struct D;
#[tagset(index(3u64))]
#[tagset(C)]
#[tagset(include(super::my_subset::MySubSet))]
#[tagset(D)]
pub struct MySet;
}
fn main() {
let a: my_subset::MySubSet = my_subset::A.into();
let b: my_subset::MySubSet = my_subset::B.into();
assert_eq!(a.discriminant(), 7);
assert_eq!(b.discriminant(), 11);
use tagset::TagSet as _;
let a: my_set::MySet = my_subset::A.into();
let b: my_set::MySet = my_subset::B.into();
let c: my_set::MySet = my_set::C.into();
let d: my_set::MySet = my_set::D.into();
assert_eq!(a.discriminant(), 4);
assert_eq!(b.discriminant(), 8);
assert_eq!(c.discriminant(), 3);
assert_eq!(d.discriminant(), 9);
}
```
# Metadata
tagset supports adding metadata to traits to control default implementation.
To add metadata to a trait, use the [tagset_meta] attribute, then use `#[meta(...)]` attributes
where desired.
## default
`default` can be applied to associated types or associated functions.
When applied to an associated type, it takes a type path which will serve as the default for
any implementing set. In this case, it is not necessary to provide an override in the set impl.
When applied to a trait function, it takes a function body. The macros mentioned in the overrides
section are available here as well.
Note: telety aliases all types used in the function body so they are usable at the set definition
without importing them, but it cannot alias `use` items or values. Those must be textually valid
at the location of the set definition.
## bounds
By default, the set implements traits when all its variants implement the trait. This may be too
strict or too lax for some default implementations. The `bounds` meta attribute takes
where predicates to use as the baseline bounds to implement the trait for a set. See the
type bounds section for special bounds syntax.
```rust
pub mod my_trait {
#[telety::telety(crate::my_trait)]
#[tagset::tagset_meta]
#[meta(bounds())]
pub trait MyTrait {
#[meta(default(std::convert::Infallible))]
type Error;
#[meta(default {
match_by_value!(self, value => Ok(std::any::type_name_of_val(value)))
})]
fn name(&self) -> Result<&'static str, Self::Error>;
}
}
pub mod my_set {
# use tagset::tagset;
pub struct A;
pub struct B;
#[tagset(impl super::my_trait::MyTrait)]
#[tagset(A)]
#[tagset(B)]
pub struct MySet;
}
fn main() {
use my_trait::MyTrait as _;
let a: my_set::MySet = my_set::A.into();
let b: my_set::MySet = my_set::B.into();
assert_eq!(a.name().unwrap().split("::").last().unwrap(), "A");
assert_eq!(b.name().unwrap().split("::").last().unwrap(), "B");
}
```
# derive
tagset currently uses a private internal type when implementing the set. Because of this,
derives do not work as-is. Wrap them in a tagset attribute:
```rust
pub mod my_set {
# use tagset::tagset;
#[derive(Clone)]
pub struct A;
#[tagset(derive(Clone))]
#[tagset(A)]
pub struct MySet;
}
fn main() {
let before: my_set::MySet = my_set::A.into();
let after = before.clone();
}
```
# serde
When the serde feature is enabled, tagset includes Serialize and Deserialize proxies
with default implementations. Currently, the Deserialize implementation requires also
implementing the trait [serde::DeserializeFromDiscriminant], but this may be automatic
in the future.
```rust,ignore
# // This does not work as a doctest for some reason, see tests/serde_roundtrip.rs
# use tagset::tagset;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Number(pub i32);
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Text(pub String);
#[tagset(impl tagset::proxy::serde::Serialize)]
#[tagset(impl<'de> tagset::serde::DeserializeFromDiscriminant<'de>)]
#[tagset(impl<'de> tagset::proxy::serde::Deserialize<'de>)]
#[tagset(Number)]
#[tagset(Text)]
pub struct MySet;
fn main() -> anyhow::Result<()> {
let value: MySet = Text("asdf".to_string()).into();
let serialized = serde_json::to_string(&value)?;
let deserialized: MySet = serde_json::from_str(&*serialized)?;
let text: Text = deserialized.try_into().unwrap_or_else(|_| unreachable!());
assert_eq!(text.0, "asdf");
Ok(())
}
```
The default (de)serialization implementation structures the set as a 2-element tuple.
The first element is the discriminant, the second is the inner variant.