#version #type #procedural #proc-macro

macro verty

procedural macro to generate different versions of a type

2 releases

new 0.1.1 Nov 27, 2024
0.1.0 Nov 27, 2024

#992 in Development tools

Download history 57/week @ 2024-11-21

72 downloads per month

MIT-0 license

72KB
2.5K SLoC

Versioned types for Rust

This is a procedural macro that makes writing types that have multiple versions short and easy!

This is done by just putting #[versioned] on a single type definition, which will then be converted into multiple types (one for each version) and, optionally, an enum of all versions of the type.

The versions have to be usizes.

Example

use verty::versioned;

# #[versioned(start = 1, end = 3)] struct Schema { /* ... */}

#[versioned(start = 1)]
struct CustomerData {
	name: String,
	#[ver = 1..=2]
	age: u8,
	#[ver = 3..]
	is_over_18: bool,
	last_login_ip: ver_match! {
		1 => { std::net::Ipv4Addr };
		2.. => { std::net::IpAddr }
	},
	#[ver = 2]
	agreed_to_cookies: bool,
	schema: VerType!(Schema),
}

expands to

# #[verty::versioned(start = 1, end = 3)] struct Schema { /* ... */}

struct CustomerDataV1 {
	name: String,
	age: u8,
	last_login_ip: std::net::Ipv4Addr,
	schema: SchemaV1
}

struct CustomerDataV2 {
	name: String,
	age: u8,
	last_login_ip: std::net::IpAddr,
	agreed_to_cookies: bool,
	schema: SchemaV2
}

struct CustomerDataV3 {
	name: String,
	is_over_18: bool,
	last_login_ip: std::net::IpAddr,
	schema: SchemaV3
}

Features

All three kinds of types are supported: struct, enum, union.

You can put a #[ver = <range>] helper attribute on any fields, variants, variant fields, or generic parameters to restrict which versions of the type they appear in.

  • The number of versions the type has will be determined by the highest version explicitly mentioned in any of these helper attributes.
  • The range must be a range literal of usize literals or a single usize literal (e.g. 2 is equivalent to 2..=2)
  • The version types are called <input>V<n> by default, where <input> is the name of the input type and <n> is the version.

You can put a #[ver_attr(<range>, <attr>)] helper attribute anywhere to apply #[<attr>] in its place, but only for versions in <range>. This works exactly like #[cfg_attr(...)].

You can put a #[ver_where(<range>, <clauses>)] helper attribute on the type definition to apply where <clauses> to it, but only for versions in <range>.

You can use the ver_match! pseudo-macro to produce a statement/expression/type/item depending on the expanded version. The syntax is ver_match! { $(<range> => { <anything> });+ $(;)? }. Note that each version must appear exactly once, no more, no less.

You can use the VerType! pesudo-macro as a field type to get version-dependent types. For example, VerType!(Foo) expands to FooV<n> in version <n>.

  • The input of VerType! can also contain pseudo-macros, which are expanded accordingly, for example VerType!(Bar<ver_gen!(1, T)>) would expand to BarV0<> for version 0 and BarV1<T> for version 1.

You can use the ver_gen! pseudo-macro in generic argument lists to only include a generic argument for a given version. Its syntax is ver_gen!(<range>, <generic argument>).

You can give arguments to the macro (#[versioned(<args>)] instead of #[versioned]) to influence code generation. The options are comma-separated, with the following possibilities:

  • start = <n>: Let <n> be the first version instead of 0. This is probably mostly useful with 1 (i.e. start = 1). Also makes ..<m> ranges start with <n> instead of 0. Also makes using a version number below it anywhere an error.
  • end = <n>: Let <n> be the last version instead of the highest mentioned one. Also makes using a version number above it anywhere an error.
  • rename(<n> => <ident>): Rename the type for version <n> to <ident>.

Similar crates

  • duplicate is very useful to copy-paste items with slight changes between each of them, but it requires a moderate amount of boilerplate, especially when you have more complex differences between the different copies.

Dependencies

~210–650KB
~15K SLoC