WARNING: until v0.1.0 this crate is in brainstorming phase, expect major breaking changes
The newtype pattern is a powerful idiom in Rust to use the type system to distinguish between different uses of the same type.
https://doc.rust-lang.org/rust-by-example/generics/new_types.html
Unfortunally implementing the newtype pattern requires some boilerplate code. This Crate
provides the [newtype!] declarative macro and traits to conveniently implement this
boilerplate code for you.
How it works
We define newtypes as tuple structs with only one single data member. Thus it's type, lifetimes, generics are all well known and can be reused when implementing member functions and traits for the newtype instead typing repetive code. Think about it as code-completion at compile time.
Additionally the user can define a invariant that must must hold at any time. This invariant will be checked whenever a newtype is constructed and at some other (unspecified) places. This invariant is a contract that must never be broken. Newtypes are not a refinement type system, careless usage and trait implementations can lead to cases where the contract can be possibly broken on misuse.
newtypedecl augments derive, where approbiate one can and should use derive. The newtype macro addresses things that are commonly not covered by derive macros.
Simple Example
The simplest case lets you use it like this:
use newtype;
newtype!
Terminating a newtype with a semicolon will implement a preselected (opinionated) set of
methods and traits for it. See the [newtype!] macro docs for a list what this implements by
default.
Control Block Example
When using a control block in braces instead the terminating semicolon after a the newtype definiton we gain some DWIM superpowers:
use *;
newtype!
let example = default;
assert_eq!;
example.print_value;
Principles
The macro tries the most generic applicable form of methods and trait implementations. This
usually means that parameters are impl Into or impl AsRef. Future versions may introduce
dyn dispatch.
Dangerous usage
Whenever a newtype is not only cosmetic but implements some contract which must be fulfilled at any time, either by just documenting this or by adding invariants to the newtype there is a danger that misuse breaks this contracts when a reference to the inner value is reachable. This not only covers mutable references. Immutable references are subject of this problem too when the inner value provides some kind of interior mutability.
The only way to address these problems is by being careful of what interfaces and traits a newtype implement. This is not something this library can enforce, we only try to accomplish this in a best-effort base and leave the actual decisions to the implementor.
When you want to be very safe then do not implement traits that expose a reference to the inner. Only provide methods that do safe access and transformations.
Panics
Constructing a newtype from a inner value can fail the invariant check. The 'try' forms
try_new() and TryFrom handle this and will return a runtime error in invariant
failure. Other forms that take a raw inner to be inserted into a newtype will panic in case
the invariant check fails. Thus users should not implement the traits and functions that
infallibly construct or alter a newtype when invariants are defined.
The invariant must holds at any time, never make the assumption it is ok to store an invalid value not even temporaly.