#[strict_types]Expand description
A macro to enforce strict typing on struct and enum fields. It checks if any field uses a primitive type and generates a compile-time error if it does. The idea is to encourage the use of newtype wrappers for primitive types to ensure type safety and clarity in the codebase.
The motivation behind this macro is to prevent the use of primitive types directly in structs, which can lead to confusion and bugs. The primitive types are often too generic and have a too wide range of values, can be misused in different contexts, and do not convey the intent of the data being represented, especially meaning having useful names for the types and intentions behind them.
Also, often, the primitive types are not only checked for the width
of the allowed range of values, but must also contain some values
that are not allowed from within the allowed range. For example,
a u8 type can be used to represent a percentage, but it can also
be used to represent a count of items, which is a different
concept. In this case, the u8 type does not convey the intent of
the data being represented, and it is better to use a newtype wrapper
to make the intent clear. There might be at least two “Percentage”
types in the codebase, one is limited to the range of 0-100, and
another type which can go beyond 100 (but still not less than zero),
to express the surpassing of the 100% mark. Not to mention that
sometimes, in certain contexts, the percentage can be negative
(e.g. when calculating the difference between two values).
This macro is a way to enforce the use of newtype wrappers for
primitive types in structs, which can help to avoid confusion and
bugs in the codebase. It is a compile-time check that will generate
an error if any field in a struct uses a primitive type directly.
§Example usage:
use strict_typing::strict_types;
#[repr(transparent)]
struct MyNewTypeWrapper<T>(T);
#[strict_types]
struct MyStruct {
// This will generate a compile-time error
// because `u8` is a primitive type.
// my_field: u8,
// But this not:
my_field: MyNewTypeWrapper<u8>,
}Yes, this is a very simple macro, but it is intended to be used as a way to enforce strict typing in the codebase, and to encourage the use of newtype wrappers for primitive types in structs.
/// # Example with disallow which adds types to the disallowed
list:
use strict_typing::strict_types;
#[strict_types(disallow(String))]
struct MyStruct {
// This will generate a compile-time error
// because `String` is now also a forbidden type.
my_field: String,
}When a type is added to the disallowed list or removed from it,
the macro requires the user to document the reason for
the change in the /// # Strictness section of the documentation.
The documentation should be in the form of a list of items,
where each item is a type that is allowed or disallowed, example:
use strict_typing::strict_types;
/// # Strictness
///
/// - [String] - this is a disallowed type, because it is too bad.
#[strict_types(disallow(String))]
struct MyStruct {
my_field: String,
}To remove from the default disallow list, you can use the
allow directive:
use strict_typing::strict_types;
/// # Strictness
///
/// - [u8] - this is an allowed type, because it is used for
/// representing a small number of items.
#[strict_types(allow(u8))]
struct MyStruct {
my_field: u8,
}The macro also supports working directly on the whole impl and
trait items, analysing the function signatures and their
return types; however, annotating a trait method or an impl method
is yet impossible due to Rust limitations.