This crate provides traits that unify the Rust primitives so that they can be used as type parameters. The tree looks like this:
Fundamental: all non-pointer primitives: integers, floating-point numbers,
Numeric: all numbers
Integral: all 2’s-complement integers
Signed: all signed integers:
Unsigned: all unsigned integers:
.as_other() methods that allow
as-casting from one primitive to another. The other traits all expose every trait implementation, inherent method, and associated constant shared by all implementors, so that you should not have any source-code changes between using a named primitive and a generic type parameter from this crate.
In addition, the
AtMostN traits place bit-width numbers in the
N position, and are implemented on types that satisfy the width constraint.
But wait, there’s more! In addition to unifying the ordinary fundamentals, I also elected to unify pointers and references.
*const u8 and
*mut u8 are two different types, and
*const T and
*const U are two more different types. The explosion of individual pointer types not only by pointee but also by permission makes it basically impossible to work with pointers in a generic manner.
bitvec is built entirely around the concept of working on pointers whose pointee types are provided generically.
So I created the
Pointer<> structure to unify not only pointers to any pointee, but also the
Permission trait to unify
*mut as *const (it’s different than just
*const!). The end result,
Pointer<T: ?Sized, P: Permission>, represents any pointer with any write permission, implements the pointer primitives’ inherent API, and adds additional methods for safe and correct journeys through the permission system.
Of particular note are the
.cast_shared() goes from
Pointer<T, P> to
Pointer<T, (Shared, P)>. This is useless when
Shared, but when
Unique, it represents an originally-mutable pointer that has been temporarily downgraded to const, and can be re-promoted back to mutable in the future.
Pointers that were always
*const cannot be cast to
*mut without incurring a penalty in Miri. But pointers that were
*mut can be cast to
*const and then back to
*mut. Actually using the re-promoted pointer requires ensuring that no other pointers are attempting to read from the pointed-to location, as normal, but that is out of
Some(*mut T) when
Unique or a
(Shared, P) where the
P would eventually produce
Some(*mut T). This allows any level of recursively-added
(Shared, P) prefixes. The only
Permission type that returns
Pointer<T: Sized> has convenience methods for producing
Pointer<[T]> slice descriptors, which I need because
bitvec is built entirely on slice pointers; I also have a
NonNullPtr<T, P> wrapper type with the non-null optimization guarantee, and a
Reference type alias that resolves to either
&mut T as appropriate.
Thanks to Generic Associated Types, which began stabilizing in 1.65, I am able to accomplish all of this by storing the correct pointer primitive (
*const T for
Pointer<T, Shared> and
Pointer<T, (Shared, P)>;
*mut T for
Pointer<T, Unique>) and to implement
T -> U pointee-type conversions such as
This module has been very difficult to create and use, but has made
bitvec way simpler for me to implement. It shouldn’t leak out too much to
bitvec users, since they are encouraged to not propagate
bitvec’s generic arguments and to instead instantiate monomorphized types. Initializing a
bitvec type with
&T will propagate
P: Shared throughout the library; similarly,
&mut T propagates
T: Unique, and your code should never have to explicitly name this type parameter.
It does make the generated API documentation worse to read by adding yet another type parameter to all of the region descriptors, which is unfortunate.
I am building
funty::ptr entirely to suit my needs in the
bitvec project. If you think it could be useful to you, please use it and tell me what it’s missing for your story. I have the stable pointer API (which is not sufficient, unfortunately, but I am commited to working on stable Rust) and things that are convenient for me. I am only one user, and more input is always worthwhile.
Upcoming Pointer APIs
The standard library is experimenting with better APIs for working with the actual numeric value contained inside a pointer, metadata for wide pointers to difficult types such as slices and trait objects, and better use of slice pointers that does not require creating intermediate references.
I try to mirror these where I am able, but I can only do so much work in a shim. The intermediate period where these APIs exist in the compiler and standard library, and therefore can be tested by tools like Miri, but are not yet available to third-party libraries like
funty, is very awkward. I do my best, but ultimately, if you are writing the kind of code that needs this module, you are in a very murky grey area with regards to correctness under Miri.
funty will not make this worse, but it certainly won’t make it better just yet either.
This is a complicated topic, and I’m always working on improving what I’ve written down as my understanding of it so that either I can be corrected, or I can be useful to others. If you’re interested in poking beneath the skin of Rust’s concept of memory-addresses, I encourage you to start here and in the standard-library docs and see how well you can understand what
funty is doing. I’ll be honest, I have to re-read my own notes basically every time I resume working on it. This is hard, but it’s incredibly satisfying when it succeeds.